Defining events and listeners — make:event, make:listener
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
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
],
];