From abe4a5d1a18859214e1bc00c3b5415eb3c263bb7 Mon Sep 17 00:00:00 2001 From: David Badura Date: Sat, 14 Feb 2026 12:04:35 +0100 Subject: [PATCH] use type info in argument resolver --- phpstan-baseline.neon | 8 +--- src/Metadata/Subscriber/ArgumentMetadata.php | 5 ++- .../Subscriber/ArgumentTypeNotSupported.php | 12 ------ .../AttributeSubscriberMetadataFactory.php | 20 ++++----- .../EventArgumentResolver.php | 5 +-- .../ArgumentResolver/LookupResolver.php | 2 +- .../MessageArgumentResolver.php | 2 +- .../RecordedOnArgumentResolver.php | 2 +- ...AttributeSubscriberMetadataFactoryTest.php | 42 +++---------------- .../Engine/DefaultSubscriptionEngineTest.php | 3 -- .../EventArgumentResolverTest.php | 7 ++-- .../ArgumentResolver/LookupResolverTest.php | 7 ++-- .../MessageArgumentResolverTest.php | 7 ++-- .../RecordedOnArgumentResolverTest.php | 9 ++-- 14 files changed, 39 insertions(+), 92 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 31b9eac27..842e4438c 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -294,12 +294,6 @@ parameters: count: 1 path: tests/Integration/Subscription/SubscriptionTest.php - - - message: '#^Instantiated class Patchlevel\\EventSourcing\\Tests\\Integration\\Subscription\\MigrateAggregateToStreamStoreSubscriber not found\.$#' - identifier: class.notFound - count: 1 - path: tests/Integration/Subscription/SubscriptionTest.php - - message: '#^Parameter \#4 \$maxAttempts of class Patchlevel\\EventSourcing\\Subscription\\RetryStrategy\\ClockBasedRetryStrategy constructor expects int\<1, max\>, 0 given\.$#' identifier: argument.type @@ -433,7 +427,7 @@ parameters: path: tests/Unit/Message/Translator/ReplaceEventTranslatorTest.php - - message: '#^Method class@anonymous/tests/Unit/Metadata/Subscriber/AttributeSubscriberMetadataFactoryTest\.php\:236\:\:profileVisited\(\) has parameter \$message with no type specified\.$#' + message: '#^Method class@anonymous/tests/Unit/Metadata/Subscriber/AttributeSubscriberMetadataFactoryTest\.php\:234\:\:profileVisited\(\) has parameter \$message with no type specified\.$#' identifier: missingType.parameter count: 1 path: tests/Unit/Metadata/Subscriber/AttributeSubscriberMetadataFactoryTest.php diff --git a/src/Metadata/Subscriber/ArgumentMetadata.php b/src/Metadata/Subscriber/ArgumentMetadata.php index ed7ef590c..f4df5e084 100644 --- a/src/Metadata/Subscriber/ArgumentMetadata.php +++ b/src/Metadata/Subscriber/ArgumentMetadata.php @@ -4,12 +4,13 @@ namespace Patchlevel\EventSourcing\Metadata\Subscriber; +use Symfony\Component\TypeInfo\Type; + final class ArgumentMetadata { public function __construct( public readonly string $name, - public readonly string $type, - public readonly bool $allowsNull = false, + public readonly Type $type, ) { } } diff --git a/src/Metadata/Subscriber/ArgumentTypeNotSupported.php b/src/Metadata/Subscriber/ArgumentTypeNotSupported.php index f1fe155ff..a8224f764 100644 --- a/src/Metadata/Subscriber/ArgumentTypeNotSupported.php +++ b/src/Metadata/Subscriber/ArgumentTypeNotSupported.php @@ -21,16 +21,4 @@ public static function missingType(string $class, string $method, string $argume ), ); } - - public static function onlyNamedTypeSupported(string $class, string $method, string $argumentName): self - { - return new self( - sprintf( - 'Argument type for method "%s" in class "%s" is not supported. Argument "%s" must not have a union or intersection type.', - $method, - $class, - $argumentName, - ), - ); - } } diff --git a/src/Metadata/Subscriber/AttributeSubscriberMetadataFactory.php b/src/Metadata/Subscriber/AttributeSubscriberMetadataFactory.php index 53bec8a2e..dc29fd8fc 100644 --- a/src/Metadata/Subscriber/AttributeSubscriberMetadataFactory.php +++ b/src/Metadata/Subscriber/AttributeSubscriberMetadataFactory.php @@ -14,13 +14,20 @@ use ReflectionAttribute; use ReflectionClass; use ReflectionMethod; -use ReflectionNamedType; +use Symfony\Component\TypeInfo\TypeResolver\TypeResolver; use function array_key_exists; use function count; final class AttributeSubscriberMetadataFactory implements SubscriberMetadataFactory { + private readonly TypeResolver $typeResolver; + + public function __construct() + { + $this->typeResolver = TypeResolver::create(); + } + /** @var array */ private array $subscriberMetadata = []; @@ -171,18 +178,9 @@ private function subscribeMethod(ReflectionMethod $method): SubscribeMethodMetad ); } - if (!$type instanceof ReflectionNamedType) { - throw ArgumentTypeNotSupported::onlyNamedTypeSupported( - $method->getDeclaringClass()->getName(), - $method->getName(), - $parameter->getName(), - ); - } - $arguments[] = new ArgumentMetadata( $parameter->getName(), - $type->getName(), - $parameter->allowsNull(), + $this->typeResolver->resolve($type), ); } diff --git a/src/Subscription/Subscriber/ArgumentResolver/EventArgumentResolver.php b/src/Subscription/Subscriber/ArgumentResolver/EventArgumentResolver.php index 6f362bffe..23ed9283f 100644 --- a/src/Subscription/Subscriber/ArgumentResolver/EventArgumentResolver.php +++ b/src/Subscription/Subscriber/ArgumentResolver/EventArgumentResolver.php @@ -7,9 +7,6 @@ use Patchlevel\EventSourcing\Message\Message; use Patchlevel\EventSourcing\Metadata\Subscriber\ArgumentMetadata; -use function class_exists; -use function is_a; - final class EventArgumentResolver implements ArgumentResolver { public function resolve(ArgumentMetadata $argument, Message $message): object @@ -19,6 +16,6 @@ public function resolve(ArgumentMetadata $argument, Message $message): object public function support(ArgumentMetadata $argument, string $eventClass): bool { - return class_exists($argument->type) && is_a($eventClass, $argument->type, true); + return $argument->type->isIdentifiedBy($eventClass); } } diff --git a/src/Subscription/Subscriber/ArgumentResolver/LookupResolver.php b/src/Subscription/Subscriber/ArgumentResolver/LookupResolver.php index 676b8b197..6b466e148 100644 --- a/src/Subscription/Subscriber/ArgumentResolver/LookupResolver.php +++ b/src/Subscription/Subscriber/ArgumentResolver/LookupResolver.php @@ -29,6 +29,6 @@ public function resolve(ArgumentMetadata $argument, Message $message): Lookup public function support(ArgumentMetadata $argument, string $eventClass): bool { - return $argument->type === Lookup::class; + return $argument->type->isIdentifiedBy(Lookup::class); } } diff --git a/src/Subscription/Subscriber/ArgumentResolver/MessageArgumentResolver.php b/src/Subscription/Subscriber/ArgumentResolver/MessageArgumentResolver.php index ceabce7c8..50cdb848f 100644 --- a/src/Subscription/Subscriber/ArgumentResolver/MessageArgumentResolver.php +++ b/src/Subscription/Subscriber/ArgumentResolver/MessageArgumentResolver.php @@ -16,6 +16,6 @@ public function resolve(ArgumentMetadata $argument, Message $message): Message public function support(ArgumentMetadata $argument, string $eventClass): bool { - return $argument->type === Message::class; + return $argument->type->isIdentifiedBy(Message::class); } } diff --git a/src/Subscription/Subscriber/ArgumentResolver/RecordedOnArgumentResolver.php b/src/Subscription/Subscriber/ArgumentResolver/RecordedOnArgumentResolver.php index 496e1c10b..ddc71f5b1 100644 --- a/src/Subscription/Subscriber/ArgumentResolver/RecordedOnArgumentResolver.php +++ b/src/Subscription/Subscriber/ArgumentResolver/RecordedOnArgumentResolver.php @@ -18,6 +18,6 @@ public function resolve(ArgumentMetadata $argument, Message $message): DateTimeI public function support(ArgumentMetadata $argument, string $eventClass): bool { - return $argument->type === DateTimeImmutable::class; + return $argument->type->isIdentifiedBy(DateTimeImmutable::class); } } diff --git a/tests/Unit/Metadata/Subscriber/AttributeSubscriberMetadataFactoryTest.php b/tests/Unit/Metadata/Subscriber/AttributeSubscriberMetadataFactoryTest.php index c0ab8319f..144335160 100644 --- a/tests/Unit/Metadata/Subscriber/AttributeSubscriberMetadataFactoryTest.php +++ b/tests/Unit/Metadata/Subscriber/AttributeSubscriberMetadataFactoryTest.php @@ -24,7 +24,7 @@ use Patchlevel\EventSourcing\Tests\Unit\Fixture\ProfileVisited; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; -use Stringable; +use Symfony\Component\TypeInfo\Type; #[CoversClass(AttributeSubscriberMetadataFactory::class)] final class AttributeSubscriberMetadataFactoryTest extends TestCase @@ -189,14 +189,14 @@ public function profileCreated(ProfileCreated $profileCreated, string $aggregate [ ProfileVisited::class => new SubscribeMethodMetadata( 'profileVisited', - [new ArgumentMetadata('message', Message::class)], + [new ArgumentMetadata('message', Type::object(Message::class))], ), ProfileCreated::class => new SubscribeMethodMetadata( 'profileCreated', [ - new ArgumentMetadata('profileCreated', ProfileCreated::class), - new ArgumentMetadata('aggregateId', 'string'), + new ArgumentMetadata('profileCreated', Type::object(ProfileCreated::class)), + new ArgumentMetadata('aggregateId', Type::string()), ], ), ], @@ -220,7 +220,7 @@ public function profileVisited(ProfileVisited|null $message): void self::assertEquals( [ ProfileVisited::class => new SubscribeMethodMetadata('profileVisited', [ - new ArgumentMetadata('message', ProfileVisited::class, true), + new ArgumentMetadata('message', Type::nullable(Type::object(ProfileVisited::class))), ]), ], $metadata->subscribeMethods, @@ -245,38 +245,6 @@ public function profileVisited($message): void $metadataFactory->metadata($subscriber::class); } - public function testUnionTypeNotSupported(): void - { - $this->expectException(ArgumentTypeNotSupported::class); - - $subscriber = new #[Subscriber('foo', RunMode::FromBeginning)] - class { - #[Subscribe(ProfileVisited::class)] - public function profileVisited(ProfileVisited|ProfileCreated $event): void - { - } - }; - - $metadataFactory = new AttributeSubscriberMetadataFactory(); - $metadataFactory->metadata($subscriber::class); - } - - public function testIntersectionTypeNotSupported(): void - { - $this->expectException(ArgumentTypeNotSupported::class); - - $subscriber = new #[Subscriber('foo', RunMode::FromBeginning)] - class { - #[Subscribe(ProfileVisited::class)] - public function profileVisited(ProfileVisited&Stringable $event): void - { - } - }; - - $metadataFactory = new AttributeSubscriberMetadataFactory(); - $metadataFactory->metadata($subscriber::class); - } - public function testSubscribeAllWithExplicitSubscribeMethod(): void { $this->expectException(DuplicateSubscribeMethod::class); diff --git a/tests/Unit/Subscription/Engine/DefaultSubscriptionEngineTest.php b/tests/Unit/Subscription/Engine/DefaultSubscriptionEngineTest.php index 38a4d0efc..b6fc5c737 100644 --- a/tests/Unit/Subscription/Engine/DefaultSubscriptionEngineTest.php +++ b/tests/Unit/Subscription/Engine/DefaultSubscriptionEngineTest.php @@ -14,9 +14,6 @@ use Patchlevel\EventSourcing\Attribute\Teardown; use Patchlevel\EventSourcing\Message\Message; use Patchlevel\EventSourcing\Store\ArrayStream; -use Patchlevel\EventSourcing\Store\Criteria\Criteria; -use Patchlevel\EventSourcing\Store\Criteria\FromIndexCriterion; -use Patchlevel\EventSourcing\Store\Store; use Patchlevel\EventSourcing\Subscription\Cleanup\CleanupFailed; use Patchlevel\EventSourcing\Subscription\Cleanup\CleanupTaskHandler; use Patchlevel\EventSourcing\Subscription\Cleanup\Dbal\DropTableTask; diff --git a/tests/Unit/Subscription/Subscriber/ArgumentResolver/EventArgumentResolverTest.php b/tests/Unit/Subscription/Subscriber/ArgumentResolver/EventArgumentResolverTest.php index 718ba6547..786b53fc1 100644 --- a/tests/Unit/Subscription/Subscriber/ArgumentResolver/EventArgumentResolverTest.php +++ b/tests/Unit/Subscription/Subscriber/ArgumentResolver/EventArgumentResolverTest.php @@ -12,6 +12,7 @@ use Patchlevel\EventSourcing\Tests\Unit\Fixture\ProfileVisited; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; +use Symfony\Component\TypeInfo\Type; #[CoversClass(EventArgumentResolver::class)] final class EventArgumentResolverTest extends TestCase @@ -22,14 +23,14 @@ public function testSupport(): void self::assertTrue( $resolver->support( - new ArgumentMetadata('foo', ProfileCreated::class, false), + new ArgumentMetadata('foo', Type::object(ProfileCreated::class)), ProfileCreated::class, ), ); self::assertFalse( $resolver->support( - new ArgumentMetadata('foo', ProfileVisited::class, false), + new ArgumentMetadata('foo', Type::object(ProfileVisited::class)), ProfileCreated::class, ), ); @@ -45,7 +46,7 @@ public function testResolve(): void self::assertSame( $event, $resolver->resolve( - new ArgumentMetadata('foo', ProfileVisited::class, false), + new ArgumentMetadata('foo', Type::object(ProfileVisited::class)), $message, ), ); diff --git a/tests/Unit/Subscription/Subscriber/ArgumentResolver/LookupResolverTest.php b/tests/Unit/Subscription/Subscriber/ArgumentResolver/LookupResolverTest.php index b87149270..2e62b07b9 100644 --- a/tests/Unit/Subscription/Subscriber/ArgumentResolver/LookupResolverTest.php +++ b/tests/Unit/Subscription/Subscriber/ArgumentResolver/LookupResolverTest.php @@ -16,6 +16,7 @@ use Patchlevel\EventSourcing\Tests\Unit\Fixture\ProfileVisited; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; +use Symfony\Component\TypeInfo\Type; #[CoversClass(LookupResolver::class)] final class LookupResolverTest extends TestCase @@ -29,14 +30,14 @@ public function testSupport(): void self::assertTrue( $resolver->support( - new ArgumentMetadata('lookup', Lookup::class, false), + new ArgumentMetadata('lookup', Type::object(Lookup::class)), ProfileCreated::class, ), ); self::assertFalse( $resolver->support( - new ArgumentMetadata('foo', ProfileCreated::class, false), + new ArgumentMetadata('foo', Type::object(ProfileCreated::class)), ProfileCreated::class, ), ); @@ -56,7 +57,7 @@ public function testResolve(): void ); $lookup = $resolver->resolve( - new ArgumentMetadata('foo', Lookup::class, false), + new ArgumentMetadata('foo', Type::object(Lookup::class)), $message, ); diff --git a/tests/Unit/Subscription/Subscriber/ArgumentResolver/MessageArgumentResolverTest.php b/tests/Unit/Subscription/Subscriber/ArgumentResolver/MessageArgumentResolverTest.php index a78e58955..0d76a7c0f 100644 --- a/tests/Unit/Subscription/Subscriber/ArgumentResolver/MessageArgumentResolverTest.php +++ b/tests/Unit/Subscription/Subscriber/ArgumentResolver/MessageArgumentResolverTest.php @@ -10,6 +10,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use stdClass; +use Symfony\Component\TypeInfo\Type; #[CoversClass(MessageArgumentResolver::class)] final class MessageArgumentResolverTest extends TestCase @@ -20,14 +21,14 @@ public function testSupport(): void self::assertTrue( $resolver->support( - new ArgumentMetadata('foo', Message::class, false), + new ArgumentMetadata('foo', Type::object(Message::class)), 'qux', ), ); self::assertFalse( $resolver->support( - new ArgumentMetadata('foo', 'bar', false), + new ArgumentMetadata('foo', Type::string()), 'qux', ), ); @@ -41,7 +42,7 @@ public function testResolve(): void self::assertSame( $message, $resolver->resolve( - new ArgumentMetadata('foo', Message::class, false), + new ArgumentMetadata('foo', Type::object(Message::class)), $message, ), ); diff --git a/tests/Unit/Subscription/Subscriber/ArgumentResolver/RecordedOnArgumentResolverTest.php b/tests/Unit/Subscription/Subscriber/ArgumentResolver/RecordedOnArgumentResolverTest.php index 7ca6c443d..c3697ef0e 100644 --- a/tests/Unit/Subscription/Subscriber/ArgumentResolver/RecordedOnArgumentResolverTest.php +++ b/tests/Unit/Subscription/Subscriber/ArgumentResolver/RecordedOnArgumentResolverTest.php @@ -12,6 +12,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use stdClass; +use Symfony\Component\TypeInfo\Type; #[CoversClass(RecordedOnArgumentResolver::class)] final class RecordedOnArgumentResolverTest extends TestCase @@ -22,14 +23,14 @@ public function testSupport(): void self::assertTrue( $resolver->support( - new ArgumentMetadata('foo', DateTimeImmutable::class, false), + new ArgumentMetadata('foo', Type::object(DateTimeImmutable::class)), 'qux', ), ); self::assertFalse( $resolver->support( - new ArgumentMetadata('foo', 'bar', false), + new ArgumentMetadata('foo', Type::string()), 'qux', ), ); @@ -47,7 +48,7 @@ public function testResolveFromAggregateHeader(): void self::assertSame( $date, $resolver->resolve( - new ArgumentMetadata('foo', DateTimeImmutable::class, false), + new ArgumentMetadata('foo', Type::object(DateTimeImmutable::class)), $message, ), ); @@ -63,7 +64,7 @@ public function testResolveFromRecordedOnHeader(): void self::assertSame( $date, $resolver->resolve( - new ArgumentMetadata('foo', DateTimeImmutable::class, false), + new ArgumentMetadata('foo', Type::object(DateTimeImmutable::class)), $message, ), );