Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
964 lines
23 KiB
Markdown
964 lines
23 KiB
Markdown
# 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
|