In Laravel, what is an event subscriber, how do you register one, and when would you choose it over separate listener classes?
answer
- one class, several events
- subscribe(Dispatcher $events)
- return an event => method array
- Event::subscribe in AppServiceProvider
- discovery can find it too
basics
~20 sAn event subscriber is one class whose subscribe(Dispatcher $events) method maps several events to its own handler methods. Register it with Event::subscribe() in AppServiceProvider, or let discovery find handle*-named methods; use it when several events share one concern.
solid answer
~40 sA **subscriber** groups handlers for several events in one class. Its `subscribe(Dispatcher $events)` method either calls `$events->listen(OrderPlaced::class, [self::class, 'onPlaced'])` for each event, or returns an array such as `[OrderPlaced::class => 'handleOrderPlaced']`, which Laravel resolves against the subscriber's own methods. You register it with `Event::subscribe(RecordOrderAnalytics::class)` in `AppServiceProvider::boot()`; the class is built through the container. If it lives in `app/Listeners` and its methods are named `handle*` with typed first parameters, discovery already registers them, so calling `Event::subscribe` as well runs each handler twice. Choose a subscriber when one concern such as analytics reacts to many events and shares dependencies; keep separate listeners when each reaction is independent or needs its own queue settings.
code
php · 27 lines<?php
namespace App\Listeners;
use App\Events\OrderCancelled;
use App\Events\OrderPlaced;
use App\Events\OrderShipped;
use App\Services\Analytics;
use Illuminate\Events\Dispatcher;
class RecordOrderAnalytics
{
public function __construct(private Analytics $analytics) {}
public function onPlaced(OrderPlaced $e): void { $this->analytics->track('placed', $e->order); }
public function onShipped(OrderShipped $e): void { $this->analytics->track('shipped', $e->order); }
public function onCancelled(OrderCancelled $e): void { $this->analytics->track('cancelled', $e->order); }
public function subscribe(Dispatcher $events): array
{
return [
OrderPlaced::class => 'onPlaced',
OrderShipped::class => 'onShipped',
OrderCancelled::class => 'onCancelled',
];
}
}go deeper
Recall that a subscriber is one class with a subscribe() method and that Event::subscribe registers it.
Explain the array and imperative styles of subscribe(), how discovery treats handle*-named methods, and why that can double-register a subscriber.
Decide when grouping by concern beats one listener per reaction, considering ownership, queue settings and how teammates find handlers.
Set conventions for where cross-cutting reactions live so that side effects of domain events stay discoverable as the codebase grows.
## What a subscriber is In Laravel, a **listener** is usually one class per reaction: `SendOrderConfirmation` handles `OrderPlaced` and nothing else. An **event subscriber** turns that around: one class declares handlers for several events and tells the dispatcher about them itself. The typical fit is a cross-cutting concern that reacts to a whole family of events, such as an analytics recorder that writes a reporting row for `OrderPlaced`, `OrderShipped` and `OrderCancelled`. ## Writing one A subscriber needs a `subscribe()` method that receives the event dispatcher (`Illuminate\Events\Dispatcher`). It can work in two styles: 1. **Imperative** — call `$events->listen()` for each event, passing `[self::class, 'method']` or any other listener. 2. **Declarative** — return an array of event class names to method names. Laravel checks each string against the subscriber's own methods and registers `[SubscriberClass, method]`; a value can also be an array of several method names, or a separate listener class. The handler methods receive the event object like any listener's `handle()` and are resolved through the container, so the subscriber's constructor can take dependencies such as an analytics client. ## How the returned array is read When `subscribe()` returns an array, the dispatcher walks it event by event. Each value is wrapped into a list, and for each entry: 1. if it is a string naming a public method of the subscriber, the dispatcher registers `[SubscriberClass, thatMethod]` for the event 2. otherwise the value is passed to `listen()` unchanged, so it may be another listener class or a callable That means a subscriber can mix its own methods with existing listener classes, for example `OrderShipped::class => ['onShipped', NotifyWarehouse::class]`. When `subscribe()` returns nothing, as in the imperative style, the dispatcher simply keeps whatever the method registered through `$events->listen()`. Handlers are stored as class-and-method pairs, so the container builds the subscriber again whenever one of them runs; state kept on the instance between two events is therefore not something to rely on. ## Registering it There are two routes, and they must not be combined for the same class: - `Event::subscribe(RecordOrderAnalytics::class)` in `AppServiceProvider::boot()` calls `subscribe()` and registers whatever it declares - **event discovery** already scans `app/Listeners`; if the subscriber lives there and its handlers are public methods named `handle*` with a typed first parameter (`handleOrderPlaced(OrderPlaced $event)`), discovery registers those methods on its own, without calling `subscribe()` If both routes apply, every handler is registered twice and runs twice. `php artisan event:list` shows the duplicate. The usual fix is to name the handlers `on*` (for example `onOrderPlaced`) and register explicitly, or to rely on discovery and delete the `Event::subscribe` call. ## Subscriber or separate listeners | Concern | Subscriber | Separate listener classes | |---|---|---| | Several events, one responsibility | natural fit | many near-identical classes | | Shared dependencies or helpers | one constructor | repeated in each class | | Per-reaction queue settings | shared by the whole class | set per listener | | Finding who handles an event | read `subscribe()` | read type-hints or `event:list` | | Testing a reaction | call the method directly | call `handle()` directly | Guidelines: - prefer a subscriber when the grouping reflects **one owner** of the code (analytics, audit logging, cache invalidation for an aggregate) - prefer separate listeners when reactions belong to **different owners** or evolve independently, such as stock reservation and customer email - remember that queue behaviour is decided per class: a subscriber implementing `ShouldQueue` queues every one of its handlers as a separate job, all configured by the same class-level settings, so a mix of fast and slow handlers is a sign to split it ## A note on built-in events Subscribers are also a tidy way to react to framework events in one place, for example the authentication events `Illuminate\Auth\Events\Login` and `Logout`, without creating one listener class per event. The same double-registration rule applies there.
- A subscriber in app/Listeners has methods handleOrderPlaced(OrderPlaced $e) and is also passed to Event::subscribe; what goes wrong?Discovery registers `handleOrderPlaced` because its name starts with `handle` and its first parameter is typed, and `Event::subscribe` registers it again from the returned array. Each `OrderPlaced` then records analytics twice. Rename the handlers (for example `onOrderPlaced`) or drop the explicit call.
- Can a subscriber's returned array map one event to more than one method?Yes. The value for an event can be an array of method names; Laravel wraps each value and registers every method it finds on the subscriber. A value that is not one of the subscriber's methods is passed to `listen()` as an ordinary listener.
saying these in an interview costs you the question
- A subscriber must be listed in a $subscribe array because Event::subscribe does not exist.
- Discovery calls a subscriber's subscribe() method automatically.
- Subscriber handlers cannot receive constructor-injected services.
- A subscriber is the only way to handle several events in one class.