The official Laravel package for patchlevel/event-sourcing. An Eloquent-like aggregate API, facades for commands, queries, repositories, and the store, Artisan commands for the projection lifecycle, and read models built with the Schema and DB facades - event sourcing that feels like Laravel.
$ composer require patchlevel/laravel-event-sourcing $ php artisan vendor:publish --tag patchlevel-config $ php artisan vendor:publish --tag patchlevel-migrations $ php artisan migrate
Define events and aggregates as plain PHP, then work with them the way you work with Laravel: static load and save on the aggregate, facades for the services, and read models built with the Schema and DB facades.
Pure PHP classes with a single attribute - no base class, no framework coupling. Your domain model stays clean and testable.
#[Event('hotel.guest_is_checked_in')] final class GuestIsCheckedIn { public function __construct( public readonly string $guestName, ) {} }
Extend the package's AggregateRoot and your aggregate gets static load and save helpers - the same mental model as an Eloquent model, backed by an event stream.
#[Aggregate(name: 'hotel')] final class Hotel extends AggregateRoot { #[Id] private Uuid $id; /** @var list<string> */ private array $guests = []; public function checkIn(string $guestName): void { if (in_array($guestName, $this->guests, true)) { throw new GuestAlreadyCheckedIn($guestName); } $this->recordThat(new GuestIsCheckedIn($guestName)); } #[Apply] protected function applyGuestIsCheckedIn(GuestIsCheckedIn $event): void { $this->guests[] = $event->guestName; } }
No repository wiring needed in your controllers: load the aggregate by id, call a business method, save. The package replays the event history behind the scenes.
final class HotelController { public function checkIn(string $id, Request $request): JsonResponse { $hotel = Hotel::load(Uuid::fromString($id)); $hotel->checkIn($request->json('name')); $hotel->save(); return response()->json(); } }
Projectors build read models the Laravel way: create tables with a Schema Blueprint in #[Setup], write rows with the DB facade. Rebuild any time from the event stream.
#[Projector('hotel')] final class HotelProjection { use SubscriberUtil; #[Subscribe(GuestIsCheckedIn::class)] public function handleGuestIsCheckedIn(Uuid $hotelId): void { DB::table($this->table()) ->where('id', $hotelId->toString()) ->increment('guests'); } #[Setup] public function create(): void { Schema::create($this->table(), static function (Blueprint $table): void { $table->uuid('id')->primary(); $table->string('name'); $table->integer('guests'); }); } }
Not a generic library with a thin adapter. The package plugs event sourcing into the Laravel building blocks you already use every day.
Dispatch commands and queries through facades, reach the store and repositories the same way, and drive the whole event-sourcing lifecycle from Artisan.
Dispatch commands with CommandBus::dispatch - handled by the method you marked with the #[Handle] attribute, including aggregate loading and saving.
Retrieve data with QueryBus::dispatch - answered by services via the #[Answer] attribute.
Grab the repository for any aggregate with Repository::get, or load and save raw messages through the Store facade.
Set up subscriptions, boot projections, and run workers with php artisan event-sourcing:* commands.
use Patchlevel\LaravelEventSourcing\Facade\CommandBus; use Patchlevel\LaravelEventSourcing\Facade\QueryBus; use Patchlevel\LaravelEventSourcing\Facade\Repository; // dispatch a command CommandBus::dispatch(new BookHotel($hotelId, $guestName)); // ask a question $count = QueryBus::dispatch(new HotelCountQuery()); // or talk to the repository directly $repository = Repository::get(Hotel::class); $hotel = $repository->load($hotelId);
$ php artisan event-sourcing:subscription:setup $ php artisan event-sourcing:subscription:boot $ php artisan event-sourcing:subscription:run
The integration ships everything from patchlevel/event-sourcing - production features you would otherwise build yourself.
Managed lifecycle for projections and processors: replay, rebuild, retry, gap detection, and blue-green versioning.
Mark fields as personal data, store keys separately, and make a subject unreadable forever - without rewriting history.
Automatic, cache-backed snapshots keep replays fast for long-lived aggregates.
Evolve old events on the fly, or rewrite them permanently when you retire legacy formats.
Archive past lifecycle phases and load only the active slice - full history preserved.
Given-When-Then testing with in-memory stores for lightning-fast unit tests of business logic.
Everything you need to know about event sourcing in Laravel with patchlevel.
Run composer require patchlevel/laravel-event-sourcing. The service provider is auto-discovered. Publish the config with php artisan vendor:publish --tag patchlevel-config, publish and run the migrations, and add the EventSourcingMiddleware in bootstrap/app.php - then you are ready to define your first aggregate.
Yes. Aggregates extend an AggregateRoot base class with static load and save helpers similar to Eloquent models, facades like Repository, Store, CommandBus, and QueryBus give quick access to services, and Artisan picks up all CLI commands automatically.
Absolutely. Event-sourced aggregates and Eloquent models coexist in the same application. Projections typically build read models with the Schema and DB facades or with Eloquent models, so the read side stays plain Laravel.
The event store is built on Doctrine DBAL and supports PostgreSQL, MySQL, MariaDB, and SQLite out of the box. Custom stores for other technologies can be implemented against clean interfaces.
Projectors subscribe to events with the #[Subscribe] attribute and write read models using the DB facade, the Schema builder, or Eloquent. The subscription engine manages their lifecycle - boot, replay, rebuild, retry - via Artisan commands.
The package is the official Laravel integration of patchlevel/event-sourcing, which has over 300,000 Packagist installs. It is MIT licensed, fully typed, and includes production features like GDPR-compliant crypto-shredding, snapshots, and a managed subscription lifecycle.
Install the package and build your first aggregate, event, and projection in minutes - the getting-started guide walks you through it.