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 Store Laravel Package

prooph/event-store

Common interfaces and classes for Prooph Event Store implementations. Provides the core building blocks to work with event stores, with persistent implementations available via separate TCP and HTTP client packages. Supports PHP 7.4+ (v7).

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require prooph/event-store
    

    Ensure your project meets PHP 8.0+ requirements (updated from PHP 7.4+).

  2. Basic Event Store Initialization

    use Prooph\EventStore\EventStore;
    use Prooph\EventStore\StreamName;
    
    $eventStore = new EventStore(
        new \Prooph\EventStore\Pdo\MySql\EventStoreConnection(
            new \PDO('mysql:host=localhost;dbname=event_store', 'user', 'pass')
        )
    );
    
  3. First Use Case: Appending an Event

    $event = new \Prooph\EventStore\Model\Event(
        uuid('event-uuid'),
        'my-aggregate',
        'EventOccurred',
        ['data' => 'payload']
    );
    
    $eventStore->appendToStream(
        StreamName::fromString('my-stream'),
        [$event]
    );
    
  4. Where to Look First

    • Official Documentation
    • src/Prooph/EventStore/ for core classes.
    • tests/ for usage examples (note: abstract test classes have been renamed due to PHPUnit compatibility changes in v7.12.3).
    • Changelog for breaking changes.

Implementation Patterns

Common Workflows

1. Event Sourcing with Aggregates

class MyAggregate {
    private $events = [];

    public function apply(Event $event) {
        $this->events[] = $event;
        // Apply event logic (e.g., update state)
    }

    public function recordThat(EventOccurred $event) {
        $this->apply($event);
        return $event;
    }
}

// Usage:
$aggregate = new MyAggregate();
$event = $aggregate->recordThat(new EventOccurred('payload'));
$eventStore->appendToStream($streamName, [$event]);

2. Reading Events (Projection)

$events = $eventStore->load(
    StreamName::fromString('my-stream'),
    0, // from version
    10 // limit
);

foreach ($events as $event) {
    // Process event (e.g., update a read model)
}

3. Handling Snapshots

// Take a snapshot
$snapshot = new \Prooph\EventStore\Model\Snapshot(
    uuid('snapshot-uuid'),
    'my-aggregate',
    10, // version
    ['state' => 'serialized']
);
$eventStore->appendToStream($streamName, [$snapshot]);

// Load snapshot
$snapshot = $eventStore->loadSnapshot(
    StreamName::fromString('my-stream'),
    10
);

4. Integration with Prooph Service Bus

use Prooph\ServiceBus\EventBus;
use Prooph\EventStore\Plugin\AggregateTranslatorPlugin;

$eventBus = new EventBus();
$eventBus->plug(new AggregateTranslatorPlugin($eventStore));

Best Practices

  • Stream Naming: Use a consistent naming convention (e.g., {aggregateId}-{aggregateType}).
  • Event Versioning: Include metadata like occurredOn in events for time-based queries.
  • Error Handling: Wrap appendToStream in transactions or retry logic for idempotency.
  • Testing:
    • Mock EventStore for unit tests.
    • Use a lightweight in-memory store (e.g., Prooph\EventStore\Pdo\Sqlite\EventStoreConnection) for integration tests.
    • Note: Refactor tests using renamed abstract test classes due to PHPUnit compatibility changes in v7.12.3.

Gotchas and Tips

Pitfalls

  1. Concurrency Conflicts

    • Events are immutable, but streams are append-only. Use expectedVersion to handle conflicts:
      $eventStore->appendToStream(
          $streamName,
          [$event],
          5 // expectedVersion (throws if stream is ahead)
      );
      
    • Tip: Implement optimistic concurrency control in your application layer.
  2. Performance with Large Streams

    • Loading entire streams is inefficient. Use loadFrom() with version ranges or paginate:
      $events = $eventStore->loadFrom(
          $streamName,
          0,
          100 // batch size
      );
      
    • Tip: For read-heavy workloads, consider a separate projection database.
  3. Metadata Quirks

    • Event metadata (e.g., headers) is stored as JSON. Ensure serialization/deserialization is consistent:
      $event = new Event(
          uuid(),
          'stream',
          'Event',
          ['data' => 'payload'],
          ['custom' => json_encode(['key' => 'value'])] // Avoid nested JSON
      );
      
  4. Snapshot Gaps

    • Snapshots are stored as events. If a snapshot is corrupted or missing, reprocess events from the last known good version.
    • Tip: Validate snapshots during load:
      $snapshot = $eventStore->loadSnapshot($streamName, $version);
      if (!$snapshot) {
          // Rebuild from events
      }
      
  5. PHPUnit Test Compatibility

    • Breaking Change: Abstract test classes have been renamed in v7.12.3 due to PHPUnit compatibility updates.
    • Action: Update any custom test classes extending renamed abstract classes (e.g., Prooph\EventStore\Test\AbstractTest).

Debugging Tips

  • Enable Logging Configure the PDO connection to log SQL queries:
    $pdo = new \PDO('mysql:host=localhost;dbname=event_store', 'user', 'pass');
    $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
    
  • Check Stream Existence Use streamExists() before appending to avoid errors:
    if (!$eventStore->streamExists($streamName)) {
        $eventStore->createStream($streamName);
    }
    
  • Event Store Schema Ensure your database matches the expected schema (run migrations if using prooph/event-store-pdo).

Extension Points

  1. Custom Event Stores Implement Prooph\EventStore\EventStoreInterface for non-PDO backends (e.g., Redis, Elasticsearch):

    class CustomEventStore implements EventStoreInterface {
        public function appendToStream(StreamName $streamName, array $events, ?int $expectedVersion = null) {
            // Custom logic
        }
        // ... other methods
    }
    
  2. Plugins Extend functionality with plugins (e.g., AggregateTranslatorPlugin for service bus integration). Example:

    class MyPlugin implements PluginInterface {
        public function handle(Envelope $command) {
            // Custom command handling
        }
    }
    
  3. Event Serialization Override the default JSON serializer for custom formats (e.g., MessagePack):

    $eventStore->setSerializer(new \Prooph\EventStore\Serializer\MessagePackSerializer());
    
  4. Middleware Use middleware for cross-cutting concerns (e.g., logging, validation):

    $eventStore->plug(new class implements PluginInterface {
        public function handle(Envelope $command) {
            // Pre/post processing
        }
    });
    
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.
emuniq/filament-browser-notifications
syriable/filament-translator
hungnm28/livewire-form
wenprise/eloquent
crudly/encrypted
fadion/bouncy
cuci/prototurk-sdk
gos/pubsub-router-bundle
cuci/prototurk-sdk-symfony
clementtalleu/easyadmin-markdown-bundle
codeflextech/permission-manager
karnoweb/livewire-datepicker
sayedenam/sayed-dashboard
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui