symfony/event-dispatcher
Symfony EventDispatcher component lets application parts communicate via events. Dispatch events and register listeners or subscribers to react to them, enabling decoupled, extensible architectures with a lightweight, reusable event system.
Installation:
composer require symfony/event-dispatcher
Laravel already includes this package as a dependency, so no additional setup is required unless you're using Symfony-specific features.
First Use Case: Dispatch an event and listen to it:
// Dispatch an event
$dispatcher = app(\Symfony\Component\EventDispatcher\EventDispatcher::class);
$event = new \Symfony\Component\EventDispatcher\GenericEvent('user.created', ['user_id' => 1]);
$dispatcher->dispatch($event, 'user.created');
// Register a listener
$dispatcher->addListener('user.created', function ($event) {
// Handle the event
});
Laravel Integration:
Use Laravel’s built-in Event facade for seamless integration:
use Illuminate\Support\Facades\Event;
// Dispatch
Event::dispatch(new UserCreated(1));
// Listen (in EventServiceProvider)
protected $listen = [
UserCreated::class => [
'App\Listeners\SendWelcomeEmail',
],
];
Event facade for Laravel-specific usage.#[AsEventListener] attribute (Symfony 7.4+) for modern PHP attribute-based listeners.Event-Driven Architecture: Replace direct service calls with events to decouple components.
// Before (tight coupling)
$authService->verify($user);
// After (decoupled)
event(new UserVerificationRequested($user));
Listener Registration:
// app/Providers/EventServiceProvider.php
protected $listen = [
UserVerificationRequested::class => [
'App\Listeners\LogVerification',
'App\Listeners\SendVerificationEmail',
],
];
$dispatcher->addListener(UserVerificationRequested::class, $listener);
Event Propagation: Control whether subsequent listeners execute:
$event->stopPropagation(); // Stops further listeners
Priority-Based Listeners: Use priorities to define execution order:
$dispatcher->addListener(UserVerificationRequested::class, [$listener, 'handle'], 100);
Subscribers: Group related listeners in a single class:
class UserVerificationSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
UserVerificationRequested::class => 'handleVerification',
];
}
public function handleVerification(UserVerificationRequested $event)
{
// Logic
}
}
Laravel Queues: Dispatch events in jobs to trigger async listeners:
class ProcessUserDataJob implements ShouldQueue
{
public function handle()
{
event(new UserDataProcessed($userId));
}
}
Real-Time Updates: Use Laravel Echo or Pusher to broadcast events:
class OrderStatusUpdatedListener
{
public function handle(OrderStatusUpdated $event)
{
broadcast(new OrderStatusUpdatedBroadcast($event->orderId, $event->status));
}
}
Event Sourcing: Store events in a database for audit trails:
class StoreEventListener
{
public function handle(GenericEvent $event)
{
EventStore::store($event);
}
}
Dynamic Feature Flags: Toggle listeners based on config:
if (config('features.new_ui')) {
$dispatcher->addListener(UserCreated::class, $newUIListener);
}
Memory Leaks in TraceableEventDispatcher:
TraceableEventDispatcher is reset during dispatch.EventDispatcher instead for non-debugging scenarios.Listener Order Chaos:
$dispatcher->addListener('event.name', [$listener, 'method'], 100);
Circular Dependencies:
stopPropagation() or guard against loops:
if (!$event->isPropagationStopped()) {
$event->stopPropagation();
}
Performance Overhead:
Laravel-Specific Quirks:
Event facade uses a singleton dispatcher, which may not behave identically to Symfony’s EventDispatcher.app(EventDispatcher::class) for Symfony-specific features.Enable Debugging:
Use TraceableEventDispatcher to log dispatched events:
$dispatcher = new TraceableEventDispatcher();
$dispatcher->addListener('*', function ($event) {
\Log::debug('Event dispatched:', ['event' => $event->getName()]);
});
Listener Not Firing:
EventServiceProvider or dynamic additions).#[AsEventListener] for type safety).Event Not Stopping Propagation:
stopPropagation() is called on the event object.Custom EventDispatcher:
Extend EventDispatcher for custom logic:
class CustomEventDispatcher extends EventDispatcher
{
public function dispatchWithMiddleware($event, $eventName = null)
{
// Add middleware logic
parent::dispatch($event, $eventName);
}
}
Event Subscriber Interface:
Implement EventSubscriberInterface for reusable subscriber logic:
class AnalyticsSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
'user.created' => 'trackUserCreation',
'order.placed' => 'trackOrder',
];
}
}
Event Attributes (PHP 8.0+):
Use #[AsEventListener] for modern attribute-based listeners:
#[AsEventListener(event: UserCreated::class, method: 'handle')]
class UserCreatedListener
{
public function handle(UserCreated $event)
{
// Logic
}
}
Laravel Service Provider Integration:
Bind Symfony’s EventDispatcher to Laravel’s container:
public function register()
{
$this->app->singleton(\Symfony\Component\EventDispatcher\EventDispatcher::class, function ($app) {
$dispatcher = new EventDispatcher();
// Add listeners dynamically
return $dispatcher;
});
}
Laravel’s Event Dispatcher:
Laravel’s Event facade uses a singleton instance of Illuminate\Events\Dispatcher, which is a wrapper around Symfony’s EventDispatcher. To access the underlying Symfony dispatcher:
$symfonyDispatcher = app(\Symfony\Component\EventDispatcher\EventDispatcher::class);
Priority Defaults:
Symfony’s default priority is 0. Laravel’s EventServiceProvider may override this, so explicitly set priorities if needed.
Global vs. Local Listeners:
EventServiceProvider) run for all dispatches.How can I help you explore Laravel packages today?