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