Skip to content

Event Listeners

Event Listeners react to domain events published by command handlers. They enable loose coupling between bounded contexts and handle side effects.

Listener Structure

Listeners implement ListenerInterface and use the __invoke pattern:

final readonly class UserAuthenticatedEventHandler implements ListenerInterface
{
    public function __invoke(DomainEventInterface $event): void
    {
        $this->sessionService->setUserAuthenticated(
            $event->getUser()->getId(),
            $event->getUser()->getEmail()
        );
    }
}

Event Registration

Listeners are registered in config/events.php:

return [
    LoanCreatedEvent::class => [
        LoanCreatedEventHandler::class,
        NotifyLibrarianHandler::class,  // Multiple listeners allowed
    ],
    LoanReturnedEvent::class => [
        LoanReturnedEventHandler::class,
    ],
];

Cross-Aggregate Communication

Listeners enable communication between bounded contexts:

final readonly class LoanCreatedEventHandler implements ListenerInterface
{
    public function __invoke(DomainEventInterface $event): void
    {
        $command = new UpdateBookStatusCommand(
            $event->getLoan()->getBookId(),
            BookStatus::Borrowed
        );
        $this->commandBus->dispatch($command);
    }
}

Naming Convention

The standard pattern is {Event}Handler: one handler per event, following the Single Responsibility Principle. Example: LoanCreatedEventHandler, UserAuthenticatedEventHandler.

A grouped variant {Entity}EventHandler may be used when multiple events share identical logic (e.g., BookEventHandler for logging).

Best Practices

Do:

  • React with side effects (logging, notifications)
  • Keep listeners focused on single responsibilities
  • Handle failures gracefully without breaking the flow

Don't:

  • Contain complex business logic
  • Create circular event chains
  • Perform long-running operations synchronously

See Also