diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dbec1a..a5874e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to `laravel-event-projector` will be documented in this file +## x.x.x - 2019-xx-xx +- Added `event_class_map` to alias your event classes which allows for refactoring after events have been fired + ## 2.6.3 - 2019-06-14 - fix warnings in console commands diff --git a/config/event-projector.php b/config/event-projector.php index 9ee875e..27c2765 100644 --- a/config/event-projector.php +++ b/config/event-projector.php @@ -55,6 +55,13 @@ */ 'stored_event_job' => \Spatie\EventProjector\HandleStoredEventJob::class, + /* + * Similar to Relation::morphMap() you can define which alias responds to which + * event class. This allows you to change the namespace or classnames + * 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 diff --git a/docs/advanced-usage/using-aliases-for-stored-event-classes.md b/docs/advanced-usage/using-aliases-for-stored-event-classes.md new file mode 100644 index 0000000..9d6e6f8 --- /dev/null +++ b/docs/advanced-usage/using-aliases-for-stored-event-classes.md @@ -0,0 +1,21 @@ +--- +title: Using aliases for stored event classes +weight: 8 +--- + +By default we store the `Event`'s FQCN in the database when storing the events. This prevents you from changing the name or the namespace of your event classes. + +To get around this you can define event class aliases in the `event-projector.php` config file: + +```php + /* + * Similar to Relation::morphMap() you can define which alias responds to which + * event class. This allows you to change the namespace or classnames + * of your events but still handle older events correctly. + */ + 'event_class_map' => [ + 'money_added' => MoneyAddedEvent::class, + ], +``` + +With this configuration, instead of saving `\App\Events\MoneyAddedEvent` in the database, we just store `money_added`, now you can change the event classname and namespace. Just make sure to also change the mapping! diff --git a/docs/installation-setup.md b/docs/installation-setup.md index 502deec..836283b 100644 --- a/docs/installation-setup.md +++ b/docs/installation-setup.md @@ -79,6 +79,13 @@ return [ * it should extend \Spatie\EventProjector\HandleDomainEventJob. */ 'stored_event_job' => \Spatie\EventProjector\HandleStoredEventJob::class, + + /* + * Similar to Relation::morphMap() you can define which alias responds to which + * event class. This allows you to change the namespace or classnames + * 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 diff --git a/src/Models/StoredEvent.php b/src/Models/StoredEvent.php index d6c2920..04432e4 100644 --- a/src/Models/StoredEvent.php +++ b/src/Models/StoredEvent.php @@ -4,6 +4,7 @@ use Exception; use Carbon\Carbon; +use Illuminate\Support\Arr; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Builder; use Spatie\EventProjector\ShouldBeStored; @@ -27,7 +28,7 @@ public static function createForEvent(ShouldBeStored $event, string $uuid = null { $storedEvent = new static(); $storedEvent->aggregate_uuid = $uuid; - $storedEvent->event_class = get_class($event); + $storedEvent->event_class = static::getEventClass(get_class($event)); $storedEvent->attributes['event_properties'] = app(EventSerializer::class)->serialize(clone $event); $storedEvent->meta_data = []; $storedEvent->created_at = Carbon::now(); @@ -37,6 +38,11 @@ public static function createForEvent(ShouldBeStored $event, string $uuid = null return $storedEvent; } + public function getEventClassAttribute(string $value): string + { + return static::getActualClassForEvent($value); + } + public function getEventAttribute(): ShouldBeStored { try { @@ -100,4 +106,20 @@ public static function store(ShouldBeStored $event, string $uuid = null): void { static::storeMany([$event], $uuid); } + + protected static function getEventClass(string $class): string + { + $map = config('event-projector.event_class_map', []); + + if (! empty($map) && in_array($class, $map)) { + return array_search($class, $map, true); + } + + return $class; + } + + protected static function getActualClassForEvent(string $class): string + { + return Arr::get(config('event-projector.event_class_map', []), $class, $class); + } } diff --git a/tests/EventSubscriberTest.php b/tests/EventSubscriberTest.php index c9b9cac..c6cc2b9 100644 --- a/tests/EventSubscriberTest.php +++ b/tests/EventSubscriberTest.php @@ -49,6 +49,27 @@ public function it_will_log_events_that_implement_ShouldBeStored() $this->assertEquals($this->account->id, $storedEvent->event->account->id); } + /** @test * */ + public function it_will_log_events_that_implement_ShouldBeStored_with_a_map() + { + $this->setConfig('event-projector.event_class_map', [ + 'money_added' => MoneyAddedEvent::class, + ]); + + event(new MoneyAddedEvent($this->account, 1234)); + + $this->assertCount(1, StoredEvent::get()); + + $storedEvent = StoredEvent::first(); + + $this->assertEquals(MoneyAddedEvent::class, $storedEvent->event_class); + $this->assertEquals('money_added', $storedEvent->getAttributes()['event_class']); + + $this->assertInstanceOf(MoneyAddedEvent::class, $storedEvent->event); + $this->assertEquals(1234, $storedEvent->event->amount); + $this->assertEquals($this->account->id, $storedEvent->event->account->id); + } + /** @test */ public function it_will_not_store_events_without_the_ShouldBeStored_interface() { diff --git a/tests/Models/StoredEventTest.php b/tests/Models/StoredEventTest.php index ccac818..75bc363 100644 --- a/tests/Models/StoredEventTest.php +++ b/tests/Models/StoredEventTest.php @@ -45,6 +45,19 @@ public function it_will_throw_a_human_readable_exception_when_the_event_couldnt_ StoredEvent::first()->event; } + /** @test * */ + public function it_will_store_the_alias_when_a_classname_is_found_in_the_event_class_map() + { + $this->setConfig('event-projector.event_class_map', [ + 'money_added' => MoneyAddedEvent::class, + ]); + + $this->fireEvents(); + + $this->assertEquals(MoneyAddedEvent::class, StoredEvent::first()->event_class); + $this->assertEquals('money_added', StoredEvent::first()->getAttributes()['event_class']); + } + public function fireEvents(int $number = 1, string $className = MoneyAddedEvent::class) { foreach (range(1, $number) as $i) {