diff --git a/docs/pages/events.md b/docs/pages/events.md index c0a564219..4541319fb 100644 --- a/docs/pages/events.md +++ b/docs/pages/events.md @@ -42,6 +42,31 @@ final class ProfileCreated * `profile.name_changed` * `hotel.guest_checked_out` +## Alias + +You also have the option to set aliases for the events. +This can be useful when you want to rename events but still need to process the old ones. + +```php +use Patchlevel\EventSourcing\Attribute\Event; + +#[Event(name: 'profile.registered', aliases: ['profile.created'])] +final class ProfileRegistered +{ +} +``` +When saving, the name will always be used. However, when loading, aliases will also be taken into account. + +!!! note + + In the database, the name of the event is always stored, + allowing the class to be renamed without encountering any issues. + +!!! tip + + If you want to make significant changes to an event, + you can take a look at the [Upcaster](upcasting.md). + ## Serializer So that the events can be saved in the database, they must be serialized and deserialized. diff --git a/docs/pages/upcasting.md b/docs/pages/upcasting.md index 877bc2211..92a7b63a0 100644 --- a/docs/pages/upcasting.md +++ b/docs/pages/upcasting.md @@ -65,6 +65,10 @@ final class EventNameRenameUpcaster implements Upcaster } } ``` +!!! tip + + Events can also have [aliases](./events.md#alias). This is usually sufficient. + ## Configure After we have defined the upcasting rules, we also have to pass the whole thing to the serializer. diff --git a/src/Attribute/Event.php b/src/Attribute/Event.php index fdcafe900..3d8378e7c 100644 --- a/src/Attribute/Event.php +++ b/src/Attribute/Event.php @@ -11,6 +11,8 @@ final class Event { public function __construct( public readonly string $name, + /** @var list */ + public readonly array $aliases = [], ) { } } diff --git a/src/Metadata/Event/AttributeEventMetadataFactory.php b/src/Metadata/Event/AttributeEventMetadataFactory.php index 5245fb4ca..aeb4edf91 100644 --- a/src/Metadata/Event/AttributeEventMetadataFactory.php +++ b/src/Metadata/Event/AttributeEventMetadataFactory.php @@ -36,6 +36,7 @@ public function metadata(string $event): EventMetadata $this->eventMetadata[$event] = new EventMetadata( $eventAttribute->name, $this->splitStream($reflectionClass), + $eventAttribute->aliases, ); return $this->eventMetadata[$event]; diff --git a/src/Metadata/Event/AttributeEventRegistryFactory.php b/src/Metadata/Event/AttributeEventRegistryFactory.php index 0af0d56be..1828af8d1 100644 --- a/src/Metadata/Event/AttributeEventRegistryFactory.php +++ b/src/Metadata/Event/AttributeEventRegistryFactory.php @@ -28,13 +28,21 @@ public function create(array $paths): EventRegistry continue; } - $eventName = $attributes[0]->newInstance()->name; + $attribute = $attributes[0]->newInstance(); - if (array_key_exists($eventName, $result)) { - throw new EventAlreadyInRegistry($eventName); + if (array_key_exists($attribute->name, $result)) { + throw new EventAlreadyInRegistry($attribute->name); } - $result[$eventName] = $class; + $result[$attribute->name] = $class; + + foreach ($attribute->aliases as $alias) { + if (array_key_exists($alias, $result)) { + throw new EventAlreadyInRegistry($alias); + } + + $result[$alias] = $class; + } } return new EventRegistry($result); diff --git a/src/Metadata/Event/EventMetadata.php b/src/Metadata/Event/EventMetadata.php index e5064109b..74ae1cff2 100644 --- a/src/Metadata/Event/EventMetadata.php +++ b/src/Metadata/Event/EventMetadata.php @@ -9,6 +9,8 @@ final class EventMetadata public function __construct( public readonly string $name, public readonly bool $splitStream = false, + /** @var list */ + public readonly array $aliases = [], ) { } } diff --git a/src/Metadata/Event/EventRegistry.php b/src/Metadata/Event/EventRegistry.php index 2d16da168..bff60f727 100644 --- a/src/Metadata/Event/EventRegistry.php +++ b/src/Metadata/Event/EventRegistry.php @@ -6,6 +6,7 @@ use function array_flip; use function array_key_exists; +use function array_reverse; final class EventRegistry { @@ -19,7 +20,7 @@ final class EventRegistry public function __construct(array $eventNameToClassMap) { $this->nameToClassMap = $eventNameToClassMap; - $this->classToNameMap = array_flip($eventNameToClassMap); + $this->classToNameMap = array_flip(array_reverse($eventNameToClassMap)); } /** @param class-string $eventClass */ diff --git a/tests/Integration/BasicImplementation/Events/ProfileCreated.php b/tests/Integration/BasicImplementation/Events/ProfileCreated.php index 1536fbdeb..dad32583a 100644 --- a/tests/Integration/BasicImplementation/Events/ProfileCreated.php +++ b/tests/Integration/BasicImplementation/Events/ProfileCreated.php @@ -7,7 +7,7 @@ use Patchlevel\EventSourcing\Attribute\Event; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\ProfileId; -#[Event('profile.created')] +#[Event('profile.created', aliases: ['profile_was_created'])] final class ProfileCreated { public function __construct( diff --git a/tests/Unit/Metadata/Event/EventRegistryTest.php b/tests/Unit/Metadata/Event/EventRegistryTest.php index f8401d58f..ee7a71fee 100644 --- a/tests/Unit/Metadata/Event/EventRegistryTest.php +++ b/tests/Unit/Metadata/Event/EventRegistryTest.php @@ -33,6 +33,25 @@ public function testMapping(): void self::assertEquals([ProfileCreated::class => 'profile.created'], $registry->eventNames()); } + public function testMappingWithAliases(): void + { + $registry = new EventRegistry([ + 'profile.created' => ProfileCreated::class, + 'profile.created_alias' => ProfileCreated::class, + ]); + + self::assertTrue($registry->hasEventClass(ProfileCreated::class)); + self::assertTrue($registry->hasEventName('profile.created')); + self::assertEquals('profile.created', $registry->eventName(ProfileCreated::class)); + self::assertEquals(ProfileCreated::class, $registry->eventClass('profile.created')); + self::assertEquals(ProfileCreated::class, $registry->eventClass('profile.created_alias')); + self::assertEquals([ + 'profile.created' => ProfileCreated::class, + 'profile.created_alias' => ProfileCreated::class, + ], $registry->eventClasses()); + self::assertEquals([ProfileCreated::class => 'profile.created'], $registry->eventNames()); + } + public function testEventClassNotRegistered(): void { $this->expectException(EventClassNotRegistered::class);