diff --git a/baseline.xml b/baseline.xml index ef1f03ec..7e58717d 100644 --- a/baseline.xml +++ b/baseline.xml @@ -73,7 +73,7 @@ $method $method - $subscribeMethod + $method diff --git a/src/Metadata/Projector/AttributeProjectorMetadataFactory.php b/src/Metadata/Projector/AttributeProjectorMetadataFactory.php index b2161ff2..5bee3135 100644 --- a/src/Metadata/Projector/AttributeProjectorMetadataFactory.php +++ b/src/Metadata/Projector/AttributeProjectorMetadataFactory.php @@ -47,23 +47,7 @@ public function metadata(string $projector): ProjectorMetadata $instance = $attribute->newInstance(); $eventClass = $instance->eventClass; - if ($eventClass === Subscribe::ALL) { - throw new SubscribeAllNotSupported( - $projector, - $method->getName(), - ); - } - - if (array_key_exists($eventClass, $subscribeMethods)) { - throw new DuplicateSubscribeMethod( - $projector, - $eventClass, - $subscribeMethods[$eventClass], - $method->getName(), - ); - } - - $subscribeMethods[$eventClass] = $method->getName(); + $subscribeMethods[$eventClass][] = $method->getName(); } if ($method->getAttributes(Setup::class)) { diff --git a/src/Metadata/Projector/DuplicateSubscribeMethod.php b/src/Metadata/Projector/DuplicateSubscribeMethod.php deleted file mode 100644 index 8896c478..00000000 --- a/src/Metadata/Projector/DuplicateSubscribeMethod.php +++ /dev/null @@ -1,29 +0,0 @@ - */ + /** @var array> */ public readonly array $subscribeMethods = [], public readonly string|null $setupMethod = null, public readonly string|null $teardownMethod = null, diff --git a/src/Metadata/Projector/SubscribeAllNotSupported.php b/src/Metadata/Projector/SubscribeAllNotSupported.php deleted file mode 100644 index a5850e0f..00000000 --- a/src/Metadata/Projector/SubscribeAllNotSupported.php +++ /dev/null @@ -1,24 +0,0 @@ -projectorResolver->resolveSubscribeMethod($projector, $message); + $subscribeMethods = $this->projectorResolver->resolveSubscribeMethods($projector, $message); - if (!$subscribeMethod) { - continue; + foreach ($subscribeMethods as $subscribeMethod) { + $subscribeMethod($message); } - - $subscribeMethod($message); } } } diff --git a/src/Pipeline/Target/ProjectorTarget.php b/src/Pipeline/Target/ProjectorTarget.php index 4ed1c253..49ce5927 100644 --- a/src/Pipeline/Target/ProjectorTarget.php +++ b/src/Pipeline/Target/ProjectorTarget.php @@ -19,13 +19,11 @@ public function __construct( public function save(Message ...$messages): void { foreach ($messages as $message) { - $subscribeMethod = $this->projectorResolver->resolveSubscribeMethod($this->projector, $message); + $subscribeMethods = $this->projectorResolver->resolveSubscribeMethods($this->projector, $message); - if (!$subscribeMethod) { - continue; + foreach ($subscribeMethods as $subscribeMethod) { + $subscribeMethod($message); } - - $subscribeMethod($message); } } } diff --git a/src/Projection/Projectionist/DefaultProjectionist.php b/src/Projection/Projectionist/DefaultProjectionist.php index 955fbf69..3a7b94c1 100644 --- a/src/Projection/Projectionist/DefaultProjectionist.php +++ b/src/Projection/Projectionist/DefaultProjectionist.php @@ -461,15 +461,15 @@ private function handleMessage(int $index, Message $message, Projection $project throw ProjectorNotFound::forProjectionId($projection->id()); } - $subscribeMethod = $this->projectorResolver->resolveSubscribeMethod($projector, $message); + $subscribeMethods = $this->projectorResolver->resolveSubscribeMethods($projector, $message); - if (!$subscribeMethod) { + if ($subscribeMethods === []) { $projection->changePosition($index); $this->projectionStore->save($projection); $this->logger?->debug( sprintf( - 'Projectionist: Projector "%s" for "%s" has no subscribe method for "%s", continue.', + 'Projectionist: Projector "%s" for "%s" has no subscribe methods for "%s", continue.', $projector::class, $projection->id()->toString(), $message->event()::class, @@ -480,7 +480,9 @@ private function handleMessage(int $index, Message $message, Projection $project } try { - $subscribeMethod($message); + foreach ($subscribeMethods as $subscribeMethod) { + $subscribeMethod($message); + } } catch (Throwable $e) { $this->logger?->error( sprintf( diff --git a/src/Projection/Projector/MetadataProjectorResolver.php b/src/Projection/Projector/MetadataProjectorResolver.php index 54661576..a6bfbe3e 100644 --- a/src/Projection/Projector/MetadataProjectorResolver.php +++ b/src/Projection/Projector/MetadataProjectorResolver.php @@ -5,11 +5,13 @@ namespace Patchlevel\EventSourcing\Projection\Projector; use Closure; +use Patchlevel\EventSourcing\Attribute\Subscribe; use Patchlevel\EventSourcing\EventBus\Message; use Patchlevel\EventSourcing\Metadata\Projector\AttributeProjectorMetadataFactory; use Patchlevel\EventSourcing\Metadata\Projector\ProjectorMetadataFactory; -use function array_key_exists; +use function array_map; +use function array_merge; final class MetadataProjectorResolver implements ProjectorResolver { @@ -42,18 +44,21 @@ public function resolveTeardownMethod(object $projector): Closure|null return $projector->$method(...); } - public function resolveSubscribeMethod(object $projector, Message $message): Closure|null + /** @return iterable */ + public function resolveSubscribeMethods(object $projector, Message $message): iterable { $event = $message->event(); $metadata = $this->metadataFactory->metadata($projector::class); - if (!array_key_exists($event::class, $metadata->subscribeMethods)) { - return null; - } - - $subscribeMethod = $metadata->subscribeMethods[$event::class]; + $methods = array_merge( + $metadata->subscribeMethods[$event::class] ?? [], + $metadata->subscribeMethods[Subscribe::ALL] ?? [], + ); - return $projector->$subscribeMethod(...); + return array_map( + static fn (string $method) => $projector->$method(...), + $methods, + ); } public function projectorId(object $projector): ProjectorId diff --git a/src/Projection/Projector/ProjectorResolver.php b/src/Projection/Projector/ProjectorResolver.php index 414f8c92..a3a66687 100644 --- a/src/Projection/Projector/ProjectorResolver.php +++ b/src/Projection/Projector/ProjectorResolver.php @@ -13,7 +13,8 @@ public function resolveSetupMethod(object $projector): Closure|null; public function resolveTeardownMethod(object $projector): Closure|null; - public function resolveSubscribeMethod(object $projector, Message $message): Closure|null; + /** @return iterable */ + public function resolveSubscribeMethods(object $projector, Message $message): iterable; public function projectorId(object $projector): ProjectorId; } diff --git a/tests/Unit/Metadata/Projector/AttributeProjectorMetadataFactoryTest.php b/tests/Unit/Metadata/Projector/AttributeProjectorMetadataFactoryTest.php index eb176c34..5d57e270 100644 --- a/tests/Unit/Metadata/Projector/AttributeProjectorMetadataFactoryTest.php +++ b/tests/Unit/Metadata/Projector/AttributeProjectorMetadataFactoryTest.php @@ -70,7 +70,7 @@ public function drop(): void $metadata = $metadataFactory->metadata($projection::class); self::assertEquals( - [ProfileVisited::class => 'handle'], + [ProfileVisited::class => ['handle']], $metadata->subscribeMethods, ); @@ -94,8 +94,29 @@ public function handle(): void self::assertEquals( [ - ProfileVisited::class => 'handle', - ProfileCreated::class => 'handle', + ProfileVisited::class => ['handle'], + ProfileCreated::class => ['handle'], + ], + $metadata->subscribeMethods, + ); + } + + public function testSubscribeAll(): void + { + $projection = new #[Projector('foo', 1)] + class { + #[Subscribe(Subscribe::ALL)] + public function handle(): void + { + } + }; + + $metadataFactory = new AttributeProjectorMetadataFactory(); + $metadata = $metadataFactory->metadata($projection::class); + + self::assertEquals( + [ + '*' => ['handle'], ], $metadata->subscribeMethods, ); diff --git a/tests/Unit/Pipeline/Target/ProjectorRepositoryTargetTest.php b/tests/Unit/Pipeline/Target/ProjectorRepositoryTargetTest.php index ca5b7ac6..2adff0b7 100644 --- a/tests/Unit/Pipeline/Target/ProjectorRepositoryTargetTest.php +++ b/tests/Unit/Pipeline/Target/ProjectorRepositoryTargetTest.php @@ -38,7 +38,7 @@ public function __invoke(Message $message): void $projectorRepository->projectors()->shouldBeCalledOnce()->willReturn([$projector]); $projectorResolver = $this->prophesize(ProjectorResolver::class); - $projectorResolver->resolveSubscribeMethod($projector, $message)->shouldBeCalledOnce()->willReturn($projector(...)); + $projectorResolver->resolveSubscribeMethods($projector, $message)->shouldBeCalledOnce()->willReturn([$projector(...)]); $projectorRepositoryTarget = new ProjectorRepositoryTarget($projectorRepository->reveal(), $projectorResolver->reveal()); $projectorRepositoryTarget->save($message); @@ -65,7 +65,7 @@ public function __invoke(Message $message): void $projectorRepository->projectors()->shouldBeCalledOnce()->willReturn([$projector]); $projectorResolver = $this->prophesize(ProjectorResolver::class); - $projectorResolver->resolveSubscribeMethod($projector, $message)->shouldBeCalledOnce()->willReturn(null); + $projectorResolver->resolveSubscribeMethods($projector, $message)->shouldBeCalledOnce()->willReturn([]); $projectorRepositoryTarget = new ProjectorRepositoryTarget($projectorRepository->reveal(), $projectorResolver->reveal()); $projectorRepositoryTarget->save($message); diff --git a/tests/Unit/Pipeline/Target/ProjectorTargetTest.php b/tests/Unit/Pipeline/Target/ProjectorTargetTest.php index 20e86f78..7916b034 100644 --- a/tests/Unit/Pipeline/Target/ProjectorTargetTest.php +++ b/tests/Unit/Pipeline/Target/ProjectorTargetTest.php @@ -34,7 +34,7 @@ public function __invoke(Message $message): void }; $projectorResolver = $this->prophesize(ProjectorResolver::class); - $projectorResolver->resolveSubscribeMethod($projector, $message)->shouldBeCalledOnce()->willReturn($projector(...)); + $projectorResolver->resolveSubscribeMethods($projector, $message)->shouldBeCalledOnce()->willReturn([$projector(...)]); $projectorTarget = new ProjectorTarget($projector, $projectorResolver->reveal()); $projectorTarget->save($message); @@ -58,7 +58,7 @@ public function __invoke(Message $message): void }; $projectorResolver = $this->prophesize(ProjectorResolver::class); - $projectorResolver->resolveSubscribeMethod($projector, $message)->shouldBeCalledOnce()->willReturn(null); + $projectorResolver->resolveSubscribeMethods($projector, $message)->shouldBeCalledOnce()->willReturn([]); $projectorTarget = new ProjectorTarget($projector, $projectorResolver->reveal()); $projectorTarget->save($message); diff --git a/tests/Unit/Projection/Projectionist/DefaultProjectionistTest.php b/tests/Unit/Projection/Projectionist/DefaultProjectionistTest.php index dbc38874..ef26d6ce 100644 --- a/tests/Unit/Projection/Projectionist/DefaultProjectionistTest.php +++ b/tests/Unit/Projection/Projectionist/DefaultProjectionistTest.php @@ -81,7 +81,7 @@ class { $projectorResolver = $this->prophesize(ProjectorResolver::class); $projectorResolver->projectorId($projector)->willReturn($projectorId); $projectorResolver->resolveSetupMethod($projector)->willReturn(null); - $projectorResolver->resolveSubscribeMethod($projector, $message)->willReturn(null); + $projectorResolver->resolveSubscribeMethods($projector, $message)->willReturn([]); $projectionist = new DefaultProjectionist( $streamableStore->reveal(), @@ -131,7 +131,7 @@ public function handle(Message $message): void $projectorResolver = $this->prophesize(ProjectorResolver::class); $projectorResolver->resolveSetupMethod($projector)->willReturn($projector->create(...)); - $projectorResolver->resolveSubscribeMethod($projector, $message)->willReturn($projector->handle(...)); + $projectorResolver->resolveSubscribeMethods($projector, $message)->willReturn([$projector->handle(...)]); $projectorResolver->projectorId($projector)->willReturn($projectorId); $projectionist = new DefaultProjectionist( @@ -185,7 +185,7 @@ public function handle(Message $message): void $projectorResolver = $this->prophesize(ProjectorResolver::class); $projectorResolver->resolveSetupMethod($projector)->willReturn($projector->create(...)); - $projectorResolver->resolveSubscribeMethod($projector, $message)->willReturn($projector->handle(...)); + $projectorResolver->resolveSubscribeMethods($projector, $message)->willReturn([$projector->handle(...)]); $projectorResolver->projectorId($projector)->willReturn($projectorId); $projectionist = new DefaultProjectionist( @@ -246,7 +246,7 @@ public function handle(Message $message): void $projectorRepository->projectors()->willReturn([$projector1, $projector2])->shouldBeCalledOnce(); $projectorResolver = $this->prophesize(ProjectorResolver::class); - $projectorResolver->resolveSubscribeMethod($projector1, $message)->willReturn($projector1->handle(...)); + $projectorResolver->resolveSubscribeMethods($projector1, $message)->willReturn([$projector1->handle(...)]); $projectorResolver->projectorId($projector1)->willReturn($projectorId1); $projectorResolver->projectorId($projector2)->willReturn($projectorId2); @@ -354,8 +354,8 @@ public function handle(Message $message): void $projectorRepository->projectors()->willReturn([$projector])->shouldBeCalledOnce(); $projectorResolver = $this->prophesize(ProjectorResolver::class); - $projectorResolver->resolveSubscribeMethod($projector, $message1)->willReturn($projector->handle(...)); - $projectorResolver->resolveSubscribeMethod($projector, $message2)->willReturn($projector->handle(...)); + $projectorResolver->resolveSubscribeMethods($projector, $message1)->willReturn([$projector->handle(...)]); + $projectorResolver->resolveSubscribeMethods($projector, $message2)->willReturn([$projector->handle(...)]); $projectorResolver->projectorId($projector)->willReturn($projectorId); $projectionist = new DefaultProjectionist( @@ -401,7 +401,7 @@ public function handle(Message $message): void $projectorRepository->projectors()->willReturn([$projector])->shouldBeCalledOnce(); $projectorResolver = $this->prophesize(ProjectorResolver::class); - $projectorResolver->resolveSubscribeMethod($projector, $message)->willReturn($projector->handle(...)); + $projectorResolver->resolveSubscribeMethods($projector, $message)->willReturn([$projector->handle(...)]); $projectorResolver->projectorId($projector)->willReturn($projectorId); $projectionist = new DefaultProjectionist( @@ -449,7 +449,7 @@ public function handle(Message $message): void $projectorRepository->projectors()->willReturn([$projector])->shouldBeCalledOnce(); $projectorResolver = $this->prophesize(ProjectorResolver::class); - $projectorResolver->resolveSubscribeMethod($projector, $message1)->willReturn($projector->handle(...)); + $projectorResolver->resolveSubscribeMethods($projector, $message1)->willReturn([$projector->handle(...)]); $projectorResolver->projectorId($projector)->willReturn($projectorId); $projectionist = new DefaultProjectionist( @@ -508,7 +508,7 @@ public function handle(Message $message): void $projectorRepository->projectors()->willReturn([$projector1, $projector2])->shouldBeCalledOnce(); $projectorResolver = $this->prophesize(ProjectorResolver::class); - $projectorResolver->resolveSubscribeMethod($projector1, $message)->willReturn($projector1->handle(...)); + $projectorResolver->resolveSubscribeMethods($projector1, $message)->willReturn([$projector1->handle(...)]); $projectorResolver->projectorId($projector1)->willReturn($projectorId1); $projectorResolver->projectorId($projector2)->willReturn($projectorId2); @@ -557,7 +557,7 @@ public function handle(Message $message): void $projectorRepository->projectors()->willReturn([$projector])->shouldBeCalledOnce(); $projectorResolver = $this->prophesize(ProjectorResolver::class); - $projectorResolver->resolveSubscribeMethod($projector, $message)->willReturn($projector->handle(...)); + $projectorResolver->resolveSubscribeMethods($projector, $message)->willReturn([$projector->handle(...)]); $projectorResolver->projectorId($projector)->willReturn($projectorId); $projectionist = new DefaultProjectionist( @@ -664,8 +664,8 @@ public function handle(Message $message): void $projectorRepository->projectors()->willReturn([$projector])->shouldBeCalledOnce(); $projectorResolver = $this->prophesize(ProjectorResolver::class); - $projectorResolver->resolveSubscribeMethod($projector, $message1)->willReturn($projector->handle(...)); - $projectorResolver->resolveSubscribeMethod($projector, $message2)->willReturn($projector->handle(...)); + $projectorResolver->resolveSubscribeMethods($projector, $message1)->willReturn([$projector->handle(...)]); + $projectorResolver->resolveSubscribeMethods($projector, $message2)->willReturn([$projector->handle(...)]); $projectorResolver->projectorId($projector)->willReturn($projectorId); $projectionist = new DefaultProjectionist( diff --git a/tests/Unit/Projection/Projector/MetadataProjectorResolverTest.php b/tests/Unit/Projection/Projector/MetadataProjectorResolverTest.php index d8ebdf1a..5251dfee 100644 --- a/tests/Unit/Projection/Projector/MetadataProjectorResolverTest.php +++ b/tests/Unit/Projection/Projector/MetadataProjectorResolverTest.php @@ -23,12 +23,9 @@ public function testResolveHandleMethod(): void { $projection = new #[Projector('dummy')] class { - public static Message|null $handledMessage = null; - #[Subscribe(ProfileCreated::class)] public function handleProfileCreated(Message $message): void { - self::$handledMessage = $message; } }; @@ -40,13 +37,42 @@ public function handleProfileCreated(Message $message): void ); $resolver = new MetadataProjectorResolver(); - $result = $resolver->resolveSubscribeMethod($projection, $message); + $result = $resolver->resolveSubscribeMethods($projection, $message); - self::assertIsCallable($result); + self::assertEquals( + [ + $projection->handleProfileCreated(...), + ], + $result, + ); + } + + public function testResolveHandleAll(): void + { + $projection = new #[Projector('dummy')] + class { + #[Subscribe(Subscribe::ALL)] + public function handleProfileCreated(Message $message): void + { + } + }; - $result($message); + $message = new Message( + new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('profile@test.com'), + ), + ); + + $resolver = new MetadataProjectorResolver(); + $result = $resolver->resolveSubscribeMethods($projection, $message); - self::assertSame($message, $projection::$handledMessage); + self::assertEquals( + [ + $projection->handleProfileCreated(...), + ], + $result, + ); } public function testNotResolveHandleMethod(): void @@ -62,9 +88,9 @@ class { ); $resolver = new MetadataProjectorResolver(); - $result = $resolver->resolveSubscribeMethod($projection, $message); + $result = $resolver->resolveSubscribeMethods($projection, $message); - self::assertNull($result); + self::assertEmpty($result); } public function testResolveCreateMethod(): void