Queueable listeners — ShouldQueue on a listener
Concept
PSR-14 (Event Dispatcher) is a PHP-FIG standard that defines contracts for event dispatching systems. Laravel's event system is compatible with PSR-14 but predates it, using its own interfaces. Understanding the internals helps debug event flow.
Illuminate\Events\Dispatcher: The core event system class. Bound in the container as events. event() helper resolves this binding and calls dispatch().
How dispatch works internally:
event(new OrderPlaced($order))callsapp('events')->dispatch($event).- Dispatcher checks
$this->listenersfor the event class + any wildcard listeners. - For each listener: if it's a class name string, it resolves it from the container (
app($listener)), callshandle($event). - If the listener implements
ShouldQueue, the Dispatcher wraps the listener + event in aCallQueuedListenerjob and dispatches to the queue instead of callinghandle()directly. - The queue worker resolves the listener class, deserializes the event, calls
handle().
Wildcard listeners: Event::listen('App\Events\Order*', ...) — matches any event whose class name starts with App\Events\Order.
Stopping event propagation: Return false from a listener to stop further listeners from receiving the event. Only works for synchronous listeners.
Event::fake(): Swaps the dispatcher with a fake that records dispatches. Used in tests to assert events were dispatched without actually running listeners.
Code Example
<?php
// Wildcard listeners — catch all events in a namespace
\Illuminate\Support\Facades\Event::listen('App\Events\*', function(string $eventName, array $data) {
// $data[0] is the event object
\Illuminate\Support\Facades\Log::debug("Event dispatched: $eventName", [
'class' => get_class($data[0]),
]);
});
// Stop propagation — return false from listener
class FirstListener
{
public function handle(OrderPlaced $event): bool|null
{
if ($event->order->isFraudulent()) {
return false; // Stop — subsequent listeners won't fire
}
// ... normal handling
return null; // continue propagation (default)
}
}
// Event::fake() in tests — essential for testing event dispatching
use Illuminate\Support\Facades\Event;
test('order placement dispatches OrderPlaced event', function() {
Event::fake(); // Swap dispatcher — no listeners fire
$order = Order::factory()->create();
event(new \App\Events\OrderPlaced($order, 'stripe'));
Event::assertDispatched(\App\Events\OrderPlaced::class);
Event::assertDispatched(\App\Events\OrderPlaced::class, function($event) use ($order) {
return $event->order->id === $order->id;
});
Event::assertNotDispatched(\App\Events\OrderCancelled::class);
});
// Fake only specific events (others fire normally)
Event::fake([\App\Events\OrderPlaced::class]);
// Event discovery — auto-discover listeners from handle() type hints
// In EventServiceProvider:
public function shouldDiscoverEvents(): bool { return true; }
// Laravel scans Listeners directory, reads handle() method type hints, maps automatically
// Dispatching from code — both equivalent
event(new OrderPlaced($order, 'stripe'));
\Illuminate\Support\Facades\Event::dispatch(new OrderPlaced($order, 'stripe'));
OrderPlaced::dispatch($order, 'stripe'); // via Dispatchable trait