# Event System This guide covers the event-driven architecture of the framework. ## Overview The framework provides a sophisticated event system built on two core components: - **EventBus**: Simple, lightweight interface for basic event dispatching - **EventDispatcher**: Full-featured implementation with handler management, priorities, and propagation control **Key Features**: - Attribute-based event handler registration via `#[OnEvent]` - Priority-based handler execution - Event propagation control (stop propagation) - Support for inheritance and interface-based event matching - Automatic handler discovery via framework's attribute system - Domain events for business logic decoupling - Application lifecycle events for framework integration ## EventBus vs EventDispatcher ### EventBus Interface **Purpose**: Simple contract for event dispatching ```php interface EventBus { public function dispatch(object $event): void; } ``` **When to use**: - Simple event dispatching without return values - Fire-and-forget events - When you don't need handler results - Dependency injection when you want interface-based type hints **Example Usage**: ```php final readonly class OrderService { public function __construct( private EventBus $eventBus ) {} public function createOrder(CreateOrderCommand $command): Order { $order = Order::create($command); // Fire event without caring about results $this->eventBus->dispatch(new OrderCreatedEvent($order)); return $order; } } ``` ### EventDispatcher Implementation **Purpose**: Full-featured event system with handler management and results **Key Features**: - Returns array of handler results - Priority-based handler execution (highest priority first) - Stop propagation support - Manual handler registration via `addHandler()` or `listen()` - Automatic handler discovery via `#[OnEvent]` attribute - Inheritance and interface matching **When to use**: - Need handler return values for processing - Want priority control over execution order - Need to stop event propagation conditionally - Require manual handler registration - Building complex event workflows **Example Usage**: ```php final readonly class PaymentProcessor { public function __construct( private EventDispatcher $dispatcher ) {} public function processPayment(Payment $payment): PaymentResult { // Dispatch and collect results from all handlers $results = $this->dispatcher->dispatch( new PaymentProcessingEvent($payment) ); // Process handler results foreach ($results as $result) { if ($result instanceof PaymentValidationFailure) { throw new PaymentException($result->reason); } } return new PaymentResult($payment, $results); } } ``` ### Implementation Relationship ```php // EventDispatcher implements EventBus interface final class EventDispatcher implements EventBus { public function dispatch(object $event): array { // Full implementation with handler management } } // In DI Container $container->singleton(EventBus::class, EventDispatcher::class); $container->singleton(EventDispatcher::class, EventDispatcher::class); ``` **Recommendation**: Use `EventDispatcher` for type hints when you need full features, use `EventBus` interface when you want loose coupling and don't need results. ## Domain Events ### What are Domain Events? Domain Events represent significant state changes or occurrences in your business domain. They enable loose coupling between domain components. **Characteristics**: - Immutable (`readonly` classes) - Named in past tense (OrderCreated, UserRegistered, PaymentCompleted) - Contain only relevant domain data - No business logic (pure data objects) - Include timestamp for audit trail ### Creating Domain Events ```php namespace App\Domain\Order\Events; use App\Domain\Order\ValueObjects\OrderId; use App\Framework\Core\ValueObjects\Timestamp; final readonly class OrderCreatedEvent { public readonly Timestamp $occurredAt; public function __construct( public OrderId $orderId, public UserId $userId, public Money $total, ?Timestamp $occurredAt = null ) { $this->occurredAt = $occurredAt ?? Timestamp::now(); } } ``` **Best Practices**: - Use Value Objects for event properties - Include timestamp for audit trail - Keep events focused (single responsibility) - Version events for backward compatibility - Document event payload in PHPDoc ### Framework Lifecycle Events The framework provides built-in events for application lifecycle: #### ApplicationBooted **Triggered**: After application bootstrap completes ```php final readonly class ApplicationBooted { public function __construct( public \DateTimeImmutable $bootTime, public string $environment, public Version $version, public \DateTimeImmutable $occurredAt = new \DateTimeImmutable() ) {} } ``` **Use Cases**: - Initialize services after boot - Start background workers - Setup scheduled tasks - Warm caches #### BeforeHandleRequest **Triggered**: Before HTTP request processing ```php final readonly class BeforeHandleRequest { public function __construct( public Request $request, public Timestamp $timestamp, public array $context = [] ) {} } ``` **Use Cases**: - Request logging - Performance monitoring - Security checks - Request transformation #### AfterHandleRequest **Triggered**: After HTTP request processing ```php final readonly class AfterHandleRequest { public function __construct( public Request $request, public Response $response, public Duration $processingTime, public Timestamp $timestamp, public array $context = [] ) {} } ``` **Use Cases**: - Response logging - Performance metrics - Analytics collection - Cleanup operations ### Example: User Registration Event ```php namespace App\Domain\User\Events; final readonly class UserRegisteredEvent { public readonly Timestamp $occurredAt; public function __construct( public UserId $userId, public Email $email, public UserName $userName, ?Timestamp $occurredAt = null ) { $this->occurredAt = $occurredAt ?? Timestamp::now(); } } // Dispatching the event final readonly class UserService { public function __construct( private UserRepository $repository, private EventBus $eventBus ) {} public function register(RegisterUserCommand $command): User { $user = User::create( $command->email, $command->userName, $command->password ); $this->repository->save($user); // Dispatch domain event $this->eventBus->dispatch( new UserRegisteredEvent( $user->id, $user->email, $user->userName ) ); return $user; } } ``` ## Event Handler Registration ### Attribute-Based Registration **Primary Method**: Use `#[OnEvent]` attribute for automatic discovery ```php use App\Framework\Core\Events\OnEvent; final readonly class UserEventHandlers { public function __construct( private EmailService $emailService, private Logger $logger ) {} #[OnEvent(priority: 100)] public function sendWelcomeEmail(UserRegisteredEvent $event): void { $this->emailService->send( to: $event->email, template: 'welcome', data: ['userName' => $event->userName->value] ); } #[OnEvent(priority: 50)] public function logUserRegistration(UserRegisteredEvent $event): void { $this->logger->info('User registered', [ 'user_id' => $event->userId->toString(), 'email' => $event->email->value, 'occurred_at' => $event->occurredAt->format('Y-m-d H:i:s') ]); } } ``` **Attribute Parameters**: - `priority` (optional): Handler execution order (higher = earlier, default: 0) - `stopPropagation` (optional): Stop execution after this handler (default: false) ### Priority-Based Execution Handlers execute in **priority order** (highest to lowest): ```php #[OnEvent(priority: 200)] // Executes first public function criticalHandler(OrderCreatedEvent $event): void { } #[OnEvent(priority: 100)] // Executes second public function highPriorityHandler(OrderCreatedEvent $event): void { } #[OnEvent(priority: 50)] // Executes third public function normalHandler(OrderCreatedEvent $event): void { } #[OnEvent] // Executes last (default priority: 0) public function defaultHandler(OrderCreatedEvent $event): void { } ``` **Use Cases for Priorities**: - **Critical (200+)**: Security checks, validation, fraud detection - **High (100-199)**: Transaction logging, audit trail - **Normal (50-99)**: Business logic, notifications - **Low (0-49)**: Analytics, metrics, cleanup ### Stop Propagation **Purpose**: Prevent subsequent handlers from executing ```php #[OnEvent(priority: 100, stopPropagation: true)] public function validatePayment(PaymentProcessingEvent $event): PaymentValidationResult { $result = $this->validator->validate($event->payment); if (!$result->isValid()) { // Stop propagation - no further handlers execute return new PaymentValidationFailure($result->errors); } return new PaymentValidationSuccess(); } #[OnEvent(priority: 50)] public function processPayment(PaymentProcessingEvent $event): void { // This handler only executes if validation passes // (previous handler didn't set stopPropagation result) $this->gateway->charge($event->payment); } ``` **Important**: `stopPropagation` stops execution **after** the current handler completes. ### Manual Handler Registration **Alternative**: Register handlers programmatically ```php final readonly class EventInitializer { public function __construct( private EventDispatcher $dispatcher ) {} #[Initializer] public function registerHandlers(): void { // Using listen() method $this->dispatcher->listen( OrderCreatedEvent::class, function (OrderCreatedEvent $event) { // Handle event }, priority: 100 ); // Using addHandler() method $this->dispatcher->addHandler( eventClass: UserRegisteredEvent::class, handler: [$this->userService, 'onUserRegistered'], priority: 50 ); } } ``` **When to use manual registration**: - Dynamic handler registration based on configuration - Third-party library integration - Closure-based handlers for simple cases - Runtime handler modification ### Handler Discovery The framework automatically discovers handlers marked with `#[OnEvent]`: ```php // In Application Bootstrap final readonly class EventSystemInitializer { #[Initializer] public function initialize(EventDispatcher $dispatcher): void { // Automatic discovery finds all #[OnEvent] methods $discoveredHandlers = $this->attributeScanner->findMethodsWithAttribute( OnEvent::class ); foreach ($discoveredHandlers as $handler) { $dispatcher->addHandler( eventClass: $handler->getEventClass(), handler: [$handler->instance, $handler->method], priority: $handler->attribute->priority ?? 0 ); } } } ``` **No Manual Setup Required**: Framework handles discovery automatically during initialization. ## Event Middleware **Concept**: Middleware pattern for event processing (transform, filter, log, etc.) ### Custom Event Middleware ```php interface EventMiddleware { public function process(object $event, callable $next): mixed; } final readonly class LoggingEventMiddleware implements EventMiddleware { public function __construct( private Logger $logger ) {} public function process(object $event, callable $next): mixed { $eventClass = get_class($event); $startTime = microtime(true); $this->logger->debug("Event dispatched: {$eventClass}"); try { $result = $next($event); $duration = (microtime(true) - $startTime) * 1000; $this->logger->debug("Event processed: {$eventClass}", [ 'duration_ms' => $duration ]); return $result; } catch (\Throwable $e) { $this->logger->error("Event failed: {$eventClass}", [ 'error' => $e->getMessage() ]); throw $e; } } } ``` ### Middleware Pipeline ```php final class EventDispatcherWithMiddleware { /** @var EventMiddleware[] */ private array $middleware = []; public function addMiddleware(EventMiddleware $middleware): void { $this->middleware[] = $middleware; } public function dispatch(object $event): array { $pipeline = array_reduce( array_reverse($this->middleware), fn($next, $middleware) => fn($event) => $middleware->process($event, $next), fn($event) => $this->eventDispatcher->dispatch($event) ); return $pipeline($event); } } ``` ### Validation Middleware ```php final readonly class ValidationEventMiddleware implements EventMiddleware { public function __construct( private ValidatorInterface $validator ) {} public function process(object $event, callable $next): mixed { // Validate event before dispatching $errors = $this->validator->validate($event); if (!empty($errors)) { throw new InvalidEventException( "Event validation failed: " . implode(', ', $errors) ); } return $next($event); } } ``` ## Async Event Processing ### Queue-Based Async Handling ```php use App\Framework\Queue\Queue; use App\Framework\Queue\ValueObjects\JobPayload; final readonly class AsyncEventHandler { public function __construct( private Queue $queue ) {} #[OnEvent(priority: 50)] public function processAsync(OrderCreatedEvent $event): void { // Dispatch to queue for async processing $job = new ProcessOrderEmailJob( orderId: $event->orderId, userEmail: $event->userEmail ); $this->queue->push( JobPayload::immediate($job) ); } } // Background Job final readonly class ProcessOrderEmailJob { public function __construct( public OrderId $orderId, public Email $userEmail ) {} public function handle(EmailService $emailService): void { // Process email asynchronously $emailService->sendOrderConfirmation( $this->orderId, $this->userEmail ); } } ``` ### Event Buffering Pattern ```php final class EventBuffer { private array $bufferedEvents = []; #[OnEvent] public function bufferEvent(DomainEvent $event): void { $this->bufferedEvents[] = $event; } public function flush(EventDispatcher $dispatcher): void { foreach ($this->bufferedEvents as $event) { $dispatcher->dispatch($event); } $this->bufferedEvents = []; } } // Usage in transaction try { $this->entityManager->beginTransaction(); // Buffer events during transaction $this->eventBuffer->bufferEvent(new OrderCreatedEvent(/* ... */)); $this->eventBuffer->bufferEvent(new InventoryReservedEvent(/* ... */)); $this->entityManager->commit(); // Flush events after successful commit $this->eventBuffer->flush($this->dispatcher); } catch (\Exception $e) { $this->entityManager->rollback(); // Events are not flushed on rollback throw $e; } ``` ## Event Best Practices ### 1. Event Naming **✅ Good**: Past tense, descriptive ```php OrderCreatedEvent UserRegisteredEvent PaymentCompletedEvent InventoryReservedEvent ``` **❌ Bad**: Present/future tense, vague ```php CreateOrderEvent RegisterUserEvent CompletePayment ReserveInventory ``` ### 2. Event Granularity **✅ Focused Events**: ```php final readonly class OrderCreatedEvent { /* ... */ } final readonly class OrderShippedEvent { /* ... */ } final readonly class OrderCancelledEvent { /* ... */ } ``` **❌ God Events**: ```php final readonly class OrderEvent { public function __construct( public string $action, // 'created', 'shipped', 'cancelled' public Order $order ) {} } ``` ### 3. Immutability **✅ Readonly Events**: ```php final readonly class UserUpdatedEvent { public function __construct( public UserId $userId, public Email $newEmail ) {} } ``` **❌ Mutable Events**: ```php final class UserUpdatedEvent { public UserId $userId; public Email $newEmail; public function setEmail(Email $email): void { $this->newEmail = $email; // Bad: Events should be immutable } } ``` ### 4. Handler Independence **✅ Independent Handlers**: ```php #[OnEvent] public function sendEmail(OrderCreatedEvent $event): void { // Self-contained - doesn't depend on other handlers $this->emailService->send(/* ... */); } #[OnEvent] public function updateInventory(OrderCreatedEvent $event): void { // Independent - no shared state with email handler $this->inventoryService->reserve(/* ... */); } ``` **❌ Coupled Handlers**: ```php private bool $emailSent = false; #[OnEvent(priority: 100)] public function sendEmail(OrderCreatedEvent $event): void { $this->emailService->send(/* ... */); $this->emailSent = true; // Bad: Shared state } #[OnEvent(priority: 50)] public function logEmailSent(OrderCreatedEvent $event): void { if ($this->emailSent) { // Bad: Depends on other handler $this->logger->info('Email sent'); } } ``` ### 5. Error Handling **✅ Graceful Degradation**: ```php #[OnEvent] public function sendNotification(OrderCreatedEvent $event): void { try { $this->notificationService->send(/* ... */); } catch (NotificationException $e) { // Log error but don't fail the entire event dispatch $this->logger->error('Notification failed', [ 'order_id' => $event->orderId->toString(), 'error' => $e->getMessage() ]); } } ``` ### 6. Avoid Circular Events **❌ Circular Dependency**: ```php #[OnEvent] public function onOrderCreated(OrderCreatedEvent $event): void { // Bad: Dispatching event from event handler can cause loops $this->eventBus->dispatch(new OrderProcessedEvent($event->orderId)); } #[OnEvent] public function onOrderProcessed(OrderProcessedEvent $event): void { // Bad: Creates circular dependency $this->eventBus->dispatch(new OrderCreatedEvent(/* ... */)); } ``` **✅ Use Command/Service Layer**: ```php #[OnEvent] public function onOrderCreated(OrderCreatedEvent $event): void { // Good: Use service to perform additional work $this->orderProcessingService->processOrder($event->orderId); } ``` ### 7. Performance Considerations **Heavy Operations → Queue**: ```php #[OnEvent] public function generateInvoicePdf(OrderCreatedEvent $event): void { // Heavy operation - push to queue $this->queue->push( JobPayload::immediate( new GenerateInvoiceJob($event->orderId) ) ); } ``` **Light Operations → Synchronous**: ```php #[OnEvent] public function logOrderCreation(OrderCreatedEvent $event): void { // Light operation - execute immediately $this->logger->info('Order created', [ 'order_id' => $event->orderId->toString() ]); } ``` ## Common Patterns ### Event Sourcing Pattern ```php final class OrderEventStore { #[OnEvent] public function appendEvent(DomainEvent $event): void { $this->eventStore->append([ 'event_type' => get_class($event), 'event_data' => json_encode($event), 'occurred_at' => $event->occurredAt->format('Y-m-d H:i:s'), 'aggregate_id' => $event->getAggregateId() ]); } } ``` ### Saga Pattern ```php final readonly class OrderSaga { #[OnEvent(priority: 100)] public function onOrderCreated(OrderCreatedEvent $event): void { // Step 1: Reserve inventory $this->inventoryService->reserve($event->items); } #[OnEvent(priority: 90)] public function onInventoryReserved(InventoryReservedEvent $event): void { // Step 2: Process payment $this->paymentService->charge($event->orderId); } #[OnEvent(priority: 80)] public function onPaymentCompleted(PaymentCompletedEvent $event): void { // Step 3: Ship order $this->shippingService->ship($event->orderId); } } ``` ### CQRS Pattern ```php // Command Side - Dispatches Events final readonly class CreateOrderHandler { public function handle(CreateOrderCommand $command): Order { $order = Order::create($command); $this->repository->save($order); $this->eventBus->dispatch( new OrderCreatedEvent($order) ); return $order; } } // Query Side - Maintains Read Model #[OnEvent] public function updateOrderReadModel(OrderCreatedEvent $event): void { $this->orderReadModel->insert([ 'order_id' => $event->orderId->toString(), 'user_id' => $event->userId->toString(), 'total' => $event->total->toDecimal(), 'status' => 'created' ]); } ``` ## Framework Integration ### With Queue System ```php #[OnEvent] public function queueHeavyTask(OrderCreatedEvent $event): void { $this->queue->push( JobPayload::immediate( new ProcessOrderAnalyticsJob($event->orderId) ) ); } ``` ### With Scheduler ```php #[OnEvent] public function scheduleReminder(OrderCreatedEvent $event): void { $this->scheduler->schedule( 'send-order-reminder-' . $event->orderId, OneTimeSchedule::at(Timestamp::now()->addDays(3)), fn() => $this->emailService->sendReminder($event->orderId) ); } ``` ### With Cache System ```php #[OnEvent] public function invalidateCache(OrderCreatedEvent $event): void { $this->cache->forget( CacheTag::fromString("user_{$event->userId}") ); } ``` ## Summary The Event System provides: - ✅ **Decoupled Architecture**: Loose coupling via events - ✅ **Flexible Handling**: Priority-based, propagation control - ✅ **Automatic Discovery**: `#[OnEvent]` attribute registration - ✅ **Multiple Patterns**: Domain events, application events, async processing - ✅ **Framework Integration**: Works with Queue, Scheduler, Cache - ✅ **Testing Support**: Easy to unit test and integration test - ✅ **Performance**: Efficient handler execution with priority optimization **When to Use Events**: - Decoupling domain logic from side effects (email, notifications) - Cross-module communication without tight coupling - Audit trail and event sourcing - Async processing of non-critical operations - CQRS and saga patterns **When NOT to Use Events**: - Synchronous business logic requiring immediate results - Simple function calls (don't over-engineer) - Performance-critical paths (events have overhead) - Operations requiring transactional guarantees across handlers