0

Defining events and listeners — make:event, make:listener

Beginner5 min read·lv-18-002

Concept

Queueable listeners defer listener work to a background queue instead of running synchronously in the request cycle. This is critical for listeners that do expensive work: sending emails, calling external APIs, processing images.

Making a listener queueable: Implement Illuminate\Contracts\Queue\ShouldQueue on the listener class. No methods required from the interface — it's a marker interface. Laravel automatically detects it and dispatches the listener as a queued job.

Queue configuration on listeners:

  • public string $connection = 'redis': Which queue connection to use.
  • public string $queue = 'emails': Which queue to push onto.
  • public int $delay = 10: Delay in seconds before the job is processed.

ShouldQueueAfterCommit: If your listener dispatches within a database transaction, you may want it to queue only after the transaction commits: implements ShouldQueue, ShouldQueueAfterCommit.

Conditionally queuing: Override shouldQueue(Event $event): bool. If false, the listener runs synchronously.

Retries and failure: public int $tries = 3, public int $backoff = 5. If the listener fails after all retries, it's sent to the failed_jobs table.

handle() receives the event: The event object must be serializable (models are automatically serialized via SerializesModels).

Code Example

php
<?php
namespace App\Listeners;

use App\Events\OrderPlaced;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendOrderConfirmationEmail implements ShouldQueue
{
    // Queue configuration
    public string $connection = 'redis';
    public string $queue      = 'emails';
    public int    $delay      = 5;    // wait 5 seconds before processing
    public int    $tries      = 3;    // retry 3 times on failure
    public int    $backoff    = 60;   // wait 60s between retries

    public function handle(OrderPlaced $event): void
    {
        \Illuminate\Support\Facades\Mail::to($event->order->customer->email)
            ->send(new \App\Mail\OrderConfirmation($event->order));
    }

    // Conditional queuing — run synchronously for small orders
    public function shouldQueue(OrderPlaced $event): bool
    {
        return $event->order->total >= 10; // queue only if order total >= $10
    }

    // Handle failure
    public function failed(OrderPlaced $event, \Throwable $exception): void
    {
        \Illuminate\Support\Facades\Log::error('Failed to send order confirmation', [
            'order_id' => $event->order->id,
            'error'    => $exception->getMessage(),
        ]);
    }
}

// ShouldQueueAfterCommit — only queued after DB transaction commits
use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;

class ProcessOrderAfterPayment implements ShouldQueue, ShouldQueueAfterCommit
{
    public function handle(OrderPlaced $event): void
    {
        // This won't be queued until the outer DB::transaction() commits
        // Safe to use even when dispatched inside a transaction
    }
}

// In EventServiceProvider — mixing sync and async listeners
protected $listen = [
    \App\Events\OrderPlaced::class => [
        \App\Listeners\UpdateInventory::class,         // sync — runs immediately
        \App\Listeners\SendOrderConfirmationEmail::class, // queued — deferred
        \App\Listeners\NotifyWarehouse::class,         // queued — deferred
    ],
];