diff --git a/src/DependencyInjection/HandlerCompilerPass.php b/src/DependencyInjection/HandlerCompilerPass.php index 2196754f..8741c91b 100644 --- a/src/DependencyInjection/HandlerCompilerPass.php +++ b/src/DependencyInjection/HandlerCompilerPass.php @@ -4,23 +4,14 @@ namespace Patchlevel\EventSourcingBundle\DependencyInjection; -use Patchlevel\EventSourcing\Attribute\Inject; use Patchlevel\EventSourcing\CommandBus\Handler\CreateAggregateHandler; -use Patchlevel\EventSourcing\CommandBus\Handler\ServiceNotResolvable; use Patchlevel\EventSourcing\CommandBus\Handler\UpdateAggregateHandler; use Patchlevel\EventSourcing\CommandBus\HandlerFinder; use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootRegistry; use Patchlevel\EventSourcing\Repository\RepositoryManager; -use Patchlevel\EventSourcingBundle\CommandBus\SymfonyParameterResolver; -use ReflectionAttribute; -use ReflectionMethod; -use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; -use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\TypeInfo\Type\ObjectType; -use Symfony\Component\TypeInfo\TypeResolver\TypeResolver; use function sprintf; use function strtolower; @@ -41,14 +32,11 @@ public function process(ContainerBuilder $container): void foreach ($aggregateRootRegistry->aggregateClasses() as $aggregateName => $aggregateClass) { $parameterResolverId = sprintf('.event_sourcing.handler_parameter_resolver.%s', $aggregateName); - $services = []; foreach (HandlerFinder::findInClass($aggregateClass) as $aggregateHandler) { $handlerId = strtolower(sprintf('event_sourcing.handler.%s.%s', $aggregateName, $aggregateHandler->method)); $handlerClass = $aggregateHandler->static ? CreateAggregateHandler::class : UpdateAggregateHandler::class; - $services += $this->services(new ReflectionMethod($aggregateClass, $aggregateHandler->method)); - $container->register($handlerId, $handlerClass) ->setArguments([ new Reference(RepositoryManager::class), @@ -61,61 +49,6 @@ public function process(ContainerBuilder $container): void 'bus' => $bus, ]); } - - $container->register($parameterResolverId, SymfonyParameterResolver::class) - ->setArguments([ - new ServiceLocatorArgument($services), - ]); } } - - /** @return array */ - private function services(ReflectionMethod $method): array - { - $services = []; - $prefix = strtolower($method->getName()) . '.'; - - foreach ($method->getParameters() as $index => $parameter) { - if ($index === 0) { - continue; // skip first parameter (command) - } - - $key = $prefix . $parameter->getName(); - - $attributes = $parameter->getAttributes(Inject::class); - - if ($attributes !== []) { - $services[$key] = new Reference($attributes[0]->newInstance()->service); - - continue; - } - - $attributes = $parameter->getAttributes(Autowire::class, ReflectionAttribute::IS_INSTANCEOF); - - if ($attributes !== []) { - $services[$key] = $attributes[0]->newInstance()->value; - - continue; - } - - $reflectionType = $parameter->getType(); - - if ($reflectionType === null) { - throw ServiceNotResolvable::missingType($method->getDeclaringClass()->getName(), $parameter->getName()); - } - - $type = TypeResolver::create()->resolve($reflectionType); - - if (!$type instanceof ObjectType) { - throw ServiceNotResolvable::typeNotObject( - $method->getDeclaringClass()->getName(), - $parameter->getName(), - ); - } - - $services[$key] = new Reference($type->getClassName()); - } - - return $services; - } } diff --git a/src/DependencyInjection/HandlerServiceLocatorCompilerPass.php b/src/DependencyInjection/HandlerServiceLocatorCompilerPass.php new file mode 100644 index 00000000..87eb52a6 --- /dev/null +++ b/src/DependencyInjection/HandlerServiceLocatorCompilerPass.php @@ -0,0 +1,110 @@ +hasParameter('patchlevel_event_sourcing.aggregate_handlers.bus')) { + return; + } + + /** @var AggregateRootRegistry $aggregateRootRegistry */ + $aggregateRootRegistry = $container->get(AggregateRootRegistry::class); + + foreach ($aggregateRootRegistry->aggregateClasses() as $aggregateName => $aggregateClass) { + $parameterResolverId = sprintf('.event_sourcing.handler_parameter_resolver.%s', $aggregateName); + $services = []; + + foreach (HandlerFinder::findInClass($aggregateClass) as $aggregateHandler) { + $services += $this->services(new ReflectionMethod($aggregateClass, $aggregateHandler->method), $container); + } + + $container->register($parameterResolverId, SymfonyParameterResolver::class) + ->setArguments([ + new ServiceLocatorArgument($services), + ]); + } + } + + /** @return array */ + private function services(ReflectionMethod $method, ContainerInterface $container): array + { + $services = []; + $prefix = strtolower($method->getName()) . '.'; + + foreach ($method->getParameters() as $index => $parameter) { + if ($index === 0) { + continue; // skip first parameter (command) + } + + $key = $prefix . $parameter->getName(); + + $attributes = $parameter->getAttributes(Inject::class); + + if ($attributes !== []) { + $services[$key] = new Reference($attributes[0]->newInstance()->service); + + continue; + } + + $attributes = $parameter->getAttributes(Autowire::class, ReflectionAttribute::IS_INSTANCEOF); + + if ($attributes !== []) { + $services[$key] = $attributes[0]->newInstance()->value; + + continue; + } + + $reflectionType = $parameter->getType(); + + if ($reflectionType === null) { + throw ServiceNotResolvable::missingType($method->getDeclaringClass()->getName(), $parameter->getName()); + } + + $type = TypeResolver::create()->resolve($reflectionType); + + if (!$type instanceof ObjectType) { + throw ServiceNotResolvable::typeNotObject( + $method->getDeclaringClass()->getName(), + $parameter->getName(), + ); + } + + $binding = sprintf('%s $%s', $type->getClassName(), $parameter->getName()); + + if ($container->has($binding)) { + $services[$key] = new Reference($binding); + + continue; + } + + $services[$key] = new Reference($type->getClassName()); + } + + return $services; + } +} diff --git a/src/PatchlevelEventSourcingBundle.php b/src/PatchlevelEventSourcingBundle.php index 8598e72e..335eb79a 100644 --- a/src/PatchlevelEventSourcingBundle.php +++ b/src/PatchlevelEventSourcingBundle.php @@ -5,6 +5,7 @@ namespace Patchlevel\EventSourcingBundle; use Patchlevel\EventSourcingBundle\DependencyInjection\HandlerCompilerPass; +use Patchlevel\EventSourcingBundle\DependencyInjection\HandlerServiceLocatorCompilerPass; use Patchlevel\EventSourcingBundle\DependencyInjection\RepositoryCompilerPass; use Patchlevel\EventSourcingBundle\DependencyInjection\SubscriberGuardCompilePass; use Patchlevel\EventSourcingBundle\DependencyInjection\TranslatorCompilerPass; @@ -18,6 +19,7 @@ public function build(ContainerBuilder $container): void $container->addCompilerPass(new RepositoryCompilerPass()); $container->addCompilerPass(new SubscriberGuardCompilePass()); $container->addCompilerPass(new HandlerCompilerPass(), priority: 100); + $container->addCompilerPass(new HandlerServiceLocatorCompilerPass(), priority: -100); $container->addCompilerPass(new TranslatorCompilerPass()); } } diff --git a/tests/Fixtures/Profile.php b/tests/Fixtures/Profile.php index 94c06fb5..b3aedad4 100644 --- a/tests/Fixtures/Profile.php +++ b/tests/Fixtures/Profile.php @@ -8,6 +8,7 @@ use Patchlevel\EventSourcing\Attribute\Apply; use Patchlevel\EventSourcing\Attribute\Handle; use Patchlevel\EventSourcing\Attribute\Id; +use Patchlevel\EventSourcing\Repository\Repository; #[Aggregate('profile')] class Profile extends BasicAggregateRoot @@ -16,7 +17,10 @@ class Profile extends BasicAggregateRoot private CustomId $id; #[Handle] - public static function create(CreateProfile $command): self + public static function create( + CreateProfile $command, + Repository $profileRepository + ): self { $profile = new self(); $profile->recordThat(new ProfileCreated($command->id)); diff --git a/tests/Unit/PatchlevelEventSourcingBundleTest.php b/tests/Unit/PatchlevelEventSourcingBundleTest.php index 77da4eaf..ba4d9ebf 100644 --- a/tests/Unit/PatchlevelEventSourcingBundleTest.php +++ b/tests/Unit/PatchlevelEventSourcingBundleTest.php @@ -9,6 +9,7 @@ use Doctrine\Migrations\Tools\Console\Command\MigrateCommand; use Doctrine\Migrations\Tools\Console\Command\StatusCommand; use InvalidArgumentException; +use Patchlevel\EventSourcing\Aggregate\CustomId; use Patchlevel\EventSourcing\Clock\FrozenClock; use Patchlevel\EventSourcing\Clock\SystemClock; use Patchlevel\EventSourcing\CommandBus\Handler\CreateAggregateHandler; @@ -92,6 +93,7 @@ use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; use Psr\SimpleCache\CacheInterface; +use stdClass; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -526,7 +528,11 @@ public function testCommandHandler(): void ] ); - self::assertInstanceOf(CreateAggregateHandler::class, $container->get('event_sourcing.handler.profile.create')); + $handler = $container->get('event_sourcing.handler.profile.create'); + + self::assertInstanceOf(CreateAggregateHandler::class, $handler); + + $handler(new CreateProfile(CustomId::fromString('1'))); $definition = $container->getDefinition('event_sourcing.handler.profile.create'); $tags = $definition->getTag('messenger.message_handler');