feat: CI/CD pipeline setup complete - Ansible playbooks updated, secrets configured, workflow ready

This commit is contained in:
2025-10-31 01:39:24 +01:00
parent 55c04e4fd0
commit e26eb2aa12
601 changed files with 44184 additions and 32477 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -464,23 +464,937 @@ $schema->table('users', function (Blueprint $table) {
## EntityManager Usage
TODO: Document EntityManager and UnitOfWork pattern
### Overview
Der EntityManager ist das zentrale Interface für alle Datenbank-Operationen. Er kombiniert mehrere Patterns:
- **Unit of Work**: Change Tracking und Transaction Management
- **Identity Map**: Vermeidung von Duplikaten
- **Lazy Loading**: Performance-Optimierung durch verzögertes Laden
- **Batch Loading**: N+1 Query Prevention
### Core Features
**Service Classes** (interne Organisation):
- `EntityFinder`: Entity-Suche und Lazy Loading
- `EntityPersister`: Insert, Update, Delete Operationen
- `EntityQueryManager`: QueryBuilder Integration
- `EntityUtilities`: Hilfsmethoden und Profiling
### Basic Operations
#### Finding Entities
```php
use App\Framework\Database\EntityManager;
final readonly class UserService
{
public function __construct(
private EntityManager $entityManager
) {}
public function getUserById(UserId $id): ?User
{
// Lazy Loading (Standard)
return $this->entityManager->find(User::class, $id->value);
}
public function getUserByIdEager(UserId $id): ?User
{
// Eager Loading - lädt Relations sofort
return $this->entityManager->findEager(User::class, $id->value);
}
public function getAllUsers(): array
{
return $this->entityManager->findAll(User::class);
}
public function findUserByEmail(Email $email): ?User
{
return $this->entityManager->findOneBy(
User::class,
['email' => $email->value]
);
}
}
```
#### Saving Entities
```php
public function createUser(CreateUserCommand $command): User
{
$user = User::create(
id: $this->entityManager->generateId(),
email: $command->email,
name: $command->name
);
// save() erkennt automatisch INSERT vs UPDATE
return $this->entityManager->save($user);
}
public function updateUser(User $user, UpdateUserCommand $command): User
{
// Explizites UPDATE
$user->updateProfile($command->name);
return $this->entityManager->update($user);
}
```
#### Batch Operations
```php
public function createMultipleUsers(array $commands): array
{
$users = [];
foreach ($commands as $command) {
$users[] = User::create(
id: $this->entityManager->generateId(),
email: $command->email,
name: $command->name
);
}
// Batch-Insert für bessere Performance
return $this->entityManager->saveAll(...$users);
}
```
#### Deleting Entities
```php
public function deleteUser(User $user): void
{
$this->entityManager->delete($user);
}
```
### N+1 Query Prevention
**Problem**: Lazy Loading führt zu N+1 Queries
```php
// ❌ N+1 Problem
$users = $this->entityManager->findAll(User::class);
foreach ($users as $user) {
// Jede Iteration führt eine separate Query aus (N+1)
echo $user->getProfile()->bio;
}
```
**Solution**: Batch Loading mit Relations
```php
// ✅ Single Query mit Batch Loading
$users = $this->entityManager->findWithRelations(
User::class,
criteria: [],
relations: ['profile', 'posts'] // Lädt Relations in Batches
);
foreach ($users as $user) {
// Kein zusätzlicher Query - Profile bereits geladen
echo $user->getProfile()->bio;
}
```
### Lazy Loading vs Eager Loading
**Lazy Loading** (Standard):
- Relations werden bei Zugriff geladen
- Performance-Vorteil bei ungenutzten Relations
- Potentielles N+1 Problem
```php
$user = $this->entityManager->find(User::class, $id);
// Profile wird erst bei Zugriff geladen:
$bio = $user->getProfile()->bio; // Separate Query
```
**Eager Loading**:
- Relations werden sofort geladen
- Kein N+1 Problem
- Overhead bei ungenutzten Relations
```php
$user = $this->entityManager->findEager(User::class, $id);
// Profile ist bereits geladen - kein zusätzlicher Query
$bio = $user->getProfile()->bio;
```
### Identity Map
Der EntityManager nutzt eine Identity Map zur Vermeidung von Duplikaten:
```php
// Beide Aufrufe liefern die GLEICHE Instanz
$user1 = $this->entityManager->find(User::class, '123');
$user2 = $this->entityManager->find(User::class, '123');
var_dump($user1 === $user2); // true
```
**Vorteile**:
- Keine Duplikate im Speicher
- Konsistente Object Identity
- Automatisches Change Tracking
**Utility Methods**:
```php
// Detach Entity (aus Identity Map entfernen)
$this->entityManager->detach($user);
// Clear Identity Map (z.B. für Batch Processing)
$this->entityManager->clear();
// Identity Map Statistics
$stats = $this->entityManager->getIdentityMapStats();
// Returns: ['total_entities' => 150, 'entities_by_class' => [...]]
```
### Reference Loading
**getReference()** - Lade Entity-Referenz ohne Existenz-Check:
```php
// Erstellt Proxy ohne Database Query
$user = $this->entityManager->getReference(User::class, $userId);
// Query wird erst bei Zugriff ausgeführt
$name = $user->getName(); // Jetzt wird geladen
```
**Use Cases**:
- Foreign Key Relationships
- Performance-kritische Pfade
- Wenn Existenz bereits bekannt ist
### Profiling und Debugging
```php
// Profiling aktivieren
$this->entityManager->setProfilingEnabled(true);
// Operationen ausführen
$users = $this->entityManager->findAll(User::class);
// Profiling-Statistiken abrufen
$stats = $this->entityManager->getProfilingStatistics();
// Profiling-Summary
$summary = $this->entityManager->getProfilingSummary();
// Returns: ProfileSummary mit total_queries, total_time, etc.
// Profiling-Daten löschen
$this->entityManager->clearProfilingData();
```
### Criteria API (Type-Safe Queries)
**Modern**: Verwendung von Criteria statt Arrays
```php
use App\Framework\Database\Criteria\DetachedCriteria;
// Type-safe Criteria
$criteria = DetachedCriteria::forEntity(User::class)
->where('email', '=', $email->value)
->andWhere('active', '=', true)
->orderBy('created_at', 'DESC')
->limit(10);
$users = $this->entityManager->findByCriteria($criteria);
$user = $this->entityManager->findOneByCriteria($criteria);
$count = $this->entityManager->countByCriteria($criteria);
```
### QueryBuilder Integration
Für komplexe Queries:
```php
// QueryBuilder für Entity-Klasse
$queryBuilder = $this->entityManager->createQueryBuilderFor(User::class);
$users = $queryBuilder
->select('*')
->where('email LIKE ?', ['%@example.com'])
->andWhere('created_at > ?', ['2024-01-01'])
->orderBy('name', 'ASC')
->limit(50)
->fetchAll();
// QueryBuilder für Table (ohne Entity)
$qb = $this->entityManager->createQueryBuilderForTable('users');
```
### Domain Events Integration
```php
// Domain Event aufzeichnen
$this->entityManager->recordDomainEvent($user, new UserRegisteredEvent($user));
// Events für Entity dispatchen
$this->entityManager->dispatchDomainEventsForEntity($user);
// Alle Events dispatchen
$this->entityManager->dispatchAllDomainEvents();
// Event-Statistiken
$stats = $this->entityManager->getDomainEventStats();
// Returns: ['total_events' => 23, 'events_by_entity' => [...]]
```
## Repository Pattern
TODO: Document repository implementation and usage
### Overview
Das Repository Pattern abstrahiert Daten-Zugriff und bietet domain-spezifische Query-Methoden. Das Framework nutzt **Composition over Inheritance**.
### Base EntityRepository
**Framework-Service** für Common Operations:
```php
namespace App\Framework\Database\Repository;
final readonly class EntityRepository
{
public function __construct(
private EntityManager $entityManager
) {}
public function find(string $entityClass, string $id): ?object
public function findAll(string $entityClass): array
public function findBy(string $entityClass, array $criteria, ...): array
public function findOneBy(string $entityClass, array $criteria): ?object
public function save(object $entity): object
public function delete(object $entity): void
public function transaction(callable $callback): mixed
// Batch Loading (N+1 Prevention)
public function findWithRelations(...): array
// Pagination
public function findPaginated(...): PaginatedResult
// Batch Operations
public function saveBatch(array $entities, int $batchSize = 100): array
public function deleteBatch(array $entities, int $batchSize = 100): void
}
```
### Domain Repository Pattern
**Composition-basiert** statt Inheritance:
```php
namespace App\Domain\User\Repositories;
use App\Framework\Database\Repository\EntityRepository;
final readonly class UserRepository
{
public function __construct(
private EntityRepository $entityRepository
) {}
public function findById(UserId $id): ?User
{
return $this->entityRepository->find(User::class, $id->value);
}
public function findByEmail(Email $email): ?User
{
return $this->entityRepository->findOneBy(
User::class,
['email' => $email->value]
);
}
public function findActiveUsers(): array
{
return $this->entityRepository->findBy(
User::class,
criteria: ['active' => true],
orderBy: ['created_at' => 'DESC']
);
}
public function save(User $user): User
{
return $this->entityRepository->save($user);
}
public function delete(User $user): void
{
$this->entityRepository->delete($user);
}
// Domain-spezifische Methode mit Batch Loading
public function findUsersWithProfiles(array $userIds): array
{
return $this->entityRepository->findWithRelations(
User::class,
criteria: ['id' => $userIds],
relations: ['profile', 'settings']
);
}
// Pagination
public function findPaginated(int $page, int $limit = 20): PaginatedResult
{
return $this->entityRepository->findPaginated(
User::class,
page: $page,
limit: $limit,
orderBy: ['created_at' => 'DESC']
);
}
}
```
### Repository Best Practices
**✅ Composition Over Inheritance**:
```php
// ✅ Framework Pattern - Composition
final readonly class OrderRepository
{
public function __construct(
private EntityRepository $entityRepository
) {}
}
// ❌ Avoid - Inheritance
class OrderRepository extends BaseRepository
{
// Problematisch: Tight coupling
}
```
**✅ Domain-Specific Methods**:
```php
// ✅ Sprechende Domain-Methoden
public function findOverdueOrders(): array
{
return $this->entityRepository->findBy(
Order::class,
['status' => 'pending'],
orderBy: ['due_date' => 'ASC']
);
}
// ❌ Generic Methods außerhalb Domain
public function findBy(array $criteria): array
{
// Zu generisch - nutze EntityRepository direkt
}
```
**✅ N+1 Prevention**:
```php
// ✅ Batch Loading für Relations
public function findOrdersWithItems(array $orderIds): array
{
return $this->entityRepository->findWithRelations(
Order::class,
criteria: ['id' => $orderIds],
relations: ['items', 'customer'] // Lädt in Batches
);
}
```
## Unit of Work Pattern
### Overview
Der Unit of Work Pattern verwaltet Entities und deren Änderungen für transaktionale Konsistenz.
**Key Features**:
- **Change Tracking**: Automatische Änderungserkennung
- **Transactional Consistency**: Atomic Commits/Rollbacks
- **Batch Operations**: Optimierte Bulk-Operationen
- **Entity States**: NEW, MANAGED, DIRTY, DELETED, DETACHED
### Entity Lifecycle
```php
use App\Framework\Database\UnitOfWork\UnitOfWork;
final readonly class OrderService
{
public function __construct(
private EntityManager $entityManager
) {}
public function createOrder(CreateOrderCommand $command): Order
{
$unitOfWork = $this->entityManager->unitOfWork;
// 1. Create new entity (state: NEW)
$order = Order::create($command);
// 2. Persist marks entity for INSERT (state: NEW → MANAGED)
$unitOfWork->persist($order);
// 3. Flush writes to database (state: MANAGED)
$unitOfWork->flush();
// 4. Commit transaction
$unitOfWork->commit();
return $order;
}
}
```
### Change Tracking
**Automatic Change Detection**:
```php
$unitOfWork = $this->entityManager->unitOfWork;
// Load entity (state: MANAGED)
$user = $this->entityManager->find(User::class, $userId);
// Modify entity
$user->updateProfile($newName);
// Change Tracking erkennt Änderung automatisch
$unitOfWork->flush(); // UPDATE query wird generiert
$unitOfWork->commit();
```
**Manual Change Tracking**:
```php
// Entity aus externer Quelle (state: DETACHED)
$user = unserialize($serializedUser);
// Merge in Unit of Work (state: DETACHED → MANAGED)
$managedUser = $unitOfWork->merge($user);
// Änderungen werden getrackt
$unitOfWork->flush();
```
### Transaction Management
**Explicit Transactions**:
```php
public function processOrder(Order $order): void
{
$unitOfWork = $this->entityManager->unitOfWork;
try {
$unitOfWork->beginTransaction();
// Multiple operations
$order->confirm();
$unitOfWork->persist($order);
$this->inventoryService->reserve($order->items);
$this->paymentService->charge($order->total);
// Write changes to database
$unitOfWork->flush();
// Commit transaction
$unitOfWork->commit();
} catch (\Exception $e) {
$unitOfWork->rollback();
throw $e;
}
}
```
**Implicit Transactions (Auto-Commit)**:
```php
// Auto-commit ist standardmäßig aktiviert
$unitOfWork->persist($entity); // Automatisch committed
```
**Disable Auto-Commit** für Batch Operations:
```php
$unitOfWork->setAutoCommit(false);
try {
$unitOfWork->beginTransaction();
foreach ($orders as $order) {
$unitOfWork->persist($order); // Kein Auto-Commit
}
$unitOfWork->flush(); // Batch-Write
$unitOfWork->commit(); // Atomic Commit
} catch (\Exception $e) {
$unitOfWork->rollback();
throw $e;
} finally {
$unitOfWork->setAutoCommit(true);
}
```
### Entity States
```php
use App\Framework\Database\UnitOfWork\EntityState;
// Check Entity State
$state = $unitOfWork->getEntityState($entity);
match ($state) {
EntityState::NEW => 'Entity marked for INSERT',
EntityState::MANAGED => 'Entity being tracked, no changes',
EntityState::DIRTY => 'Entity modified, pending UPDATE',
EntityState::DELETED => 'Entity marked for DELETE',
EntityState::DETACHED => 'Entity not managed by UnitOfWork',
};
// Check if entity is managed
if ($unitOfWork->contains($entity)) {
// Entity is managed (NEW, MANAGED, DIRTY, or DELETED)
}
```
### Detaching Entities
```php
// Detach entity from Unit of Work
$unitOfWork->detach($user);
// Entity state: DETACHED
// Changes to entity are no longer tracked
```
**Use Cases**:
- Long-running processes
- Serialization/Caching
- Read-only operations
- Memory management in batch processing
### Bulk Operations
```php
// Bulk Insert
$unitOfWork->bulkInsert($entities); // Optimized batch INSERT
// Bulk Update
$unitOfWork->bulkUpdate($entities); // Optimized batch UPDATE
// Bulk Delete
$unitOfWork->bulkDelete($entities); // Optimized batch DELETE
```
## Query Optimization
TODO: Document N+1 prevention and batch loading
### N+1 Query Problem
**Erkennnung**: Jede Iteration führt zusätzliche Query aus
```php
// ❌ N+1 Problem
$orders = $this->entityManager->findAll(Order::class); // 1 Query
foreach ($orders as $order) {
echo $order->getCustomer()->name; // N Queries (1 pro Order)
}
// Total: 1 + N Queries
```
### Solution: Batch Loading
```php
// ✅ Batch Loading
$orders = $this->entityManager->findWithRelations(
Order::class,
criteria: [],
relations: ['customer', 'items'] // Preload relations
);
foreach ($orders as $order) {
echo $order->getCustomer()->name; // Kein Query - bereits geladen
}
// Total: 2-3 Queries (1 für Orders, 1-2 für Relations)
```
### Eager vs Lazy Loading
**Strategic Loading**:
```php
// Heavy Operation - Eager Loading
public function getOrderDetails(OrderId $id): OrderDetails
{
$order = $this->entityManager->findEager(Order::class, $id->value);
// Alle Relations sofort geladen
return OrderDetails::fromOrder($order);
}
// Light Operation - Lazy Loading
public function listOrders(): array
{
// Relations werden nur bei Bedarf geladen
return $this->entityManager->findAll(Order::class);
}
```
### Pagination
```php
use App\Framework\Database\Repository\EntityRepository;
public function getUsers(int $page): PaginatedResult
{
return $this->entityRepository->findPaginated(
User::class,
page: $page,
limit: 20,
orderBy: ['created_at' => 'DESC']
);
}
// PaginatedResult enthält:
// - items: array (Entities)
// - total: int (Total count)
// - page: int (Current page)
// - limit: int (Items per page)
// - totalPages: int (Calculated)
```
### Query Caching
```php
// EntityManager mit Cache
$entityManager = new EntityManager(
// ...
cacheManager: $cacheManager
);
// Find verwendet Cache automatisch
$user = $entityManager->find(User::class, $id);
// Beim zweiten Aufruf: Cache Hit
```
## Connection Pooling
TODO: Document connection pool configuration
### Overview
Connection Pooling verbessert Performance durch Wiederverwendung von Datenbankverbindungen.
**Features**:
- Min/Max Connection Limits
- Connection Health Monitoring
- Automatic Retry mit Exponential Backoff
- Idle Connection Cleanup
- Connection Warmup
### Configuration
```php
use App\Framework\Database\Config\PoolConfig;
$poolConfig = new PoolConfig(
minConnections: 2, // Minimum pool size
maxConnections: 10, // Maximum pool size
maxIdleTime: 300, // Idle timeout (seconds)
healthCheckInterval: 60, // Health check frequency
enableWarmup: true // Pre-create min connections
);
```
### Usage
```php
use App\Framework\Database\ConnectionPool;
$pool = new ConnectionPool($driverConfig, $poolConfig, $timer);
// Get connection from pool
$connection = $pool->getConnection();
try {
// Use connection
$result = $connection->query('SELECT * FROM users');
} finally {
// Return connection to pool (automatic with PooledConnection)
$connection->release();
}
```
### Health Monitoring
```php
// Pool automatically monitors connection health
$pool->performHealthCheck();
// Get pool statistics
$stats = $pool->getPoolStatistics();
// Returns:
[
'current_connections' => 5,
'max_connections' => 10,
'connections_in_use' => 3,
'healthy_connections' => 5,
'total_created' => 12,
'total_destroyed' => 7
]
```
### Retry Logic
```php
// Automatic retry with exponential backoff
$retryStrategy = ExponentialBackoffStrategy::create()
->withMaxAttempts(3)
->withBaseDelay(Duration::fromMilliseconds(100));
// Connection pool uses retry automatically
$connection = $pool->getConnectionWithRetry($retryStrategy);
```
### Production Configuration
```php
// Production Pool Config
$productionPoolConfig = new PoolConfig(
minConnections: 5, // Keep 5 connections ready
maxConnections: 50, // Scale up to 50 under load
maxIdleTime: 600, // 10 minutes idle timeout
healthCheckInterval: 30, // Check every 30 seconds
enableWarmup: true, // Pre-warm connections
connectionTimeout: 5000 // 5 second timeout
);
```
## Transaction Management
TODO: Document transaction patterns and best practices
### Basic Transactions
```php
// EntityManager Transaction Helper
$this->entityManager->transaction(function (EntityManager $em) {
$order = Order::create($command);
$em->save($order);
$this->inventoryService->reserve($order->items);
$this->paymentService->charge($order->total);
// Automatic commit on success, rollback on exception
});
```
### Manual Transaction Control
```php
$unitOfWork = $this->entityManager->unitOfWork;
$unitOfWork->beginTransaction();
try {
// Operations
$unitOfWork->persist($entity1);
$unitOfWork->persist($entity2);
$unitOfWork->flush();
$unitOfWork->commit();
} catch (\Exception $e) {
$unitOfWork->rollback();
throw $e;
}
```
### Nested Transactions (Savepoints)
```php
$unitOfWork->beginTransaction();
try {
$order = Order::create($command);
$unitOfWork->persist($order);
// Nested transaction (savepoint)
$unitOfWork->beginTransaction();
try {
$this->inventoryService->reserve($order->items);
$unitOfWork->commit(); // Commit savepoint
} catch (\Exception $e) {
$unitOfWork->rollback(); // Rollback to savepoint
throw $e;
}
$unitOfWork->commit(); // Commit main transaction
} catch (\Exception $e) {
$unitOfWork->rollback(); // Rollback main transaction
throw $e;
}
```
### Transaction Isolation Levels
```php
use App\Framework\Database\TransactionIsolation;
$unitOfWork->beginTransaction(
isolation: TransactionIsolation::READ_COMMITTED
);
// Available isolation levels:
// - READ_UNCOMMITTED (lowest isolation)
// - READ_COMMITTED (default)
// - REPEATABLE_READ
// - SERIALIZABLE (highest isolation)
```
### Transaction Best Practices
**✅ Short Transactions**:
```php
// ✅ Keep transactions short
$this->entityManager->transaction(fn(EntityManager $em) =>
$em->save($entity)
);
```
**❌ Long Transactions**:
```php
// ❌ Avoid long-running transactions
$this->entityManager->transaction(function (EntityManager $em) {
$this->sendEmail($user); // External I/O - BAD
$this->processHeavyComputation(); // CPU-intensive - BAD
$em->save($entity);
});
```
**✅ Batch with Periodic Commits**:
```php
// ✅ Batch processing with periodic commits
$unitOfWork->setAutoCommit(false);
$batchSize = 100;
for ($i = 0; $i < count($items); $i++) {
$unitOfWork->persist($items[$i]);
if (($i + 1) % $batchSize === 0) {
$unitOfWork->flush();
$unitOfWork->commit();
$unitOfWork->beginTransaction();
}
}
$unitOfWork->flush();
$unitOfWork->commit();
$unitOfWork->setAutoCommit(true);
```
## Database Testing

View File

@@ -2,26 +2,962 @@
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
TODO: Document the differences and when to use each
### 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
TODO: Document domain event creation and handling
### 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
TODO: Document event handler attributes and 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
TODO: Document event middleware system
**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
TODO: Document async event handling patterns
### 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
TODO: List event system 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

View File

@@ -354,6 +354,7 @@ $users = $this->userRepository->findWithProfiles($userIds);
### Caching Strategy
**Framework Cache Interface mit Value Objects**:
```php
use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheKey;
@@ -878,4 +879,4 @@ Database VOs folgen allen Framework-Prinzipien:
-**Type Safety**: Union Types für Backwards Compatibility
-**Framework Integration**: `__toString()` für seamless SQL interpolation
-**Validation**: Constructor-basierte Validation
-**Explicit**: Factory Methods (`fromString()`) für clarity
-**Explicit**: Factory Methods (`fromString()`) für clarity

View File

@@ -45,6 +45,7 @@ Alle Caches unterstützen **automatische Performance-Metriken** über Decorator
**Performance**: ~70% schnellere Component-Initialisierung
**Verwendung**:
```php
use App\Framework\LiveComponents\Cache\ComponentStateCache;
use App\Framework\Core\ValueObjects\Duration;

File diff suppressed because it is too large Load Diff

View File

@@ -455,4 +455,4 @@ Die Pipeline nutzt konsequent Framework-Patterns:
- **Horizontal Scaling**: Multi-Worker Support
- **Queue Capacity**: 100,000+ Jobs (Database-backed)
- **Scheduler Load**: 10,000+ concurrent scheduled tasks
- **Memory Efficiency**: Linear scaling mit Job-Complexity
- **Memory Efficiency**: Linear scaling mit Job-Complexity

File diff suppressed because it is too large Load Diff