symfony/event-dispatcher
Symfony EventDispatcher lets application parts communicate via dispatched events and listeners/subscribers. It provides a flexible event system for decoupled architecture, supporting priority-based listeners and a consistent dispatching API for PHP apps.
Installation:
composer require symfony/event-dispatcher
Laravel already bundles this component, so no additional installation is needed if using Laravel’s built-in event system.
First Use Case:
namespace App\Events;
class UserRegistered
{
public function __construct(public string $userId) {}
}
namespace App\Listeners;
use App\Events\UserRegistered;
class SendWelcomeEmail
{
public function handle(UserRegistered $event): void
{
// Logic to send email
info("Welcome email sent to user {$event->userId}");
}
}
EventServiceProvider (Laravel):
protected $listen = [
UserRegistered::class => [
SendWelcomeEmail::class,
],
];
use App\Events\UserRegistered;
use Illuminate\Support\Facades\Event;
Event::dispatch(new UserRegistered('user-123'));
Where to Look First:
Event-Driven Architecture (EDA):
OrderCreated triggers InventoryUpdate and NotificationSent).// Dispatch an event
Event::dispatch(new OrderCreated($orderId));
// Listen to it
class UpdateInventory {
public function handle(OrderCreated $event) {
Inventory::reduceStock($event->orderId);
}
}
Priority-Based Execution:
protected $listen = [
OrderCreated::class => [
[UpdateInventory::class, 10], // Low priority
[LogOrder::class, 20], // High priority
],
];
Stopping Event Propagation:
class ValidateOrder {
public function handle(OrderCreated $event) {
if (!$event->isValid()) {
$event->stopPropagation(); // Stops other listeners
}
}
}
Event Subscribers:
class OrderSubscriber implements ShouldRegister {
public static function getSubscribedEvents(): array
{
return [
OrderCreated::class => 'handleOrderCreated',
OrderCancelled::class => 'handleOrderCancelled',
];
}
public function handleOrderCreated(OrderCreated $event) { /* ... */ }
}
Dynamic Registration:
$dispatcher = app(EventDispatcher::class);
$dispatcher->addListener(OrderCreated::class, function ($event) {
// Dynamic logic
});
php artisan make:event UserRegistered
php artisan make:listener SendWelcomeEmail --event=UserRegistered
EventDispatcher to Laravel’s container in AppServiceProvider:
public function register(): void
{
$this->app->singleton(EventDispatcher::class, function () {
return new EventDispatcher();
});
}
Event Facade:
use Illuminate\Support\Facades\Event;
Event::dispatch(new UserRegistered('user-123'));
Event::listen(OrderCreated::class, UpdateInventory::class);
Event Filtering:
#[AsEventListener] (PHP 8+) for type-safe filtering:
#[AsEventListener(event: UserRegistered::class, method: 'handle')]
class SendWelcomeEmail { /* ... */ }
#[AsEventListener(event: UserRegistered::class, method: 'handle', priority: 20)]
class LogUserRegistration { /* ... */ }
Event Decorators:
$dispatcher->addSubscriber(new LoggingSubscriber());
Async Event Handling:
Event::dispatchSync(new UserRegistered('user-123')); // Sync
Event::dispatch(new UserRegistered('user-123')); // Async (default in Laravel)
Memory Leaks:
TraceableEventDispatcher.EventDispatcher (non-traceable) for CLI or reset the dispatcher periodically:
$dispatcher->removeListener(OrderCreated::class, $listener);
TraceableEventDispatcher in queues/CLI unless debugging.Listener Order Chaos:
protected $listen = [
OrderCreated::class => [
[UpdateInventory::class, 10], // Explicit priority
],
];
Stopping Events Prematurely:
stopPropagation() halts all listeners, including critical ones.stopPropagation() sparingly. Prefer conditional logic or separate events.Circular Dependencies:
TraceableEventDispatcher to debug or refactor into separate workflows.Performance Overhead:
TraceableEventDispatcher and remove unused listeners.Enable Event Tracing:
TraceableEventDispatcher to log all dispatched events:
$dispatcher = new TraceableEventDispatcher();
$dispatcher->addListener(OrderCreated::class, function () {
// Your logic
});
$dispatcher->getTraces();
Listener Not Firing:
EventServiceProvider or via #[AsEventListener].$dispatcher->hasListeners(OrderCreated::class); // Check if listeners exist
$dispatcher->getListenersFor(OrderCreated::class); // List all listeners
Priority Conflicts:
$listeners = $dispatcher->getListenersFor(OrderCreated::class);
usort($listeners, fn ($a, $b) => $b['priority'] <=> $a['priority']);
print_r($listeners); // Verify order
Laravel’s Event Dispatcher:
EventDispatcher in its own Dispatcher class. Use Laravel’s facade (Event) for consistency:
// Avoid direct Symfony calls in Laravel:
// ❌ $dispatcher = app(EventDispatcher::class);
// ✅ Event::dispatch(new UserRegistered());
Service Provider Binding:
$this->app->afterResolving(EventDispatcher::class, function ($dispatcher) {
$dispatcher->addSubscriber(new CustomSubscriber());
});
PHP 8 Attributes:
#[AsEventListener], ensure:
symfony/event-dispatcher version supports attributes (v6.0+).use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
Custom EventDispatcher:
EventDispatcher to add features (e.g., rate limiting):
class RateLimitedEventDispatcher extends EventDispatcher {
public function dispatch($event, $propagationStopped = null) {
if (!$this->isAllowed($event)) {
return;
}
parent::dispatch($event, $propagationStopped);
}
}
Event Normalization:
How can I help you explore Laravel packages today?