Listeners and Subscribers

Queued Listeners and Event Subscribers

A listener that calls a slow service belongs on a queue. make:listener NotifyWarehouse --event=OrderPlaced --queued adds ShouldQueue, so the dispatcher pushes a job instead of calling handle(), and the queue attributes of Queues and Scheduling apply:

app/Listeners/NotifyWarehouse.php (imports omitted)PHP
#[Queue('warehouse')]
#[Tries(3)]
class NotifyWarehouse implements ShouldQueue
{
    public function handle(OrderPlaced $event): void
    {
        $lines = $event->order->items->count();
        Log::info("Warehouse picking list for order #{$event->order->id}: {$lines} lines");
    }
}

After a checkout, the jobs table held one row on the warehouse queue with "maxTries":3 in its payload, and a worker ran it:

Output of 187
$ php artisan queue:work --queue=warehouse --once
  2026-09-23 08:55:13 App\Listeners\NotifyWarehouse .................. RUNNING
  2026-09-23 08:55:13 App\Listeners\NotifyWarehouse ............. 17.09ms DONE
$ tail -n 1 storage/logs/laravel.log
[2026-09-23 08:55:13] local.INFO: Warehouse picking list for order #1: 2 lines

SerializesModels put only the order's key in the payload, so the worker reloaded the order and its items. A failed($event, $e) method runs after the last attempt, shouldQueue($event) decides at runtime, and closures queue with Event::listen(queueable(fn (OrderPlaced $e) => ...)). Workers and retries are Queues and Scheduling.

A subscriber groups handlers in one class: CatalogCacheSubscriber's subscribe() returns [OrderPlaced::class => 'forgetAfterSale', ProductRestocked::class => 'forgetAfterRestock']. Those names do not start with handle, so discovery skips them and Event::subscribe(CatalogCacheSubscriber::class) in boot() registers them, as the event:list output above showed.