diff --git a/src/Attribute/Subscribe.php b/src/Attribute/Subscribe.php index 78fc19415..26c33b6b4 100644 --- a/src/Attribute/Subscribe.php +++ b/src/Attribute/Subscribe.php @@ -9,7 +9,9 @@ #[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] final class Subscribe { - /** @param class-string $eventClass */ + public const ALL = '*'; + + /** @param class-string|'*' $eventClass */ public function __construct( public readonly string $eventClass, ) { diff --git a/src/EventBus/AttributeListenerProvider.php b/src/EventBus/AttributeListenerProvider.php index 06a732baa..4ce2fcb6a 100644 --- a/src/EventBus/AttributeListenerProvider.php +++ b/src/EventBus/AttributeListenerProvider.php @@ -7,6 +7,8 @@ use Patchlevel\EventSourcing\Attribute\Subscribe; use ReflectionClass; +use function array_merge; + final class AttributeListenerProvider implements ListenerProvider { /** @var array>|null */ @@ -22,7 +24,10 @@ public function __construct( public function listenersForEvent(object $event): iterable { if ($this->subscribeMethods !== null) { - return $this->subscribeMethods[$event::class] ?? []; + return array_merge( + $this->subscribeMethods[$event::class] ?? [], + $this->subscribeMethods['*'] ?? [], + ); } $this->subscribeMethods = []; @@ -45,6 +50,9 @@ public function listenersForEvent(object $event): iterable } } - return $this->subscribeMethods[$event::class] ?? []; + return array_merge( + $this->subscribeMethods[$event::class] ?? [], + $this->subscribeMethods['*'] ?? [], + ); } } diff --git a/src/Metadata/Projector/AttributeProjectorMetadataFactory.php b/src/Metadata/Projector/AttributeProjectorMetadataFactory.php index aa0c2a72b..12a4b39af 100644 --- a/src/Metadata/Projector/AttributeProjectorMetadataFactory.php +++ b/src/Metadata/Projector/AttributeProjectorMetadataFactory.php @@ -47,6 +47,13 @@ public function metadata(string $projector): ProjectorMetadata $instance = $attribute->newInstance(); $eventClass = $instance->eventClass; + if ($eventClass === '*') { + throw new SubscribeAllNotSupported( + $projector, + $method->getName(), + ); + } + if (array_key_exists($eventClass, $subscribeMethods)) { throw new DuplicateSubscribeMethod( $projector, diff --git a/src/Metadata/Projector/SubscribeAllNotSupported.php b/src/Metadata/Projector/SubscribeAllNotSupported.php new file mode 100644 index 000000000..a5850e0f8 --- /dev/null +++ b/src/Metadata/Projector/SubscribeAllNotSupported.php @@ -0,0 +1,24 @@ +client->send($message); - } - } catch (SendingFailed) { - // to nothing - } - - $this->eventBus->dispatch(...$messages); - } -} diff --git a/src/WatchServer/WatchListener.php b/src/WatchServer/WatchListener.php new file mode 100644 index 000000000..8ed3e70bf --- /dev/null +++ b/src/WatchServer/WatchListener.php @@ -0,0 +1,26 @@ +client->send($message); + } catch (SendingFailed) { + // to nothing + } + } +} diff --git a/tests/Unit/EventBus/AttributeListenerProviderTest.php b/tests/Unit/EventBus/AttributeListenerProviderTest.php index 3138f63c2..6ae4bfe1d 100644 --- a/tests/Unit/EventBus/AttributeListenerProviderTest.php +++ b/tests/Unit/EventBus/AttributeListenerProviderTest.php @@ -112,4 +112,54 @@ public function __invoke(Message $message): void new ListenerDescriptor($listener2->__invoke(...)), ], $listeners); } + + public function testSubscribeAll(): void + { + $listener = new class { + #[Subscribe('*')] + public function __invoke(Message $message): void + { + } + }; + + $event = new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ); + + $eventBus = new AttributeListenerProvider([$listener]); + $listeners = $eventBus->listenersForEvent($event); + + self::assertEquals([ + new ListenerDescriptor($listener->__invoke(...)), + ], $listeners); + } + + public function testMixedSubscribeTypes(): void + { + $listener = new class { + #[Subscribe('*')] + public function foo(Message $message): void + { + } + + #[Subscribe(ProfileCreated::class)] + public function bar(Message $message): void + { + } + }; + + $event = new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ); + + $eventBus = new AttributeListenerProvider([$listener]); + $listeners = $eventBus->listenersForEvent($event); + + self::assertEquals([ + new ListenerDescriptor($listener->bar(...)), + new ListenerDescriptor($listener->foo(...)), + ], $listeners); + } } diff --git a/tests/Unit/WatchServer/WatchEventBusWrapperTest.php b/tests/Unit/WatchServer/WatchListenerTest.php similarity index 54% rename from tests/Unit/WatchServer/WatchEventBusWrapperTest.php rename to tests/Unit/WatchServer/WatchListenerTest.php index b11b4595a..c84deaeba 100644 --- a/tests/Unit/WatchServer/WatchEventBusWrapperTest.php +++ b/tests/Unit/WatchServer/WatchListenerTest.php @@ -4,54 +4,39 @@ namespace Patchlevel\EventSourcing\Tests\Unit\WatchServer; -use Patchlevel\EventSourcing\EventBus\EventBus; use Patchlevel\EventSourcing\EventBus\Message; use Patchlevel\EventSourcing\Tests\Unit\Fixture\ProfileId; use Patchlevel\EventSourcing\Tests\Unit\Fixture\ProfileVisited; use Patchlevel\EventSourcing\WatchServer\SendingFailed; -use Patchlevel\EventSourcing\WatchServer\WatchEventBusWrapper; +use Patchlevel\EventSourcing\WatchServer\WatchListener; use Patchlevel\EventSourcing\WatchServer\WatchServerClient; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; -/** @covers \Patchlevel\EventSourcing\WatchServer\WatchEventBusWrapper */ -final class WatchEventBusWrapperTest extends TestCase +/** @covers \Patchlevel\EventSourcing\WatchServer\WatchListener */ +final class WatchListenerTest extends TestCase { use ProphecyTrait; - public function testWrapper(): void + public function testListener(): void { $message = new Message(new ProfileVisited(ProfileId::fromString('1'))); - $eventBus = $this->prophesize(EventBus::class); - $eventBus->dispatch($message)->shouldBeCalled(); - $client = $this->prophesize(WatchServerClient::class); $client->send($message)->shouldBeCalled(); - $wrapper = new WatchEventBusWrapper( - $eventBus->reveal(), - $client->reveal(), - ); - - $wrapper->dispatch($message); + $listener = new WatchListener($client->reveal()); + $listener->__invoke($message); } public function testIgnoreErrors(): void { $message = new Message(new ProfileVisited(ProfileId::fromString('1'))); - $eventBus = $this->prophesize(EventBus::class); - $eventBus->dispatch($message)->shouldBeCalled(); - $client = $this->prophesize(WatchServerClient::class); $client->send($message)->shouldBeCalled()->willThrow(SendingFailed::class); - $wrapper = new WatchEventBusWrapper( - $eventBus->reveal(), - $client->reveal(), - ); - - $wrapper->dispatch($message); + $listener = new WatchListener($client->reveal()); + $listener->__invoke($message); } }