doctrine/event-manager
Doctrine Event Manager is a lightweight PHP library providing a simple event system for registering listeners and dispatching events. Commonly used across Doctrine components to decouple services and react to lifecycle or application events cleanly.
Install the package via Composer (PHP 8.1+ required):
composer require doctrine/event-manager
Begin with the minimal setup using the EventManager class and EventManagerInterface:
use Doctrine\Common\EventManager;
$evm = new EventManager();
// Register a listener for a named event
$evm->addEventListener('user.registered', function ($event) {
echo "New user registered: {$event['email']}\n";
});
// Dispatch the event with a payload (array or object)
$evm->dispatchEvent('user.registered', ['email' => 'alice@example.com']);
First real-world use case: Add event hooks in domain services — e.g., dispatch OrderShipped events after persisting an order to trigger inventory updates or shipping notifications.
Event Subscribers for Grouped Logic
Use EventSubscriberInterface to bundle multiple event handlers in one class, ideal for cohesive concerns like audit logging:
use Doctrine\Common\EventSubscriber;
class AuditLogger implements EventSubscriber
{
public function getSubscribedEvents(): array
{
return [
'user.created' => 'logUserCreation',
'user.updated' => 'logUserUpdate',
];
}
public function logUserCreation(object $event): void { /* ... */ }
public function logUserUpdate(object $event): void { /* ... */ }
}
$evm->addEventSubscriber(new AuditLogger());
Typed Events with PHP 8+
Define dedicated event classes for type safety, clarity, and IDE support:
final class ProductDiscontinued
{
public function __construct(public int $productId, public string $reason) {}
}
$evm->addEventListener(ProductDiscontinued::class, function (ProductDiscontinued $e) {
NotificationService::alertStockTeam($e->productId, $e->reason);
});
$evm->dispatchEvent(ProductDiscontinued::class, new ProductDiscontinued(123, 'End of life'));
Dependency Injection & Testing
Type-hint EventManagerInterface in services. In tests, inject a mock to assert event dispatching without side effects:
public function testDispatchesEventOnSuccess(EventManagerInterface $evm): void
{
$evm->expects($this->once())
->method('dispatchEvent')
->with(OrderPaid::class);
(new OrderService($evm))->markPaid($order);
}
Lazy Subscriber Loading (Container-Aware)
Register subscribers conditionally at runtime (e.g., in a service provider):
if ($config['audit.enabled']) {
$evm->addEventSubscriber(new AuditLogger());
}
⚠️ getListeners() Now Requires $event (v2.0+)
Breaking change: getListeners() must receive a second argument (e.g., getListeners('user.created')). The deprecated getAllListeners() was removed. Use constants for event names to avoid typos and enable refactoring:
final class SystemEvents
{
public const INIT_COMPLETE = 'system.init_complete';
}
$evm->addEventListener(SystemEvents::INIT_COMPLETE, [...]);
⚠️ Event Name Format Matters
String-based event names ('user.created') risk typos and lack IDE safety. Prefer fully-qualified class names (UserCreated::class) when possible — they support auto-imports, rename-safe refactoring, and strict type-checking.
⚠️ Subscriber Return Types Are Limited
EventSubscriberInterface::getSubscribedEvents() returns array<string, string|array>, where arrays are [method, priority]. Not [method => callable]. Don’t try to register anonymous functions here — use addEventListener() instead.
💡 Debug Listener Order & Count
Call $evm->getListeners($eventName) before dispatch to verify registration and order — critical for debugging priority-dependent behavior.
💡 Custom EventManager Decorator for Observability
Wrap the core EventManager to inject timing, context logging, or early-exit hooks:
class TracingEventManager implements EventManagerInterface
{
private EventManagerInterface $inner;
public function dispatchEvent(string $eventName, object $event = null): void
{
$start = hrtime(true);
try {
$this->inner->dispatchEvent($eventName, $event);
} finally {
$duration = (hrtime(true) - $start) / 1_000_000;
logger()->debug("Event {$eventName} took {$duration}ms");
}
}
// Delegate all other methods...
}
How can I help you explore Laravel packages today?