From 6fc91eb192736f19729a1bb9fa0bd905b42f0a61 Mon Sep 17 00:00:00 2001 From: Alexandre Parent Date: Tue, 14 Jan 2020 14:11:45 -0500 Subject: [PATCH] [DI] Fix support for multiple tags for locators and iterators --- .../Compiler/PriorityTaggedServiceTrait.php | 115 +++++++++--------- .../Tests/Compiler/IntegrationTest.php | 64 +++++++++- 2 files changed, 121 insertions(+), 58 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php index 9b3760b497..1a2c829854 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php @@ -54,78 +54,81 @@ trait PriorityTaggedServiceTrait foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) { $class = $r = null; - $priority = 0; - if (isset($attributes[0]['priority'])) { - $priority = $attributes[0]['priority']; - } elseif ($defaultPriorityMethod) { - $class = $container->getDefinition($serviceId)->getClass(); - $class = $container->getParameterBag()->resolveValue($class) ?: null; - if (($r = $container->getReflectionClass($class)) && $r->hasMethod($defaultPriorityMethod)) { - if (!($rm = $r->getMethod($defaultPriorityMethod))->isStatic()) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId)); - } + $defaultPriority = null; + $defaultIndex = null; - if (!$rm->isPublic()) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId)); - } + foreach ($attributes as $attribute) { + $index = $priority = null; - $priority = $rm->invoke(null); + if (isset($attribute['priority'])) { + $priority = $attribute['priority']; + } elseif (null === $defaultPriority && $defaultPriorityMethod) { + $class = $container->getDefinition($serviceId)->getClass(); + $class = $container->getParameterBag()->resolveValue($class) ?: null; - if (!\is_int($priority)) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return an integer, got %s: tag "%s" on service "%s".', $class, $defaultPriorityMethod, \gettype($priority), $tagName, $serviceId)); + if (($r = ($r ?? $container->getReflectionClass($class))) && $r->hasMethod($defaultPriorityMethod)) { + if (!($rm = $r->getMethod($defaultPriorityMethod))->isStatic()) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId)); + } + + if (!$rm->isPublic()) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId)); + } + + $defaultPriority = $rm->invoke(null); + + if (!\is_int($defaultPriority)) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return an integer, got %s: tag "%s" on service "%s".', $class, $defaultPriorityMethod, \gettype($priority), $tagName, $serviceId)); + } } } - } - if (null === $indexAttribute && !$needsIndexes) { - $services[$priority][] = new Reference($serviceId); + $priority = $priority ?? $defaultPriority ?? 0; - continue; - } + if (null !== $indexAttribute && isset($attribute[$indexAttribute])) { + $index = $attribute[$indexAttribute]; + } elseif (null === $defaultIndex && null === $indexAttribute && !$needsIndexes) { + // With partially associative array, insertion to get next key is simpler. + $services[$priority][] = null; + end($services[$priority]); + $defaultIndex = key($services[$priority]); + } elseif (null === $defaultIndex && $defaultIndexMethod) { + $class = $container->getDefinition($serviceId)->getClass(); + $class = $container->getParameterBag()->resolveValue($class) ?: null; - if (!$class) { - $class = $container->getDefinition($serviceId)->getClass(); - $class = $container->getParameterBag()->resolveValue($class) ?: null; - } + if (($r = ($r ?? $container->getReflectionClass($class))) && $r->hasMethod($defaultIndexMethod)) { + if (!($rm = $r->getMethod($defaultIndexMethod))->isStatic()) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); + } - if (null !== $indexAttribute && isset($attributes[0][$indexAttribute])) { - $services[$priority][$attributes[0][$indexAttribute]] = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $attributes[0][$indexAttribute]); + if (!$rm->isPublic()) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); + } - continue; - } + $defaultIndex = $rm->invoke(null); - if (!$r && !$r = $container->getReflectionClass($class)) { - throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $serviceId)); - } + if (!\is_string($defaultIndex)) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return a string, got %s: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, \gettype($defaultIndex), $tagName, $serviceId, $indexAttribute)); + } + } - $class = $r->name; - - if (!$r->hasMethod($defaultIndexMethod)) { - if ($needsIndexes) { - $services[$priority][$serviceId] = new TypedReference($serviceId, $class); - - continue; + $defaultIndex = $defaultIndex ?? $serviceId; } - throw new InvalidArgumentException(sprintf('Method "%s::%s()" not found: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); + $index = $index ?? $defaultIndex; + + $reference = null; + if (!$class || 'stdClass' === $class) { + $reference = new Reference($serviceId); + } elseif ($index === $serviceId) { + $reference = new TypedReference($serviceId, $class); + } else { + $reference = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, \is_string($index) ? $index : null); + } + + $services[$priority][$index] = $reference; } - - if (!($rm = $r->getMethod($defaultIndexMethod))->isStatic()) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); - } - - if (!$rm->isPublic()) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); - } - - $key = $rm->invoke(null); - - if (!\is_string($key)) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return a string, got %s: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, \gettype($key), $tagName, $serviceId, $indexAttribute)); - } - - $services[$priority][$key] = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $key); } if ($services) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index 6b088af2c9..26e50f484b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -314,6 +314,32 @@ class IntegrationTest extends TestCase $this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $param); } + public function testTaggedIteratorWithMultipleIndexAttribute() + { + $container = new ContainerBuilder(); + $container->register(BarTagClass::class) + ->setPublic(true) + ->addTag('foo_bar', ['foo' => 'bar']) + ->addTag('foo_bar', ['foo' => 'bar_duplicate']) + ; + $container->register(FooTagClass::class) + ->setPublic(true) + ->addTag('foo_bar') + ->addTag('foo_bar') + ; + $container->register(FooBarTaggedClass::class) + ->addArgument(new TaggedIteratorArgument('foo_bar', 'foo')) + ->setPublic(true) + ; + + $container->compile(); + + $s = $container->get(FooBarTaggedClass::class); + + $param = iterator_to_array($s->getParam()->getIterator()); + $this->assertSame(['bar' => $container->get(BarTagClass::class), 'bar_duplicate' => $container->get(BarTagClass::class), 'foo_tag_class' => $container->get(FooTagClass::class)], $param); + } + public function testTaggedServiceWithDefaultPriorityMethod() { $container = new ContainerBuilder(); @@ -350,7 +376,7 @@ class IntegrationTest extends TestCase ->addTag('foo_bar') ; $container->register('foo_bar_tagged', FooBarTaggedClass::class) - ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo'))) + ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', null, true))) ->setPublic(true) ; @@ -369,6 +395,40 @@ class IntegrationTest extends TestCase $this->assertSame(['bar' => $container->get('bar_tag'), 'foo_tag_class' => $container->get('foo_tag')], $same); } + public function testTaggedServiceLocatorWithMultipleIndexAttribute() + { + $container = new ContainerBuilder(); + $container->register('bar_tag', BarTagClass::class) + ->setPublic(true) + ->addTag('foo_bar', ['foo' => 'bar']) + ->addTag('foo_bar', ['foo' => 'bar_duplicate']) + ; + $container->register('foo_tag', FooTagClass::class) + ->setPublic(true) + ->addTag('foo_bar') + ->addTag('foo_bar') + ; + $container->register('foo_bar_tagged', FooBarTaggedClass::class) + ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', null, true))) + ->setPublic(true) + ; + + $container->compile(); + + $s = $container->get('foo_bar_tagged'); + + /** @var ServiceLocator $serviceLocator */ + $serviceLocator = $s->getParam(); + $this->assertTrue($s->getParam() instanceof ServiceLocator, sprintf('Wrong instance, should be an instance of ServiceLocator, %s given', \is_object($serviceLocator) ? \get_class($serviceLocator) : \gettype($serviceLocator))); + + $same = [ + 'bar' => $serviceLocator->get('bar'), + 'bar_duplicate' => $serviceLocator->get('bar_duplicate'), + 'foo_tag_class' => $serviceLocator->get('foo_tag_class'), + ]; + $this->assertSame(['bar' => $container->get('bar_tag'), 'bar_duplicate' => $container->get('bar_tag'), 'foo_tag_class' => $container->get('foo_tag')], $same); + } + public function testTaggedServiceLocatorWithIndexAttributeAndDefaultMethod() { $container = new ContainerBuilder(); @@ -381,7 +441,7 @@ class IntegrationTest extends TestCase ->addTag('foo_bar', ['foo' => 'foo']) ; $container->register('foo_bar_tagged', FooBarTaggedClass::class) - ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar'))) + ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar', true))) ->setPublic(true) ;