Queued Publishing
Introduction
queue() sends an activity to a Laravel queue for a worker to publish. publish() writes it in the current process.
Configure a Laravel queue worker to process queued activities. The sync connection runs them immediately.
Queueing Activities
Storyfeed::activity()
->by($request->user())
->action('place', $order)
->to($order->shop)
->queue(); After the worker publishes it:
As with Laravel mailables, call queue() to queue the activity explicitly. It uses your configured queue connection and returns no activity.
Storyfeed::record() stays synchronous.
Queue and Connection
use Storyfeed\Facades\Storyfeed;
Storyfeed::activity()
->by($request->user())
->action('place', $order)
->to($order->shop)
->onConnection('database')
->onQueue('feed')
->queue();After a worker handles the job:
Laravel's Queueable methods chain before queue(), just as on a queued job. Run a worker for the chosen connection and queue:
php artisan queue:work database --queue=feedThe database connection needs Laravel's jobs table.
Delays
Call delay() before queue() to set when the job becomes available. The builder uses delay() in place of a mailable's later():
use Storyfeed\Facades\Storyfeed;
Storyfeed::activity()
->by($request->user())
->action('place', $order)
->to($order->shop)
->delay(now()->addSeconds(10))
->queue();The worker determines when the job runs. After publication:
Per-Verb Defaults
use App\Models\Order;
use Storyfeed\Facades\Story;
Story::for(Order::class)->verb('place')
->headline(':actor placed :object with :target')
->icon('shopping-bag')
->onConnection('database')
->onQueue('feed')
->delay(10)
->afterCommit();Replace the verb's existing declaration with this one. Queue settings on the builder override these defaults. The declaration's delay() accepts seconds, a positive interval string, or a DateInterval.
These settings do not queue a publish() call. Call queue() to send the activity to the queue.
Queueing Story Classes
A Story class that implements ShouldQueue queues when published. It may also implement ShouldBeUnique. See Queueing Stories for an example. The transaction and missing-model settings below also apply.
Queues and Transactions
| Setting | Queued Publish |
|---|---|
| no explicit setting | follows the queue connection's after_commit setting |
afterCommit() | dispatches after the transaction commits; rollback discards it |
beforeCommit() | dispatches without waiting for the commit |
With synchronous publish(), afterCommit() leaves the returned activity unsaved until commit. A rollback discards it. Outside a transaction, it publishes immediately.
Capturing Labels at Dispatch
use Storyfeed\Facades\Storyfeed;
Storyfeed::activity()
->by($request->user())
->action('place', $order)
->to($order->shop)
->snapshotNow()
->queue();By default, the worker captures model labels and details when it publishes the activity. Call snapshotNow() to capture them when queue() is called. queue() always captures published_at at dispatch unless you set publishedAt().
Missing Models
use App\Models\Order;
use App\Stories\OrderWasPlaced;
use Storyfeed\Facades\Story;
Story::for(Order::class)->verb('place', OrderWasPlaced::class)
->deleteWhenMissingModels();If a model is deleted before the worker restores it, the job fails with ModelNotFoundException. This declaration discards the job without recording an activity. Replace the class binding from Story Classes with this one.
You may also call deleteWhenMissingModels() on the builder. A Story class can set public bool $deleteWhenMissingModels = true, which overrides the declaration. Models must still be restored when you use snapshotNow().
Carrying Actors and Context
By default, a queued activity uses the user authenticated when it was queued and any Storyfeed::actor() or Storyfeed::context() scope active then. See Carrying Roles Into Queued Jobs for inherited scopes and Role Precedence for the full order.
Publishing From Application Jobs
Calling publish() inside a job records the worker's current time unless you set publishedAt(). Capture the event time in the job's constructor if the activity belongs to that earlier moment:
<?php
namespace App\Jobs;
use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon;
use Storyfeed\Facades\Storyfeed;
class RecordOrder implements ShouldQueue
{
use Dispatchable, Queueable, SerializesModels;
public Carbon $occurredAt;
public function __construct(public Order $order)
{
$this->occurredAt = now();
}
public function handle(): void
{
Storyfeed::activity('place', $this->order)
->to($this->order->shop)
->publishedAt($this->occurredAt)
->publish();
}
}<?php
namespace App\Jobs;
use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon;
use Storyfeed\Facades\Storyfeed;
class RecordOrder implements ShouldQueue
{
use Dispatchable, Queueable, SerializesModels;
public Carbon $occurredAt;
public function __construct(public Order $order)
{
$this->occurredAt = now();
}
public function handle(): void
{
Storyfeed::record(
verb: 'place',
object: $this->order,
target: $this->order->shop,
publishedAt: $this->occurredAt,
);
}
}Publication time determines ordering and the activity's grouping period. Also capture any event values the job needs, since restored models contain the worker's current data. Calling queue() directly already captures publication time at dispatch.
Testing Queued Publishing
Use Storyfeed::fake() and assertQueued() to check the queued activity without sending a job. Testing Queued and Event Publishing covers the queued assertions and when to use Laravel's queue fake instead.