Install the Package
composer require event-engine/php-event-store
Define a Laravel-Compatible Event Store
Create a concrete implementation of EventEngine\EventStore\EventStore:
namespace App\Services;
use EventEngine\EventStore\Event;
use EventEngine\EventStore\EventStore;
use Illuminate\Support\Facades\DB;
class DatabaseEventStore implements EventStore
{
public function append(Event $event): void
{
DB::table('events')->insert([
'aggregate_id' => $event->getAggregateId(),
'event_name' => $event->getName(),
'data' => json_encode($event->getData()),
'version' => $event->getVersion(),
'occurred_on' => now(),
]);
}
public function load(string $aggregateId, int $fromVersion = 0, int $toVersion = PHP_INT_MAX): array
{
return DB::table('events')
->where('aggregate_id', $aggregateId)
->whereBetween('version', [$fromVersion, $toVersion])
->orderBy('version')
->get()
->map(fn ($event) => new Event(
$event->event_name,
json_decode($event->data, true),
$event->version,
$event->aggregate_id,
$event->occurred_on
));
}
}
Bind the Store to Laravel’s Container
In AppServiceProvider@boot():
public function boot()
{
$this->app->singleton(EventStore::class, function () {
return new DatabaseEventStore();
});
}
First Use Case: Storing and Loading Events
use EventEngine\EventStore\Event;
// Store an event
$eventStore = app(EventStore::class);
$event = new Event('user.registered', ['email' => 'user@example.com'], 1, 'user-123');
$eventStore->append($event);
// Load events for an aggregate
$events = $eventStore->load('user-123');
foreach ($events as $event) {
// Process events (e.g., rebuild aggregate state)
}
Create a Migration for Events Table
php artisan make:migration create_events_table
public function up()
{
Schema::create('events', function (Blueprint $table) {
$table->id();
$table->string('aggregate_id');
$table->string('event_name');
$table->json('data');
$table->unsignedInteger('version');
$table->timestamp('occurred_on');
$table->index(['aggregate_id', 'version']);
});
}
Leverage the load method to reconstruct aggregate state:
namespace App\Domain;
use EventEngine\EventStore\EventStore;
use EventEngine\EventStore\Event;
class UserAggregate
{
private array $events = [];
private string $id;
private int $version = 0;
public function __construct(private EventStore $eventStore, string $id)
{
$this->id = $id;
$this->loadFromStore();
}
private function loadFromStore(): void
{
$this->events = $this->eventStore->load($this->id);
$this->version = count($this->events);
$this->replayEvents();
}
private function replayEvents(): void
{
foreach ($this->events as $event) {
$this->apply($event);
}
}
private function apply(Event $event): void
{
$this->version = $event->getVersion();
switch ($event->getName()) {
case 'user.registered':
$this->handleRegistered($event->getData());
break;
case 'user.email.updated':
$this->handleEmailUpdated($event->getData());
break;
}
}
// Domain methods...
}
Use the event store to persist command outcomes:
namespace App\Console\Commands;
use App\Domain\UserAggregate;
use EventEngine\EventStore\Event;
use Illuminate\Console\Command;
class RegisterUserCommand extends Command
{
public function handle()
{
$userId = 'user-' . Str::uuid()->toString();
$aggregate = new UserAggregate(app(EventStore::class), $userId);
$aggregate->register(
$this->ask('Email'),
$this->ask('Name')
);
// Events are automatically appended to the store via the aggregate
}
}
Integrate with Laravel’s event system to trigger side effects:
namespace App\Listeners;
use EventEngine\EventStore\Event;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class UserRegisteredListener implements ShouldQueue
{
use InteractsWithQueue;
public function handle(Event $event)
{
if ($event->getName() === 'user.registered') {
// Send welcome email, log activity, etc.
}
}
}
Project events into read models for queries:
namespace App\Services;
use EventEngine\EventStore\EventStore;
use Illuminate\Support\Facades\DB;
class UserReadModel
{
public function __construct(private EventStore $eventStore)
{}
public function sync(string $userId)
{
$events = $this->eventStore->load($userId);
DB::table('user_profiles')->upsert(
$events->map(fn ($event) => [
'id' => $userId,
'email' => $event->getData()['email'] ?? null,
'name' => $event->getData()['name'] ?? null,
'updated_at' => $event->getOccurredOn(),
])
);
}
}
Mock the event store in tests:
namespace Tests\Unit;
use EventEngine\EventStore\Event;
use EventEngine\EventStore\EventStore;
use Tests\TestCase;
class UserAggregateTest extends TestCase
{
public function testAggregateReconstruction()
{
$mockStore = $this->createMock(EventStore::class);
$mockStore->method('load')
->willReturn([
new Event('user.registered', ['email' => 'test@example.com'], 1, 'user-1'),
]);
$aggregate = new UserAggregate($mockStore, 'user-1');
$this->assertEquals('test@example.com', $aggregate->getEmail());
}
}
Centralize event store configuration:
namespace App\Providers;
use EventEngine\EventStore\EventStore;
use Illuminate\Support\ServiceProvider;
class EventStoreServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(EventStore::class, function ($app) {
return new \App\Services\DatabaseEventStore(
$app->make(\Illuminate\Database\Connection::class)
);
});
}
}
Versioning Conflicts
append method:
public function append(Event $event): void
{
$currentVersion = DB::table('events')
->where('aggregate_id', $event->getAggregateId())
->max('version') ?? 0;
if ($event->getVersion() !== $currentVersion + 1) {
throw new \RuntimeException('Version conflict');
}
// Proceed with insertion
}
Event Data Serialization
use Symfony\Component\Serializer\SerializerInterface;
$serializer = app(SerializerInterface::class);
$data = $serializer->serialize($eventData, 'json');
Performance with Large Aggregates
public function load(string $aggregateId, int $fromVersion = 0, int $toVersion = PHP_INT_MAX, int $limit = 1000): array
{
return DB::table('events')
->where('aggregate_id', $aggregateId)
->whereBetween('version', [$fromVersion, $toVersion])
->orderBy('version')
->limit($limit)
->get()
->map(...);
}
**Laravel’s Queue System Conf
How can I help you explore Laravel packages today?