Generates Event Store pattern for PHP 8.4. Creates event sourcing storage infrastructure with event streams, stored events, optimistic locking, and version tracking. Includes unit tests.
Creates Event Store infrastructure for event sourcing with aggregate history and event replay.
| Scenario | Example |
|---|---|
| Event sourcing | Aggregate state from events |
| Audit trail | Complete change history |
| Event replay | Rebuild projections from events |
| Temporal queries | Query state at any point in time |
Path: src/Domain/{BoundedContext}/EventStore/
StoredEvent.php — Immutable stored event value objectEventStreamInterface.php — Event stream contractEventStream.php — Event stream implementationEventStoreInterface.php — Event store contractPath: src/Infrastructure/{BoundedContext}/EventStore/
DoctrineEventStore.php — Doctrine DBAL implementation with optimistic lockingStoredEventTest.php — Stored event immutability testsEventStreamTest.php — Stream operations testsDoctrineEventStoreTest.php — Integration tests| Component | Path |
|---|---|
| StoredEvent | src/Domain/{BoundedContext}/EventStore/ |
| EventStream | src/Domain/{BoundedContext}/EventStore/ |
| EventStoreInterface | src/Domain/{BoundedContext}/EventStore/ |
| DoctrineEventStore | src/Infrastructure/{BoundedContext}/EventStore/ |
| Unit Tests | tests/Unit/Domain/{BoundedContext}/EventStore/ |
| Integration Tests | tests/Integration/Infrastructure/{BoundedContext}/EventStore/ |
| Component | Pattern | Example |
|---|---|---|
| Stored Event | StoredEvent | StoredEvent |
| Stream Interface | EventStreamInterface | EventStreamInterface |
| Stream | EventStream | EventStream |
| Store Interface | EventStoreInterface | EventStoreInterface |
| Store Impl | Doctrine{Name}EventStore | DoctrineEventStore |
| Exception | ConcurrencyException | ConcurrencyException |
| Test | {ClassName}Test | StoredEventTest |
final readonly class StoredEvent
{
public function __construct(
public string $aggregateId,
public string $aggregateType,
public string $eventType,
public string $payload,
public int $version,
public \DateTimeImmutable $createdAt
) {}
public static function fromDomainEvent(string $aggregateId, string $aggregateType, int $version, object $event): self;
public function toArray(): array;
public static function fromArray(array $data): self;
}
interface EventStoreInterface
{
public function append(string $aggregateId, EventStream $events, int $expectedVersion): void;
public function load(string $aggregateId): EventStream;
public function loadFromVersion(string $aggregateId, int $fromVersion): EventStream;
}
final class EventStream implements \IteratorAggregate, \Countable
{
public static function empty(): self;
public static function fromEvents(array $events): self;
public function append(StoredEvent $event): self;
public function getVersion(): int;
public function isEmpty(): bool;
public function getIterator(): \ArrayIterator;
public function count(): int;
}
// Append events
$events = EventStream::fromEvents([
StoredEvent::fromDomainEvent($orderId, 'Order', 1, $orderCreated),
StoredEvent::fromDomainEvent($orderId, 'Order', 2, $itemAdded),
]);
$eventStore->append($orderId, $events, expectedVersion: 0);
// Load and replay
$stream = $eventStore->load($orderId);
foreach ($stream as $event) {
$aggregate->apply($event);
}
CREATE TABLE event_store (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
aggregate_id VARCHAR(36) NOT NULL,
aggregate_type VARCHAR(255) NOT NULL,
event_type VARCHAR(255) NOT NULL,
payload JSON NOT NULL,
version INT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE INDEX idx_aggregate_version (aggregate_id, version),
INDEX idx_aggregate_type (aggregate_type),
INDEX idx_event_type (event_type),
INDEX idx_created_at (created_at)
);
| Anti-pattern | Problem | Solution |
|---|---|---|
| Mutable Events | History changes | Immutable StoredEvent |
| Missing Version | No optimistic locking | Version per aggregate |
| No Idempotency | Duplicate appends | Unique aggregate_id + version |
| Large Payloads | Slow reads | Serialize only essential data |
| No Snapshots | Slow rebuilds for long streams | Use create-snapshot |
| Global Stream Only | Can't load per-aggregate | Per-aggregate stream support |
For complete PHP templates and examples, see:
references/templates.md — StoredEvent, EventStream, EventStoreInterface, DoctrineEventStore templatesreferences/examples.md — Order event store usage and tests