From bf9fcc39cbc0ddc8e4fb2d720f58dc10e61abebd Mon Sep 17 00:00:00 2001 From: David Badura Date: Mon, 25 Mar 2024 16:47:12 +0100 Subject: [PATCH] add argument resolver --- baseline.xml | 26 +------ deptrac.yaml | 1 + .../AggregateIdArgumentResolver.php | 23 ++++++ .../Subscriber/ArgumentResolver/Argument.php | 14 ++++ .../ArgumentResolver/ArgumentResolver.php | 14 ++++ .../EventArgumentResolver.php | 23 ++++++ .../MessageArgumentResolver.php | 20 ++++++ .../RecordedOnArgumentResolver.php | 22 ++++++ .../Subscriber/MetadataSubscriberAccessor.php | 71 ++++++++++++++++++- .../MetadataSubscriberAccessorRepository.php | 7 +- .../Projection/ProfileProjector.php | 18 +---- .../BasicIntegrationTest.php | 12 ++-- .../SendEmailListener.php} | 4 +- .../Projection/ProfileProjector.php | 9 +-- ...tadataSubscriberAccessorRepositoryTest.php | 1 + .../MetadataSubscriberAccessorTest.php | 10 +++ 16 files changed, 219 insertions(+), 56 deletions(-) create mode 100644 src/Subscription/Subscriber/ArgumentResolver/AggregateIdArgumentResolver.php create mode 100644 src/Subscription/Subscriber/ArgumentResolver/Argument.php create mode 100644 src/Subscription/Subscriber/ArgumentResolver/ArgumentResolver.php create mode 100644 src/Subscription/Subscriber/ArgumentResolver/EventArgumentResolver.php create mode 100644 src/Subscription/Subscriber/ArgumentResolver/MessageArgumentResolver.php create mode 100644 src/Subscription/Subscriber/ArgumentResolver/RecordedOnArgumentResolver.php rename tests/Integration/BasicImplementation/{Processor/SendEmailProcessor.php => Listener/SendEmailListener.php} (90%) diff --git a/baseline.xml b/baseline.xml index 029540042..22ff0c1fe 100644 --- a/baseline.xml +++ b/baseline.xml @@ -115,15 +115,14 @@ + + + - - subscriber->$method(...)]]> - - @@ -134,7 +133,6 @@ - @@ -142,7 +140,6 @@ - @@ -151,7 +148,6 @@ - @@ -163,7 +159,6 @@ id]]> - @@ -171,7 +166,6 @@ - @@ -202,13 +196,6 @@ - - - - - - - @@ -291,13 +278,6 @@ - - - - - - - diff --git a/deptrac.yaml b/deptrac.yaml index 9f94d591b..95e5c60a1 100644 --- a/deptrac.yaml +++ b/deptrac.yaml @@ -143,6 +143,7 @@ deptrac: - Metadata - Subscription Subscription: + - Aggregate - Attribute - Clock - Message diff --git a/src/Subscription/Subscriber/ArgumentResolver/AggregateIdArgumentResolver.php b/src/Subscription/Subscriber/ArgumentResolver/AggregateIdArgumentResolver.php new file mode 100644 index 000000000..b80add007 --- /dev/null +++ b/src/Subscription/Subscriber/ArgumentResolver/AggregateIdArgumentResolver.php @@ -0,0 +1,23 @@ +header(AggregateHeader::class)->aggregateId; + } + + public function support(Argument $argument, string $eventClass): bool + { + return $argument->type === 'string' && in_array($argument->name, ['aggregateId', 'aggregateRootId']); + } +} diff --git a/src/Subscription/Subscriber/ArgumentResolver/Argument.php b/src/Subscription/Subscriber/ArgumentResolver/Argument.php new file mode 100644 index 000000000..752d3fc4a --- /dev/null +++ b/src/Subscription/Subscriber/ArgumentResolver/Argument.php @@ -0,0 +1,14 @@ +event(); + } + + public function support(Argument $argument, string $eventClass): bool + { + return class_exists($argument->type) && is_a($eventClass, $argument->type, true); + } +} diff --git a/src/Subscription/Subscriber/ArgumentResolver/MessageArgumentResolver.php b/src/Subscription/Subscriber/ArgumentResolver/MessageArgumentResolver.php new file mode 100644 index 000000000..c2346f551 --- /dev/null +++ b/src/Subscription/Subscriber/ArgumentResolver/MessageArgumentResolver.php @@ -0,0 +1,20 @@ +type === Message::class; + } +} diff --git a/src/Subscription/Subscriber/ArgumentResolver/RecordedOnArgumentResolver.php b/src/Subscription/Subscriber/ArgumentResolver/RecordedOnArgumentResolver.php new file mode 100644 index 000000000..cb913c7f7 --- /dev/null +++ b/src/Subscription/Subscriber/ArgumentResolver/RecordedOnArgumentResolver.php @@ -0,0 +1,22 @@ +header(AggregateHeader::class)->recordedOn; + } + + public function support(Argument $argument, string $eventClass): bool + { + return $argument->type === DateTimeImmutable::class; + } +} diff --git a/src/Subscription/Subscriber/MetadataSubscriberAccessor.php b/src/Subscription/Subscriber/MetadataSubscriberAccessor.php index c55f6c571..5ae6aacb1 100644 --- a/src/Subscription/Subscriber/MetadataSubscriberAccessor.php +++ b/src/Subscription/Subscriber/MetadataSubscriberAccessor.php @@ -9,6 +9,11 @@ use Patchlevel\EventSourcing\Message\Message; use Patchlevel\EventSourcing\Metadata\Subscriber\SubscriberMetadata; use Patchlevel\EventSourcing\Subscription\RunMode; +use Patchlevel\EventSourcing\Subscription\Subscriber\ArgumentResolver\Argument; +use Patchlevel\EventSourcing\Subscription\Subscriber\ArgumentResolver\ArgumentResolver; +use ReflectionMethod; +use ReflectionNamedType; +use RuntimeException; use function array_key_exists; use function array_map; @@ -19,9 +24,11 @@ final class MetadataSubscriberAccessor implements SubscriberAccessor /** @var array> */ private array $subscribeCache = []; + /** @param list $argumentResolvers */ public function __construct( private readonly object $subscriber, private readonly SubscriberMetadata $metadata, + private readonly array $argumentResolvers, ) { } @@ -79,11 +86,71 @@ public function subscribeMethods(string $eventClass): array ); $this->subscribeCache[$eventClass] = array_map( - /** @return Closure(Message):void */ - fn (string $method) => $this->subscriber->$method(...), + fn (string $method): Closure => $this->createClosure($eventClass, $method), $methods, ); return $this->subscribeCache[$eventClass]; } + + /** + * @param class-string $eventClass + * + * @return Closure(Message):void + */ + private function createClosure(string $eventClass, string $method): Closure + { + $resolvers = $this->resolvers($eventClass, $method); + + return function (Message $message) use ($method, $resolvers): void { + $arguments = []; + + foreach ($resolvers as $resolver) { + $arguments[] = $resolver($message); + } + + $this->subscriber->$method(...$arguments); + }; + } + + /** + * @param class-string $eventClass + * + * @return list + */ + private function resolvers(string $eventClass, string $method): array + { + $resolvers = []; + + $reflection = new ReflectionMethod($this->subscriber, $method); + + foreach ($reflection->getParameters() as $parameter) { + $type = $parameter->getType(); + + if (!$type instanceof ReflectionNamedType) { + throw new RuntimeException('parameter type is required'); + } + + $argument = new Argument( + $parameter->getName(), + $type->getName(), + ); + + foreach ($this->argumentResolvers as $resolver) { + if (!$resolver->support($argument, $eventClass)) { + continue; + } + + $resolvers[] = static function (Message $message) use ($resolver, $argument): mixed { + return $resolver->resolve($argument, $message); + }; + + continue 2; + } + + throw new RuntimeException('no argument resolver found'); + } + + return $resolvers; + } } diff --git a/src/Subscription/Subscriber/MetadataSubscriberAccessorRepository.php b/src/Subscription/Subscriber/MetadataSubscriberAccessorRepository.php index 324c1eada..08a00ebd4 100644 --- a/src/Subscription/Subscriber/MetadataSubscriberAccessorRepository.php +++ b/src/Subscription/Subscriber/MetadataSubscriberAccessorRepository.php @@ -43,7 +43,12 @@ private function subscriberAccessorMap(): array foreach ($this->subscribers as $subscriber) { $metadata = $this->metadataFactory->metadata($subscriber::class); - $this->subscribersMap[$metadata->id] = new MetadataSubscriberAccessor($subscriber, $metadata); + $this->subscribersMap[$metadata->id] = new MetadataSubscriberAccessor($subscriber, $metadata, [ + new ArgumentResolver\MessageArgumentResolver(), + new ArgumentResolver\EventArgumentResolver(), + new ArgumentResolver\AggregateIdArgumentResolver(), + new ArgumentResolver\RecordedOnArgumentResolver(), + ]); } return $this->subscribersMap; diff --git a/tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php b/tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php index 3cbf892eb..b4883cc81 100644 --- a/tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php +++ b/tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php @@ -5,18 +5,14 @@ namespace Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Projection; use Doctrine\DBAL\Connection; -use Patchlevel\EventSourcing\Aggregate\AggregateHeader; use Patchlevel\EventSourcing\Attribute\Projector; use Patchlevel\EventSourcing\Attribute\Setup; use Patchlevel\EventSourcing\Attribute\Subscribe; use Patchlevel\EventSourcing\Attribute\Teardown; -use Patchlevel\EventSourcing\Message\Message; use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberUtil; use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Events\NameChanged; use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Events\ProfileCreated; -use function assert; - #[Projector('profile')] final class ProfileProjector { @@ -40,12 +36,8 @@ public function drop(): void } #[Subscribe(ProfileCreated::class)] - public function onProfileCreated(Message $message): void + public function onProfileCreated(ProfileCreated $profileCreated): void { - $profileCreated = $message->event(); - - assert($profileCreated instanceof ProfileCreated); - $this->connection->insert( $this->table(), [ @@ -56,16 +48,12 @@ public function onProfileCreated(Message $message): void } #[Subscribe(NameChanged::class)] - public function onNameChanged(Message $message): void + public function onNameChanged(NameChanged $nameChanged, string $aggregateRootId): void { - $nameChanged = $message->event(); - - assert($nameChanged instanceof NameChanged); - $this->connection->update( $this->table(), ['name' => $nameChanged->name], - ['id' => $message->header(AggregateHeader::class)->aggregateId], + ['id' => $aggregateRootId], ); } diff --git a/tests/Integration/BasicImplementation/BasicIntegrationTest.php b/tests/Integration/BasicImplementation/BasicIntegrationTest.php index d782339e8..6e505a784 100644 --- a/tests/Integration/BasicImplementation/BasicIntegrationTest.php +++ b/tests/Integration/BasicImplementation/BasicIntegrationTest.php @@ -19,8 +19,8 @@ use Patchlevel\EventSourcing\Subscription\Subscriber\MetadataSubscriberAccessorRepository; use Patchlevel\EventSourcing\Tests\DbalManager; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Aggregate\Profile; +use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Listener\SendEmailListener; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\MessageDecorator\FooMessageDecorator; -use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Processor\SendEmailProcessor; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Projection\ProfileProjector; use PHPUnit\Framework\TestCase; @@ -59,8 +59,7 @@ public function testSuccessful(): void ); $eventBus = DefaultEventBus::create([ - new SendEmailProcessor(), - $profileProjector, + new SendEmailListener(), ]); $manager = new DefaultRepositoryManager( @@ -85,6 +84,8 @@ public function testSuccessful(): void $profile = Profile::create($profileId, 'John'); $repository->save($profile); + $engine->run(); + $result = $this->connection->fetchAssociative('SELECT * FROM projection_profile WHERE id = ?', ['1']); self::assertIsArray($result); @@ -126,8 +127,7 @@ public function testSnapshot(): void ); $eventBus = DefaultEventBus::create([ - new SendEmailProcessor(), - $profileProjection, + new SendEmailListener(), ]); $manager = new DefaultRepositoryManager( @@ -152,6 +152,8 @@ public function testSnapshot(): void $profile = Profile::create($profileId, 'John'); $repository->save($profile); + $engine->run(); + $result = $this->connection->fetchAssociative('SELECT * FROM projection_profile WHERE id = ?', ['1']); self::assertIsArray($result); diff --git a/tests/Integration/BasicImplementation/Processor/SendEmailProcessor.php b/tests/Integration/BasicImplementation/Listener/SendEmailListener.php similarity index 90% rename from tests/Integration/BasicImplementation/Processor/SendEmailProcessor.php rename to tests/Integration/BasicImplementation/Listener/SendEmailListener.php index d68809df7..2e10d2b9b 100644 --- a/tests/Integration/BasicImplementation/Processor/SendEmailProcessor.php +++ b/tests/Integration/BasicImplementation/Listener/SendEmailListener.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Processor; +namespace Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Listener; use Patchlevel\EventSourcing\Attribute\Subscribe; use Patchlevel\EventSourcing\Message\Message; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Events\ProfileCreated; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\SendEmailMock; -final class SendEmailProcessor +final class SendEmailListener { #[Subscribe(ProfileCreated::class)] public function onProfileCreated(Message $message): void diff --git a/tests/Integration/BasicImplementation/Projection/ProfileProjector.php b/tests/Integration/BasicImplementation/Projection/ProfileProjector.php index 6add789b4..87f80777a 100644 --- a/tests/Integration/BasicImplementation/Projection/ProfileProjector.php +++ b/tests/Integration/BasicImplementation/Projection/ProfileProjector.php @@ -10,11 +10,8 @@ use Patchlevel\EventSourcing\Attribute\Setup; use Patchlevel\EventSourcing\Attribute\Subscribe; use Patchlevel\EventSourcing\Attribute\Teardown; -use Patchlevel\EventSourcing\Message\Message; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Events\ProfileCreated; -use function assert; - #[Projector('profile-1')] final class ProfileProjector { @@ -41,12 +38,8 @@ public function drop(): void } #[Subscribe(ProfileCreated::class)] - public function handleProfileCreated(Message $message): void + public function handleProfileCreated(ProfileCreated $profileCreated): void { - $profileCreated = $message->event(); - - assert($profileCreated instanceof ProfileCreated); - $this->connection->executeStatement( 'INSERT INTO projection_profile (id, name) VALUES(:id, :name);', [ diff --git a/tests/Unit/Subscription/Subscriber/MetadataSubscriberAccessorRepositoryTest.php b/tests/Unit/Subscription/Subscriber/MetadataSubscriberAccessorRepositoryTest.php index 160cb902c..b45887a4c 100644 --- a/tests/Unit/Subscription/Subscriber/MetadataSubscriberAccessorRepositoryTest.php +++ b/tests/Unit/Subscription/Subscriber/MetadataSubscriberAccessorRepositoryTest.php @@ -39,6 +39,7 @@ class { $accessor = new MetadataSubscriberAccessor( $subscriber, $metadataFactory->metadata($subscriber::class), + [], ); self::assertEquals([$accessor], $repository->all()); diff --git a/tests/Unit/Subscription/Subscriber/MetadataSubscriberAccessorTest.php b/tests/Unit/Subscription/Subscriber/MetadataSubscriberAccessorTest.php index 6e19f2053..e503ee0e2 100644 --- a/tests/Unit/Subscription/Subscriber/MetadataSubscriberAccessorTest.php +++ b/tests/Unit/Subscription/Subscriber/MetadataSubscriberAccessorTest.php @@ -27,6 +27,7 @@ class { $accessor = new MetadataSubscriberAccessor( $subscriber, (new AttributeSubscriberMetadataFactory())->metadata($subscriber::class), + [], ); self::assertEquals('profile', $accessor->id()); @@ -41,6 +42,7 @@ class { $accessor = new MetadataSubscriberAccessor( $subscriber, (new AttributeSubscriberMetadataFactory())->metadata($subscriber::class), + [], ); self::assertEquals('default', $accessor->group()); @@ -55,6 +57,7 @@ class { $accessor = new MetadataSubscriberAccessor( $subscriber, (new AttributeSubscriberMetadataFactory())->metadata($subscriber::class), + [], ); self::assertEquals(RunMode::FromBeginning, $accessor->runMode()); @@ -73,6 +76,7 @@ public function onProfileCreated(Message $message): void $accessor = new MetadataSubscriberAccessor( $subscriber, (new AttributeSubscriberMetadataFactory())->metadata($subscriber::class), + [], ); $result = $accessor->subscribeMethods(ProfileCreated::class); @@ -100,6 +104,7 @@ public function onFoo(Message $message): void $accessor = new MetadataSubscriberAccessor( $subscriber, (new AttributeSubscriberMetadataFactory())->metadata($subscriber::class), + [], ); $result = $accessor->subscribeMethods(ProfileCreated::class); @@ -123,6 +128,7 @@ public function onProfileCreated(Message $message): void $accessor = new MetadataSubscriberAccessor( $subscriber, (new AttributeSubscriberMetadataFactory())->metadata($subscriber::class), + [], ); $result = $accessor->subscribeMethods(ProfileCreated::class); @@ -145,6 +151,7 @@ public function method(): void $accessor = new MetadataSubscriberAccessor( $subscriber, (new AttributeSubscriberMetadataFactory())->metadata($subscriber::class), + [], ); $result = $accessor->setupMethod(); @@ -161,6 +168,7 @@ class { $accessor = new MetadataSubscriberAccessor( $subscriber, (new AttributeSubscriberMetadataFactory())->metadata($subscriber::class), + [], ); $result = $accessor->setupMethod(); @@ -181,6 +189,7 @@ public function method(): void $accessor = new MetadataSubscriberAccessor( $subscriber, (new AttributeSubscriberMetadataFactory())->metadata($subscriber::class), + [], ); $result = $accessor->teardownMethod(); @@ -197,6 +206,7 @@ class { $accessor = new MetadataSubscriberAccessor( $subscriber, (new AttributeSubscriberMetadataFactory())->metadata($subscriber::class), + [], ); $result = $accessor->teardownMethod();