0

Queueable listeners — ShouldQueue on a listener

Intermediate5 min read·lv-18-004

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:

  1. event(new OrderPlaced($order)) calls app('events')->dispatch($event).
  2. Dispatcher checks $this->listeners for the event class + any wildcard listeners.
  3. For each listener: if it's a class name string, it resolves it from the container (app($listener)), calls handle($event).
  4. If the listener implements ShouldQueue, the Dispatcher wraps the listener + event in a CallQueuedListener job and dispatches to the queue instead of calling handle() directly.
  5. 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
<?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