### Install spatie/laravel-event-sourcing via Composer Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/installation-setup.md Installs the spatie/laravel-event-sourcing package using Composer. This is the primary method for adding the package to your Laravel project. No specific inputs or outputs are detailed beyond the package installation. ```bash composer require spatie/laravel-event-sourcing ``` -------------------------------- ### Publish Laravel Event Sourcing Configuration Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/installation-setup.md Publishes the configuration file for spatie/laravel-event-sourcing. This allows you to customize various aspects of the package's behavior, such as auto-discovery of projectors and reactors, event handling, and repository implementations. The default configuration file is provided. ```bash php artisan vendor:publish --provider="Spatie\EventSourcing\EventSourcingServiceProvider" --tag="event-sourcing-config" ``` -------------------------------- ### Publish and Run Laravel Event Sourcing Migrations Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/installation-setup.md Publishes the necessary migration files for the 'stored_events' table and then runs the database migrations. This ensures that the required database structure is in place for event sourcing. It requires the Laravel application to have database migration capabilities enabled. ```bash php artisan vendor:publish --provider="Spatie\EventSourcing\EventSourcingServiceProvider" --tag="event-sourcing-migrations" php artisan migrate ``` -------------------------------- ### Create Account Example (PHP) Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md Demonstrates creating a new account using Eloquent. This is a contextual example for firing new events after projectors are set up. ```php Account::createWithAttributes(['name' => 'Yoda']); ``` -------------------------------- ### Default Configuration for Laravel Event Sourcing Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/installation-setup.md The default configuration file (`config/event-sourcing.php`) for the spatie/laravel-event-sourcing package. This file contains settings for auto-discovering projectors and reactors, defining custom projectors and reactors, configuring queueing, exception handling, and specifying models and repositories for stored events and snapshots. ```php [ app()->path(), ], /* * This directory will be used as the base path when scanning * for projectors and reactors. */ 'auto_discover_base_path' => base_path(), /* * Projectors are classes that build up projections. You can create them by performing * `php artisan event-sourcing:create-projector`. When not using auto-discovery, * Projectors can be registered in this array or a service provider. */ 'projectors' => [ // App\Projectors\YourProjector::class ], /* * Reactors are classes that handle side-effects. You can create them by performing * `php artisan event-sourcing:create-reactor`. When not using auto-discovery * Reactors can be registered in this array or a service provider. */ 'reactors' => [ // App\Reactors\YourReactor::class ], /* * A queue is used to guarantee that all events get passed to the projectors in * the right order. Here you can set of the name of the queue. */ 'queue' => env('EVENT_PROJECTOR_QUEUE_NAME', null), /* * When a Projector or Reactor throws an exception the event Projectionist can catch it * so all other projectors and reactors can still do their work. The exception will * be passed to the `handleException` method on that Projector or Reactor. */ 'catch_exceptions' => env('EVENT_PROJECTOR_CATCH_EXCEPTIONS', false), /* * This class is responsible for storing events in the EloquentStoredEventRepository. * To add extra behaviour you can change this to a class of your own. It should * extend the \Spatie\EventSourcing\StoredEvents\Models\EloquentStoredEvent model. */ 'stored_event_model' => Spatie\EventSourcing\StoredEvents\Models\EloquentStoredEvent::class, /* * This class is responsible for storing events. To add extra behaviour you * can change this to a class of your own. The only restriction is that * it should implement \Spatie\EventSourcing\StoredEvents\Repositories\EloquentStoredEventRepository. */ 'stored_event_repository' => Spatie\EventSourcing\StoredEvents\Repositories\EloquentStoredEventRepository::class, /* * This class is responsible for storing snapshots. To add extra behaviour you * can change this to a class of your own. The only restriction is that * it should implement \Spatie\EventSourcing\Snapshots\EloquentSnapshotRepository. */ 'snapshot_repository' => Spatie\EventSourcing\Snapshots\EloquentSnapshotRepository::class, /* * This class is responsible for storing events in the EloquentSnapshotRepository. * To add extra behaviour you can change this to a class of your own. It should * extend the \Spatie\EventSourcing\Snapshots\EloquentSnapshot model. */ 'snapshot_model' => Spatie\EventSourcing\Snapshots\EloquentSnapshot::class, /* * This class is responsible for handling stored events. To add extra behaviour you * can change this to a class of your own. The only restriction is that * it should implement \Spatie\EventSourcing\StoredEvents\HandleDomainEventJob. */ 'stored_event_job' => Spatie\EventSourcing\StoredEvents\HandleStoredEventJob::class, /* * Similar to Relation::enforceMorphMap() this option will make sure that every event has a * corresponding alias defined. Otherwise, an exception is thrown * if you try to persist an event without alias. */ 'enforce_event_class_map' => false, /* * Similar to Relation::morphMap() you can define which alias responds to which * event class. This allows you to change the namespace or class names * of your events but still handle older events correctly. */ 'event_class_map' => [], /* * This class is responsible for serializing events. By default an event will be serialized * and stored as json. You can customize the class name. A valid serializer * should implement Spatie\EventSourcing\EventSerializers\EventSerializer. */ ``` -------------------------------- ### Add and Subtract Money Example (PHP) Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md Shows how to add and subtract money from an account, which in turn fires 'MoneyAdded' and 'MoneySubtracted' events that the projectors will handle. ```php $yetAnotherAccount = Account::where(['name' => 'Yoda'])->first(); $yetAnotherAccount->addMoney(1000); $yetAnotherAccount->subtractMoney(50); ``` -------------------------------- ### Configure Laravel Horizon for Event Sourcing Queues Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/installation-setup.md Defines supervisor configurations for Laravel Horizon to manage event sourcing-related queues. It specifies connection, queue name, balance strategy, processes, and retries. Crucially, it ensures only one process handles the event sourcing queue to maintain order. ```php 'environments' => [ 'production' => [ // ... 'event-sourcing-supervisor-1' => [ 'connection' => 'redis', 'queue' => [env('EVENT_PROJECTOR_QUEUE_NAME')], 'balance' => 'simple', 'processes' => 1, 'tries' => 3, ], ], 'local' => [ // ... 'event-sourcing-supervisor-1' => [ 'connection' => 'redis', 'queue' => [env('EVENT_PROJECTOR_QUEUE_NAME')], 'balance' => 'simple', 'processes' => 1, 'tries' => 3, ], ], ], ``` -------------------------------- ### Create AccountBalanceProjector in PHP Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md An example `AccountBalanceProjector` class that extends `Spatie\EventSourcing\EventHandlers\Projectors\Projector`. It listens for `AccountCreated`, `MoneyAdded`, `MoneySubtracted`, and `AccountDeleted` events to manage account balances and deletions. ```php namespace App\Projectors; use App\Account; use App\Events\AccountCreated; use App\Events\AccountDeleted; use App\Events\MoneyAdded; use App\Events\MoneySubtracted; use Spatie\EventSourcing\EventHandlers\Projectors\Projector; class AccountBalanceProjector extends Projector { public function onAccountCreated(AccountCreated $event) { (new Account($event->accountAttributes))->writeable()->save(); } public function onMoneyAdded(MoneyAdded $event) { $account = Account::uuid($event->accountUuid); $account->balance += $event->amount; $account->writeable()->save(); } public function onMoneySubtracted(MoneySubtracted $event) { $account = Account::uuid($event->accountUuid); $account->balance -= $event->amount; $account->writeable()->save(); } public function onAccountDeleted(AccountDeleted $event) { Account::uuid($event->accountUuid)->writeable()->delete(); } } ``` -------------------------------- ### Configure Event Serialization and Normalization Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/installation-setup.md Sets the JSON event serializer and defines a chain of normalizers to handle complex PHP objects like Carbon instances and Eloquent models during event serialization. Additional custom normalizers can be added to the array. ```php 'event_serializer' => Spatie\EventSourcing\EventSerializers\JsonEventSerializer::class, /* * These classes normalize and restore your events when they're serialized. They allow * you to efficiently store PHP objects like Carbon instances, Eloquent models, and * Collections. If you need to store other complex data, you can add your own normalizers * to the chain. See https://symfony.com/doc/current/components/serializer.html#normalizers */ 'event_normalizers' => [ Spatie\EventSourcing\Support\CarbonNormalizer::class, Spatie\EventSourcing\Support\ModelIdentifierNormalizer::class, Symfony\Component\Serializer\Normalizer\DateTimeNormalizer::class, Symfony\Component\Serializer\Normalizer\ArrayDenormalizer::class, Spatie\EventSourcing\Support\ObjectNormalizer::class, ], ``` -------------------------------- ### Configure Cache Path for Discovered Event Handlers Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/installation-setup.md Specifies the directory where the package should store the cache for auto-discovered projectors and reactors. This improves performance in production environments by avoiding rescanning classes on every request. The default path is bootstrap/cache. ```php 'cache_path' => base_path('bootstrap/cache'), ``` -------------------------------- ### Persisting Aggregate State and Recording Events Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-aggregates/writing-your-first-aggregate.md Examples demonstrating how to retrieve an aggregate, perform actions that record events, and then persist the changes. Persisting writes events to the database and triggers projectors/reactors. ```php AccountAggregate::retrieve($uuid) ->createAccount('my account', auth()->user()->id) ->persist(); ``` ```php AccountAggregate::retrieve($uuid) ->addMoney(123) ->persist(); ``` ```php AccountAggregate::retrieve($uuid) ->subtractMoney(456) ->persist(); ``` -------------------------------- ### Enable Dispatching Events from Aggregate Roots Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/installation-setup.md A boolean flag that determines whether events fired from aggregate roots should also be dispatched as regular Laravel events. Defaults to false. ```php 'dispatch_events_from_aggregate_roots' => false, ``` -------------------------------- ### Example Storable Event Class Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/preparing-events.md Demonstrates the structure of a basic event class that extends `ShouldBeStored`. It includes properties and a constructor to define the event's data. Events extending this class are automatically serialized and stored. ```php namespace App\Events; use Spatie\EventSourcing\StoredEvents\ShouldBeStored; class MoneyAdded extends ShouldBeStored { /** @var string */ public $accountUuid; /** @var int */ public $amount; public function __construct(string $accountUuid, int $amount) { $this->accountUuid = $accountUuid; $this->amount = $amount; } } ``` -------------------------------- ### Add Multiple Reactors via Projectionist Facade Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/adding-and-removing-projectors-and-reactors.md This code demonstrates adding multiple reactors at once using the `Projectionist` facade. It takes an array of reactor class names as input. This is a convenient way to register several reactors, streamlining the setup process. ```php Projectionist::addReactors([ SendMailReactor::class, SendPushNotificationReactor::class, ]); ``` -------------------------------- ### Add Single Reactor via Projectionist Facade Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/adding-and-removing-projectors-and-reactors.md This example illustrates adding a single reactor to the event handling system via the `Projectionist` facade. The reactor's class name is passed as an argument. This allows for granular control over which reactors are active in your event sourcing setup. ```php Projectionist::addReactor(SendMailReactor::class); ``` -------------------------------- ### Create Storable Event with Artisan Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/preparing-events.md Generates a new event class that extends `Spatie\EventSourcing\StoredEvents\ShouldBeStored`. This command is the starting point for creating events that the package will store and process. ```bash php artisan make:storable-event NameOfYourEvent ``` -------------------------------- ### Laravel Event Sourcing Configuration Example Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/getting-familiar-with-event-sourcing/using-projectors-to-transform-events.md This configuration file snippet for spatie/laravel-event-sourcing shows how to register projectors. By defining the projectors in the 'projectors' array, you ensure that the package is aware of and utilizes these classes to process events and build projections. This is a key step in setting up the event sourcing system. ```php [ App\Projectors\AccountsProjector::class, // ... other projectors ], ]; ``` -------------------------------- ### Implement Projection Factories with Traits in Laravel Tests Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md This example showcases an advanced method for handling projection factories in Laravel tests using a trait. The 'SupportsProjections' trait customizes model creation for factories, ensuring UUID generation and writeability. This approach is beneficial for more complex scenarios and promotes cleaner test code, similar to regular CRUD application testing. ```php // Trait file: trait SupportsProjections { public function newModel(array $attributes = []) { return Factory::newModel([ 'uuid' => fake()->uuid(), ...$attributes, ])->writeable(); } } // Factory file: class AccountFactory extends Factory { use SupportsProjections; public function definition(): array { return [...]; } } // Test file: public function test_can_have_many_accounts() { Account::factory()->times(5)->create(); } ``` -------------------------------- ### Implement getWeight for Projector Order Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/making-sure-events-get-handled-in-the-right-order.md This code demonstrates how to implement the getWeight method within a projector to control the order in which projectors are executed. Projectors with a lower weight value are processed first. The weight can be static or dynamically determined based on the event being processed. ```php namespace App\Projectors; use Spatie\EventSourcing\EventHandlers\Projectors\Projector; use Spatie\EventSourcing\StoredEvents\StoredEvent; class MyProjector extends Projector { public function getWeight(?StoredEvent $event): int { return 5; } // } ``` ```php namespace App\Projectors; use App\Events\MoneyAddedEvent; use App\Events\MoneySubtractedEvent; use Spatie\EventSourcing\EventHandlers\Projectors\Projector; use Spatie\EventSourcing\StoredEvents\StoredEvent; class MyProjector extends Projector { public function getWeight(?StoredEvent $event): int { return match ($event?->event_class) { MoneyAddedEvent::class => 2, MoneySubtractedEvent::class => -2, default => 0, }; } // } ``` -------------------------------- ### Dedicated Event Handler Class Implementation Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/creating-and-configuring-projectors.md This PHP code presents an example implementation of a dedicated event handler class. This class has an `__invoke` method that accepts the event object and performs the necessary actions, such as adding money to an account. This pattern enhances modularity in event handling. ```php use App\Events\MoneyAdded; class AddMoneyToAccount { public function __invoke(MoneyAdded $event) { $event->account->addMoney($event->amount); } } ``` -------------------------------- ### Example Laravel Controller Method Firing Events Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/getting-familiar-with-event-sourcing/using-projectors-to-transform-events.md This snippet demonstrates how a Laravel controller method can trigger events that implement the ShouldBeStored interface. These events are then stored by the spatie/laravel-event-sourcing package and passed to projectors. It showcases the initial step in the event sourcing flow where application logic generates events. ```php account_id); $account->addMoney($request->amount); } } ``` -------------------------------- ### Create Aggregate Root using Artisan Command Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-aggregates/creating-and-configuring-aggregates.md Generates a new aggregate root class file. This command is the quickest way to start building your aggregate logic. It creates a basic class structure that extends the base AggregateRoot. ```bash php artisan make:aggregate MyAggregate ``` ```php namespace App\Aggregates; use Spatie\EventSourcing\AggregateRoots\AggregateRoot; class MyAggregate extends AggregateRoot { } ``` -------------------------------- ### Extend JsonEventSerializer for Event Upgrades - PHP Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/using-your-own-event-serializer.md This example demonstrates how to extend the default JsonEventSerializer to handle event payload upgrades. It customizes the deserialize method to add missing fields, like 'currency', to older event versions on the fly. ```php use App\Events\MoneyAdded; class UpgradeSerializer extends JsonEventSerializer { public function deserialize(string $eventClass, string $json, int $version, ?string $metadata = null): ShouldBeStored { $event = parent::deserialize($eventClass, $json, $version, $metadata); // all currency was USD before we started accepting other currencies if ($eventClass === MoneyAdded::class && empty($event->currency)) { $event->currency = 'USD'; } return $event; } } ``` -------------------------------- ### Projector Reset State Method - PHP Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/replaying-events.md Defines a `resetState` method within a projector that gets called before events are replayed. It can optionally accept an aggregate UUID for targeted resets. ```php public function resetState(?string $aggregateUuid = null): void { // reset your projector } ``` -------------------------------- ### Conditional Event Recording in Aggregate (PHP) Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/getting-familiar-with-event-sourcing/using-aggregates-to-make-decisions-based-on-the-past.md Shows how to use instance variables (recalculated from past events) within an aggregate to make decisions about recording new events. This example checks if subtracting money would exceed a limit and records either a `MoneySubtracted` event or other relevant events based on the condition. ```php public function subtractMoney(Money $amount, string $reason): { if ($this->balance->lessThan($amount)) { $this->recordThat(new CannotSubtractMoney($amount, $reason)); return; } $this->recordThat(new MoneySubtracted($amount, $reason)); } ``` -------------------------------- ### Remove Multiple Event Handlers via Projectionist Facade Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/adding-and-removing-projectors-and-reactors.md This example demonstrates removing multiple event handlers (projectors and reactors) simultaneously using the `withoutEventHandlers` method of the `Projectionist` facade. It accepts an array of event handler class names. This method is efficient for excluding several handlers at once from the event sourcing process. ```php Projectionist::withoutEventHandlers([ TransactionCountProjector::class, SendPushNotificationReactor::class, ]); ``` -------------------------------- ### Run Database Migration (Artisan Command) Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md Executes the database migrations to create the 'transactions_count' table in the database. This command should be run after defining the migration file. ```bash php artisan migrate ``` -------------------------------- ### Fire Account Creation Events in PHP Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md Demonstrates how to fire `AccountCreated` events using the `Account::createWithAttributes` method. This is a high-level way to initiate the event sourcing process for account creation. ```php Account::createWithAttributes(['name' => 'Luke']); Account::createWithAttributes(['name' => 'Leia']); ``` -------------------------------- ### Fire Transaction Events in PHP Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md Shows how to fire `MoneyAdded` and `MoneySubtracted` events to simulate financial transactions. This involves retrieving an account and calling its associated methods like `addMoney` and `subtractMoney`. ```php $account = Account::where(['name' => 'Luke'])->first(); $anotherAccount = Account::where(['name' => 'Leia'])->first(); $account->addMoney(1000); $anotherAccount->addMoney(500); $account->subtractMoney(50); ``` -------------------------------- ### Basic Aggregate Root Class Structure Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-aggregates/writing-your-first-aggregate.md A minimal aggregate root class extending `Spatie\EventSourcing\AggregateRoots\AggregateRoot`. This class will be extended to add methods for recording events. ```php namespace App\Aggregates; use Spatie\EventSourcing\AggregateRoots\AggregateRoot; class AccountAggregate extends AggregateRoot { } ``` -------------------------------- ### Account Model with Event Handling - Laravel Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md This Eloquent model, 'Account', extends Spatie's 'Projection' class and is designed to handle bank account operations. It includes methods for creating accounts, adding/subtracting money, and deleting accounts, all by dispatching corresponding events. It also features a helper method to retrieve accounts by their UUID. ```php namespace App; use App\Events\AccountCreated; use App\Events\AccountDeleted; use App\Events\MoneyAdded; use App\Events\MoneySubtracted; use Ramsey\Uuid\Uuid; use Spatie\EventSourcing\Projections\Projection; class Account extends Projection { protected $guarded = []; public static function createWithAttributes(array $attributes): Account { /* * Let's generate a uuid. */ $attributes['uuid'] = (string) Uuid::uuid4(); /* * The account will be created inside this event using the generated uuid. */ event(new AccountCreated($attributes)); /* * The uuid will be used the retrieve the created account. */ return static::uuid($attributes['uuid']); } public function addMoney(int $amount) { event(new MoneyAdded($this->uuid, $amount)); } public function subtractMoney(int $amount) { event(new MoneySubtracted($this->uuid, $amount)); } public function remove() { event(new AccountDeleted($this->uuid)); } /* * A helper method to quickly retrieve an account by uuid. */ public static function uuid(string $uuid): ?Account { return static::where('uuid', $uuid)->first(); } } ``` -------------------------------- ### Get Aggregate UUID from Event Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-reactors/creating-and-configuring-reactors.md Access the unique identifier of the aggregate root associated with an event. This is crucial for retrieving related data, such as an account, when processing an event. ```php // ... public function onMoneyAdded(MoneyAdded $event) { $account = Account::findByUuid($event->aggregateRootUuid()); Mail::to($account->user)->send(new MoreMoneyAddedMailable()); } ``` -------------------------------- ### Triggering the Reactor - PHP Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-reactors/writing-your-first-reactor.md This PHP code snippet demonstrates how to trigger the 'BigAmountAddedReactor'. By creating an account and then calling the `addMoney` method with an amount of 1000, it simulates an event that the reactor is configured to listen for, leading to the execution of its logic (sending an email in this case). ```PHP $account = Account::createWithAttributes(['name' => 'Rey']); $account->addMoney(1000); ``` -------------------------------- ### Aggregate Root with Event Recording Methods Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-aggregates/writing-your-first-aggregate.md This aggregate root defines methods to record domain events like `AccountCreated`, `MoneyAdded`, and `MoneySubtracted`. The `recordThat()` method stages events for persistence. ```php namespace App\Aggregates; use Spatie\EventSourcing\AggregateRoots\AggregateRoot; class AccountAggregate extends AggregateRoot { public function createAccount(string $name, string $userId) { $this->recordThat(new AccountCreated($name, $userId)); return $this; } public function addMoney(int $amount) { $this->recordThat(new MoneyAdded($amount)); return $this; } public function subtractAmount(int $amount) { $this->recordThat(new MoneySubtracted($amount)); return $this; } public function deleteAccount() { $this->recordThat(new AccountDeleted()); return $this; } } ``` -------------------------------- ### Create Accounts Table Migration - Laravel Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md This Laravel migration creates a database table named 'accounts' to store account information, including a unique 'uuid', 'name', and 'balance'. It utilizes Eloquent's Schema Builder and Blueprint for defining the table structure. ```php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateAccountsTable extends Migration { public function up() { Schema::create('accounts', function (Blueprint $table) { $table->increments('id'); $table->string('uuid'); $table->string('name'); $table->integer('balance')->default(0); $table->timestamps(); }); } } ``` -------------------------------- ### Create a Reactor using Artisan Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-reactors/creating-and-configuring-reactors.md This command generates a new reactor class in the `app\Reactors` directory. Reactors are responsible for reacting to specific events. ```bash php artisan make:reactor BigAmountAddedReactor ``` -------------------------------- ### Initialize Event Query by Querying and Applying Events Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/event-queries.md This PHP code snippet demonstrates the constructor for the EarningsForProductAndPeriod event query. It queries the 'events' table for 'OrderCreated' events within a specified date range and applies each retrieved event using the 'apply' method. This is suitable for limited datasets. ```php // ... use Spatie\EventSourcing\StoredEvents\Models\EloquentStoredEvent; class EarningsForProductAndPeriod extends EventQuery { public function __construct( private Period $period, private Collection $products ) { EloquentStoredEvent::query() // We're only interested in `OrderCreated` events ->whereEvent(OrderCreated::class) // And we only need events within a given period ->whereDate( 'created_at', '>=' , $this->period->getStart() ) ->whereDate( 'created_at', '<=' , $this->period->getEnd() ) ->each( fn (EloquentStoredEvent $event) => $this->apply($event->toStoredEvent()) ); } } ``` -------------------------------- ### Replay Events for Projector (Artisan Command) Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md Replays all stored events to the specified projector, updating projections based on historical data. This is useful after creating or modifying projectors. ```bash php artisan event-sourcing:replay App\Projectors\TransactionCountProjector ``` -------------------------------- ### Basic Reactor with Event Handling Method Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-reactors/creating-and-configuring-reactors.md A simple reactor class demonstrating how to define a method that handles a specific event. The method name is derived from the event type, and dependencies can be injected. ```php namespace App\Reactors; class MyReactor { public function onEventHappened(EventHappened $event) { } } ``` -------------------------------- ### Add metadata to specific events via a projector (PHP) Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/storing-metadata.md This example shows how to inject metadata into a StoredEvent object within a projector's event handler. The StoredEvent instance is passed to the handler, allowing modification of its `meta_data` property before it is updated in the repository. This method is useful for conditional metadata addition and avoids replaying the event if necessary. ```php namespace App\Projectors; use Spatie\EventSourcing\EventHandlers\Projectors\Projector; use Spatie\EventSourcing\Models\StoredEvent; use Spatie\EventSourcing\Facades\Projectionist; use App\Events\MoneyAdded; use Spatie\EventSourcing\Repositories\StoredEventRepository; class MetaDataProjector extends Projector { /* * Here you can specify which event should trigger which method. */ public array $handlesEvents = [ MoneyAdded::class => 'onMoneyAdded', ]; public function onMoneyAdded(StoredEvent $storedEvent, StoredEventRepository $repository) { if (! Projectionist::isReplaying()) { $storedEvent->meta_data['user_id'] = auth()->user()->id; $repository->update($storedEvent); } // ... } } ``` -------------------------------- ### Instantiate and Use an Event Query Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/event-queries.md This PHP code demonstrates how to create an instance of the EarningsForProductAndPeriod event query with a specific period and collection of products. It then calls the `totalPrice` method to retrieve the calculated earnings. ```php $report = new EarningsForProductAndPeriod( Period::make('2021-01-01', '2021-02-01'), $collectionOfProducts, ); $report->totalPrice(); ``` -------------------------------- ### Create Multiple Accounts Using Factories in Laravel Tests Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md This snippet demonstrates how to create multiple 'Account' models using factories within a Laravel test. It iterates through the created accounts to assert the count and type of accounts associated with the authenticated user. This approach is suitable for simpler test data generation. ```php public function test_can_have_many_accounts() { Account::factory()->times(5)->make()->each(function($account) { Account::createWithAttributes($account->toArray()); }); $this->assertCount(5, auth()->user()->accounts); $this->assertInstanceOf(Account::class, auth()->user()->accounts()->first()); } ``` -------------------------------- ### Basic Projector Implementation Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/creating-and-configuring-projectors.md This PHP code shows a fundamental structure of a projector class extending `Spatie\EventSourcing\EventHandlers\Projectors\Projector`. It includes a method `onEventHappened` that is triggered when a specific event occurs. Dependencies can be injected into these handler methods. ```php namespace App\Projectors; use Spatie\EventSourcing\EventHandlers\Projectors\Projector; class MyProjector extends Projector { public function onEventHappened(EventHappened $event) { // do some work } } ``` -------------------------------- ### Inferred Event to Method Mapping in Reactor Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-reactors/creating-and-configuring-reactors.md Simplify event handling by allowing the package to infer the method name based on the event class. When an event class is listed directly in `$handlesEvents`, the package looks for a method named `on` followed by the event's class name. ```php // in a reactor // ... protected array $handlesEvents = [ /* * If this event is passed to the reactor, the `onMoneyAdded` method will be called. */ MoneyAdded::class, ]; ``` -------------------------------- ### Create BigAmountAddedReactor - PHP Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-reactors/writing-your-first-reactor.md This PHP code defines a reactor that listens for the 'MoneyAdded' event. If the added amount is greater than or equal to 900, it retrieves the account details and sends a 'BigAmountAddedMail' to the bank director. It implements `ShouldQueue` to ensure the email sending process is handled asynchronously via Laravel's queue system. ```PHP namespace App\Reactors; use App\Account; use App\Events\MoneyAdded; use App\Mail\BigAmountAddedMail; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Support\Facades\Mail; use Spatie\EventSourcing\EventHandlers\Reactors\Reactor; class BigAmountAddedReactor extends Reactor implements ShouldQueue { public function onMoneyAdded(MoneyAdded $event) { if ($event->amount < 900) { return; } $account = Account::uuid($event->accountUuid); Mail::to('director@bank.com')->send(new BigAmountAddedMail($account, $event->amount)); } } ``` -------------------------------- ### Create an Aggregate Root using Artisan Command Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-aggregates/writing-your-first-aggregate.md This command generates a new aggregate root class in the application. It serves as the foundation for managing state and recording events. ```php php artisan make:aggregate AccountAggregate ``` -------------------------------- ### Register Projector (PHP) Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md Registers the 'TransactionCountProjector' with the Projectionist, allowing it to handle incoming events. This typically goes into a service provider. ```php // in a service provider of your own Projectionist::addProjector(TransactionCountProjector::class); ``` -------------------------------- ### Replay All Events - Artisan Command Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/replaying-events.md This artisan command replays all stored events to all projectors configured in the projectionist. It's a foundational command for re-syncing projector states. ```bash php artisan event-sourcing:replay ``` -------------------------------- ### Apply Money Events in Aggregate Root (PHP) Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-aggregates/writing-your-first-aggregate.md Implements methods to apply MoneyAdded and MoneySubtracted events to an aggregate root, updating the internal balance. This allows the aggregate to maintain its state based on historical financial transactions. It assumes the existence of MoneyAdded and MoneySubtracted event classes. ```php private $balance = 0; //... public function applyMoneyAdded(MoneyAdded $event) { $this->balance += $event->amount; } public function applyMoneySubtracted(MoneySubtracted $event) { $this->balance -= $event->amount; } ``` -------------------------------- ### Create Transactions Count Table Migration (PHP) Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md Defines the database migration for the 'transactions_count' table, which will store the count of transactions for each account. It includes columns for account UUID, transaction count, and timestamps. ```php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateTransactionsCountTable extends Migration { public function up() { Schema::create('transactions_count', function (Blueprint $table) { $table->increments('id'); $table->string('account_uuid'); $table->integer('count')->default(0); $table->timestamps(); }); } } ``` -------------------------------- ### Map Events to Projector Methods with $handlesEvents Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/creating-and-configuring-projectors.md This configuration explicitly maps event class names to specific method names within a projector. It's useful when your method names don't follow the default naming convention. No external dependencies are required beyond the package itself. ```php // in a projector // ... protected array $handlesEvents = [ MoneyAdded::class => 'onMoneyAdded', ]; ``` -------------------------------- ### Add Multiple Projectors via Projectionist Facade Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/adding-and-removing-projectors-and-reactors.md This code snippet shows how to add multiple projectors simultaneously using the `Projectionist` facade. It accepts an array of projector class names. This method is efficient for adding several projectors at once, simplifying configuration. ```php Projectionist::addProjectors([ AccountBalanceProjector::class, TransactionCountProjector::class, ]); ``` -------------------------------- ### Infer Event Handling Methods in Projectors Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/creating-and-configuring-projectors.md This shorter syntax for the `$handlesEvents` property allows the package to automatically infer the handling method name. It assumes a method named 'on' followed by the event's class name exists. This reduces boilerplate code when following the convention. ```php // in a projector // ... protected array $handlesEvents = [ /* * If this event is passed to the projector, the `onMoneyAdded` method will be called. */ MoneyAdded::class, ]; ``` -------------------------------- ### TransactionCountProjector Class (PHP) Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md A projector class that listens for 'MoneyAdded' and 'MoneySubtracted' events. It increments the transaction count for the corresponding account in the 'transactions_count' table. ```php namespace App\Projectors; use App\Events\MoneyAdded; use App\Events\MoneySubtracted; use App\TransactionCount; use Spatie\EventSourcing\Models\StoredEvent; use Spatie\EventSourcing\EventHandlers\Projectors\Projector; class TransactionCountProjector extends Projector { public function onMoneyAdded(MoneyAdded $event) { $transactionCounter = TransactionCount::firstOrCreate(['account_uuid' => $event->accountUuid]); $transactionCounter->count += 1; $transactionCounter->writeable()->save(); } public function onMoneySubtracted(MoneySubtracted $event) { $transactionCounter = TransactionCount::firstOrCreate(['account_uuid' => $event->accountUuid]); $transactionCounter->count += 1; $transactionCounter->writeable()->save(); } } ``` -------------------------------- ### Retrieve Aggregate Root Instance Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-aggregates/creating-and-configuring-aggregates.md Demonstrates different ways to retrieve an aggregate root instance. This includes using a static method and loading an aggregate manually within a class. The retrieved aggregate will have its state populated by replaying past events. ```php MyAggregate::retrieve($uuid) ``` ```php $myAggregate = new MyAggregate(); $myAggregate->loadUuid($uuid); ``` ```php public function handle(MyAggregate $aggregate) { $aggregate->load($uuid); // ... } ``` -------------------------------- ### Projector onStartingEventReplay Method - PHP Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/replaying-events.md A method in a projector that is executed right before the first event is replayed. Useful for tasks like truncating tables before re-indexing. ```php public function onStartingEventReplay() { Account::truncate(); } ``` -------------------------------- ### Add Single Projector via Projectionist Facade Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/adding-and-removing-projectors-and-reactors.md This code demonstrates how to add a single projector to the event handling system using the `Projectionist` facade. It takes the class name of the projector as an argument. This is useful for integrating specific projectors into your application's event sourcing workflow. ```php Projectionist::addProjector(TransactionCountProjector::class); ``` -------------------------------- ### Record Account Limit Hit Event (PHP) Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-aggregates/writing-your-first-aggregate.md Modifies the subtractAmount method to record an AccountLimitHit event when funds are insufficient. It includes a call to $this->persist() to ensure the new event is saved, and then throws an exception. This is useful for tracking when account limits are reached. ```php public function subtractAmount(int $amount) { if (! $this->hasSufficientFundsToSubtractAmount($amount)) { $this->recordThat(new AccountLimitHit($amount)); // persist the aggregate so the record event gets persisted $this->persist(); throw CouldNotSubtractMoney::notEnoughFunds($amount); } $this->recordThat(new MoneySubtracted($amount)); } ``` -------------------------------- ### Replay Events to Specific Projectors - Artisan Command Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/replaying-events.md Allows replaying events to one or more specific projectors by providing their class names as arguments. This is useful for targeted re-synchronization. ```bash php artisan event-sourcing:replay App\Projectors\AccountBalanceProjector php artisan event-sourcing:replay App\Projectors\AccountBalanceProjector App\Projectors\AnotherProjector ``` -------------------------------- ### Projector Using Dedicated Event Handler Class Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/creating-and-configuring-projectors.md This PHP code demonstrates how to delegate event handling to a separate class instead of a method directly within the projector. The `$handlesEvents` property maps the event class to the handler class. This promotes better organization and reusability of event handling logic. ```php // in a projector // ... protected array $handlesEvents = [ /* * If this event is passed to the projector, the `AddMoneyToAccount` class will be called. */ MoneyAdded::class => AddMoneyToAccount::class, ]; ``` -------------------------------- ### Specify Queue for Event Processing Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/advanced-usage/preparing-events.md Shows how to override the default queue for processing events. By adding a `queue` property to the event class, you can direct specific events to an alternative queue for projectors and reactors. ```php namespace App\Events; use Spatie\EventSourcing\StoredEvents\ShouldBeStored; class MyEvent extends ShouldBeStored { public $queue = 'alternativeQueue'; ... } ``` -------------------------------- ### Define and Record Events within Aggregate Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-aggregates/creating-and-configuring-aggregates.md Shows how to define a custom event class that implements ShouldBeStored and how to record this event within an aggregate root using the `recordThat` method. Recording an event applies it immediately to the aggregate's state. ```php use Spatie\EventSourcing\StoredEvents\ShouldBeStored; class MoneyAdded extends ShouldBeStored { /** @var int */ private $amount public function __construct(int $amount) { $this->amount = $amount; } } ``` ```php // somewhere inside your aggregate public function addMoney(int $amount) { $this->recordThat(new MoneyAdded($amount)); } ``` -------------------------------- ### Recording Events in an Aggregate Root (PHP) Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/getting-familiar-with-event-sourcing/using-aggregates-to-make-decisions-based-on-the-past.md Demonstrates how to record new events within an aggregate root. This is typically done within methods that represent actions or commands. The `recordThat()` method is used to store the event, which will later be persisted. ```php public function applyMoneyWasSent(MoneyWasSent $moneyWasSent): void { $this->recordThat(new MoneySent(...)); } ``` -------------------------------- ### Propose Loan After Multiple Limit Hits (PHP) Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-aggregates/writing-your-first-aggregate.md Enhances the subtractAmount method to track the number of times the account limit is hit. If the limit is hit three times (accountLimitHitCount reaches 3), a LoanProposed event is recorded in addition to the AccountLimitHit event. Assumes accountLimitHitCount is an instance variable and applyAccountLimitHit increments it. ```php private $accountLimitHitCount = 0; // we need to add this method to count the amount of this the limit was hit public function applyAccountLimitHit() { $this->accountLimitHitCount++; } public function subtractAmount(int $amount) { if (! $this->hasSufficientFundsToSubtractAmount($amount)) { $this->recordThat(new AccountLimitHit($amount)); if ($this->accountLimitHitCount === 3) { $this->recordThat(new LoanProposed()); } // persist the aggregate so the record events gets persisted $this->persist(); throw CouldNotSubtractMoney::notEnoughFunds($amount); } $this->recordThat(new MoneySubtracted($amount)); } ``` -------------------------------- ### TransactionCount Model (PHP) Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/writing-your-first-projector.md Represents the 'transactions_count' table in the database. This Eloquent model is used by the projector to store and retrieve transaction counts per account. ```php namespace App; use Spatie\EventSourcing\Projections\Projection; class TransactionCount extends Projection { protected $table = 'transactions_count'; protected $guarded = []; } ``` -------------------------------- ### Explicit Event to Method Mapping in Reactor Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-reactors/creating-and-configuring-reactors.md Define explicit mappings between event classes and their corresponding handling methods within a reactor using the `$handlesEvents` property. This provides clear control over event processing logic. ```php namespace App\Reactors; use App\Events\MoneyAdded; class BigAmountAddedReactor { /* * Here you can specify which event should trigger which method. */ protected array $handlesEvents = [ MoneyAdded::class => 'onMoneyAdded', ]; public function onMoneyAdded(MoneyAdded $event) { // do some work } } ``` -------------------------------- ### Register Projectors in ServiceProvider Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/creating-and-configuring-projectors.md This PHP code snippet demonstrates how to manually register projectors within a Laravel ServiceProvider. It shows how to add single projectors or multiple projectors at once using the `Projectionist` facade. This is an alternative to the package's default automatic registration. ```php namespace App\Providers; use App\Projectors\AccountBalanceProjector; use Illuminate\Support\ServiceProvider; use Spatie\EventSourcing\Facades\Projectionist; class EventSourcingServiceProvider extends ServiceProvider { public function register() { // adding a single projector Projectionist::addProjector(AccountBalanceProjector::class); // you can also add multiple projectors in one go Projectionist::addProjectors([ AnotherProjector::class, YetAnotherProjector::class, ]); } } ``` -------------------------------- ### Create Aggregate Snapshot Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-aggregates/snapshots.md Demonstrates how to create a snapshot for an aggregate root. This improves performance by reducing the number of events that need to be loaded when retrieving the aggregate. The snapshot stores the aggregate's state at its current version. ```php $myAggregate = MyAggregate::retrieve($uuid); $myAggregate->snapshot(); ``` -------------------------------- ### Single Event Handling with __invoke Method Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-reactors/creating-and-configuring-reactors.md Handle a single specific event by defining the event class in the `$handleEvent` property and implementing the `__invoke` method within the reactor. This is a concise way to process a sole event type. ```php // in a reactor // ... protected $handleEvent = MoneyAdded::class, public function __invoke(MoneyAdded $event) { } ``` -------------------------------- ### Create Projector Artisan Command Source: https://github.com/spatie/laravel-event-sourcing/blob/main/docs/using-projectors/creating-and-configuring-projectors.md This command generates a new projector class in the application's `app\Projectors` directory. Projectors are responsible for listening to events and performing actions based on them. The generated class will have a basic structure ready for event handling. ```bash php artisan make:projector AccountBalanceProjector ```