fix: Gitea Traefik routing and connection pool optimization
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
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
This commit is contained in:
963
docs/features/events/system.md
Normal file
963
docs/features/events/system.md
Normal file
@@ -0,0 +1,963 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user