ashleydawson/domain-event-dispatcher-bundle
Installation:
composer require ashleydawson/domain-event-dispatcher-bundle
Register the bundle in config/bundles.php (Symfony 4+) or AppKernel.php (Symfony 2/3):
AshleyDawson\DomainEventDispatcherBundle\AshleyDawsonDomainEventDispatcherBundle::class => ['all' => true],
First Event:
Define a simple event class (e.g., src/DomainEvent/UserRegistered.php):
namespace App\DomainEvent;
class UserRegistered {
public function __construct(private int $userId) {}
public function getUserId(): int { return $this->userId; }
}
First Listener:
Create a listener (e.g., src/Listener/UserRegisteredListener.php):
namespace App\Listener;
use App\DomainEvent\UserRegistered;
class UserRegisteredListener {
public function __invoke(UserRegistered $event) {
// Handle event (e.g., log, send email)
}
}
Register Listener:
Tag the listener in config/services.yaml:
services:
App\Listener\UserRegisteredListener:
tags:
- { name: ashley_dawson.domain_event_listener }
Dispatch an Event:
Trigger the event from a service/entity (e.g., src/Service/UserService.php):
use AshleyDawson\DomainEventDispatcher\DomainEventDispatcher;
class UserService {
public function registerUser(): void {
$userId = 123;
DomainEventDispatcher::getInstance()->dispatch(new UserRegistered($userId));
}
}
Use this bundle to decouple side effects (e.g., notifications, analytics) from core business logic. For example:
OrderCreated when saving an Order entity.Order class.Separation of Concerns:
UserActivated, PaymentProcessed).// Entity dispatches event
$user->activate(); // Internally dispatches UserActivated event
// Listener handles side effects
public function __invoke(UserActivated $event) {
$this->sendWelcomeEmail($event->getUserId());
}
Deferred Events:
config/packages/ashley_dawson_domain_event_dispatcher.yaml:
ashley_dawson_domain_event_dispatcher:
dispatch_deferred_events_from_kernel_event: kernel.terminate
Dependency Injection:
DomainEventDispatcher via constructor (Symfony 3.3+):
use AshleyDawson\DomainEventDispatcher\DomainEventDispatcher;
class OrderService {
public function __construct(private DomainEventDispatcher $dispatcher) {}
}
DomainEventDispatcher::getInstance().Command Bus Integration:
public function handle(CreateUserCommand $command) {
$user = $this->userRepository->save($command->toUser());
$this->dispatcher->dispatch(new UserCreated($user->getId()));
}
Messenger Component:
public function __invoke(UserRegistered $event) {
$this->messageBus->dispatch(new SendWelcomeEmailMessage($event->getUserId()));
}
Mocking the Dispatcher:
DomainEventDispatcherInterface (if available) or mock the singleton:
$dispatcher = $this->createMock(DomainEventDispatcher::class);
$dispatcher->method('dispatch')->willReturnCallback(function($event) {
$this->lastDispatchedEvent = $event;
});
$listener = new UserRegisteredListener();
$event = new UserRegistered(1);
$listener($event);
$this->assertTrue($this->emailSent);
Event Recording:
$client = static::createClient();
$client->request('GET', '/register');
$this->assertCount(1, $client->getProfile()->getCollector('ashley_dawson_domain_event_dispatcher')->getEvents());
// In a cron job or command
$dispatcher = DomainEventDispatcher::getInstance();
$dispatcher->dispatch(new BatchUserActivityEvents($userActivityEvents));
Singleton Overuse:
DomainEventDispatcher::getInstance() tightly couples code to the singleton.$dispatcher = $this->container->get('ashley_dawson.domain_event_dispatcher');
Circular Dependencies:
EventDispatcherInterface wrapper to mock/disable dispatching during tests:
$dispatcher = $this->createMock(DomainEventDispatcher::class);
$dispatcher->method('dispatch')->willReturnCallback(function($event) {
if ($event instanceof UserRegistered) {
throw new \LogicException("Infinite loop detected!");
}
});
Deferred Events in CLI:
kernel.terminate events don’t fire in Symfony CLI commands.console.terminate:
ashley_dawson_domain_event_dispatcher:
dispatch_deferred_events_from_kernel_event: console.terminate
Event Ordering:
tags:
- { name: ashley_dawson.domain_event_listener, priority: 100 }
Profiler:
DomainEventDispatcher::getInstance()->clearDeferredEvents();
Logging:
public function __invoke(MyDomainEvent $event) {
$this->logger->debug('Event dispatched', ['event' => get_class($event), 'data' => $event->getData()]);
}
Event Validation:
public function __invoke(UserRegistered $event) {
if ($event->getUserId() <= 0) {
throw new \InvalidArgumentException("Invalid user ID");
}
}
Custom Event Dispatcher:
class CustomDomainEventDispatcher extends DomainEventDispatcher {
public function dispatch($event) {
$this->logger->info(sprintf('Dispatching %s', get_class($event)));
parent::dispatch($event);
}
}
Dynamic Listeners:
ListenerLocator:
$dispatcher = DomainEventDispatcher::getInstance();
$dispatcher->addListener('UserRegistered', $dynamicListener);
Event Metadata:
class UserRegistered {
public function __construct(private int $userId, private array
How can I help you explore Laravel packages today?