Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Event Sourcing Laravel Package

prooph/event-sourcing

Lightweight PHP event sourcing library with out-of-the-box integration for prooph/event-store. Provides an AggregateRoot base and AggregateTranslator, plus UUID generation and assertions support. Note: project was supported until Dec 31, 2019 and is deprecated.

View on GitHub
Deep Wiki
Context7
## Getting Started

### **First Steps**
1. **Installation**
   ```bash
   composer require prooph/event-sourcing:^5.7.0

Add the package to your composer.json and run composer update. Note: This version requires PHP 8.0+ due to compatibility changes.

  1. Core Concepts

    • Aggregate Root: Central entity managing state via events (now fully PHP 8 compatible).
    • Events: Immutable records of state changes (e.g., OrderCreated, PaymentProcessed).
    • Event Store: Persists events (use prooph/event-store for persistence).
    • Event Sourcing: Reconstruct state by replaying events.
  2. Minimal Example (PHP 8 Syntax)

    use Prooph\EventSourcing\AggregateRoot;
    
    class Order extends AggregateRoot
    {
        public function create(string $orderId, float $amount): void
        {
            $this->recordThat(new OrderCreated($orderId, $amount));
        }
    }
    
    • Extend AggregateRoot and use recordThat() to emit events.
    • PHP 8 Features: Leverage named arguments and constructor property promotion if applicable.
  3. Replaying Events

    $order = new Order();
    $order->replay([new OrderCreated('123', 100.0)]);
    

Implementation Patterns

1. Aggregate Design (PHP 8 Optimized)

  • Single Responsibility: Each aggregate handles one domain concept (e.g., Order, User).
  • Immutable Events: Events remain immutable DTOs (e.g., OrderCreated with orderId and amount).
  • State Management with PHP 8:
    protected function apply(OrderCreated $event): void
    {
        $this->orderId = $event->orderId();
        $this->amount  = $event->amount();
        // Use PHP 8's match expressions for complex event handling:
        match ($event::class) {
            OrderCreated::class => $this->handleOrderCreated($event),
            OrderCancelled::class => $this->handleOrderCancelled($event),
        };
    }
    

2. Event Store Integration

  • Use prooph/event-store for persistence (ensure compatibility with PHP 8):
    $eventStore = new \Prooph\EventStore\Pdo\MySqlEventStore(
        $pdo,
        new \Prooph\EventStore\Uuid\UuidGenerator()
    );
    
  • Save events with PHP 8 type safety:
    $order->recordThat(new OrderCreated('123', 100.0));
    $eventStore->appendToAggregate(
        $order->aggregateId(),
        $order->getUncommittedEvents(),
        $order->expectedVersion()
    );
    

3. Command Handling (PHP 8 Attributes)

  • Pair with prooph/service-bus for CQRS. Use PHP 8 attributes for metadata:
    #[CommandHandler]
    class CreateOrderHandler
    {
        public function __invoke(CreateOrderCommand $command, Order $order)
        {
            $order->create($command->orderId(), $command->amount());
        }
    }
    

4. Projections (Read Models)

  • Use prooph/event-store-bus-bridge to project events to read models:
    $projection = new OrderProjection($eventStore, $bus);
    $projection->listenTo(OrderCreated::class)->then(function (OrderCreated $event) {
        // Update read model (e.g., database, cache)
    });
    
    • PHP 8 Tip: Use arrow functions with typed parameters:
      $projection->listenTo(OrderCreated::class)->then(fn(OrderCreated $event) => {
          // ...
      });
      

5. Testing (PHP 8 Features)

  • Unit Tests: Mock events and replay with PHP 8 syntax:
    $order = new Order();
    $order->replay([new OrderCreated('123', 100.0)]);
    $this->assertSame('123', $order->orderId());
    
  • Integration Tests: Use a test event store (e.g., InMemoryEventStore).
    • PHP 8 Tip: Use strict_types=1 in test files for type safety.

Gotchas and Tips

Pitfalls

  1. PHP 8 Breaking Changes

    • Deprecated Features: Ensure no use of deprecated PHP 7 features (e.g., create_function, call_user_func_array with variadic args).
    • Fix: Update code to use PHP 8 equivalents (e.g., arrow functions, named args).
  2. Event Ordering

    • Events must be replayed in order. Use a reliable event store (e.g., PostgreSQL, MongoDB).
    • Fix: Ensure your event store supports sequential writes (e.g., prooph/event-store with PDO).
  3. Aggregate Identity

    • Always use a stable ID (e.g., UUID) for aggregates. Avoid transient IDs.
    • Fix: Use aggregateRootId() consistently with PHP 8's string type.
  4. State vs. Events

    • Anti-pattern: Storing derived state in events (e.g., OrderTotal if recalculable).
    • Fix: Keep events primitive; compute state during replay.
  5. Concurrency (PHP 8 Optimistic Locking)

    • Use expectedVersion in appendToAggregate:
      $eventStore->appendToAggregate(
          $order->aggregateId(),
          $order->getUncommittedEvents(),
          $order->expectedVersion()
      );
      
    • Fix: Handle ConcurrentModificationException with PHP 8's exception handling:
      try {
          $eventStore->appendToAggregate(...);
      } catch (ConcurrentModificationException $e) {
          // Retry or notify user
      }
      
  6. Performance

    • Replaying thousands of events can be slow. Use projections for read-heavy workloads.
    • PHP 8 Tip: Use match expressions for faster event routing in projections.

Debugging Tips

  • Event Logs: Enable debug logging for the event store:
    $eventStore->setLogger(new \Monolog\Logger('event_store', [
        'handlers' => [new \Monolog\Handler\StreamHandler('php://stderr')],
    ]));
    
  • Aggregate State: Dump state after replay with PHP 8's var_dump or print_r:
    var_dump($order->toArray());
    
  • Event Validation: Use EventValidator to catch malformed events early:
    $validator = new EventValidator();
    $validator->validate($event); // Throws \Prooph\Common\Exception\InvalidArgumentException
    

Extension Points

  1. Custom Event Stores (PHP 8 Interfaces)

    • Implement Prooph\EventStore\EventStoreInterface for custom backends (e.g., Kafka, DynamoDB):
      class KafkaEventStore implements EventStoreInterface
      {
          public function appendToAggregate(string $aggregateId, array $events, int $expectedVersion = 0): void
          {
              // PHP 8: Use match expressions or strict typing
          }
      }
      
  2. Event Serialization (PHP 8 Enums/Attributes)

    • Override EventSerializer for custom formats (e.g., JSON, Protocol Buffers):
      class JsonEventSerializer implements EventSerializerInterface
      {
          public function serialize(array $events): string
          {
              return json_encode($events, JSON_THROW_ON_ERROR);
          }
      }
      
  3. Aggregate Metadata (PHP 8 Properties)

    • Use aggregateMetadata() to store non-event data (e.g., timestamps):
      $this->aggregateMetadata()->set('created_at', new \DateTimeImmutable());
      
  4. Domain Events (PHP 8 Attributes)

    • Emit domain events (e.g., OrderCreatedEvent) alongside aggregates:
      $this->recordDomainEvent(new OrderCreatedEvent($event));
      
    • PHP 8 Tip: Use attributes for event metadata:
      #[DomainEvent]
      class OrderCreatedEvent {}
      

Config Quirks

  • Time Zones: Ensure events use consistent timestamps (e.g., UTC) with PHP 8's DateTimeImmutable.
  • Event IDs: Use EventId for uniqueness (default: UUID v7 for PHP 8 compatibility).
  • Versioning: Track aggregateVersion to handle concurrent updates with PHP 8's strict typing.
  • PHP 8 Specifics:
    • Enable strict_types=1 in composer.json:
      {
          "config": {
              "platform": {
                  "php": "8.0"
              }
          }
      }
      
    • Use array_key_first()/`array_key_last
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
cocosmos/filament-sticky-save-bar
patrickbussmann/oauth2-apple
3brs/enterprise-security-bundle
anousss007/vigilance
supportpal/eloquent-model
ardenexal/fhir-models
laravel-at/laravel-image-sanitize
romalytar/yammi-audit-log-laravel
ardenexal/fhir-validation
arshaviras/weather-widget
laravel-chronicle/core
sunchayn/nimbus
daikazu/eloquent-salesforce-objects
unseen-codes/chat
romalytar/yammi-jobs-monitoring-laravel
kisame76/filament-db-table-state
nqxcode/laravel-lucene-search
dpfx/laravel-livewire-wizards
workos/workos-php-laravel
sofa/laravel-global-scope