diff --git a/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php b/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php index f3b3621d89..b6a06f7de7 100644 --- a/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php +++ b/src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php @@ -19,16 +19,33 @@ namespace Symfony\Component\DependencyInjection\Argument; class TaggedIteratorArgument extends IteratorArgument { private $tag; + private $indexAttribute; + private $defaultIndexMethod; - public function __construct(string $tag) + public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null) { parent::__construct([]); $this->tag = $tag; + $this->indexAttribute = $indexAttribute ?: null; + + if ($indexAttribute) { + $this->defaultIndexMethod = $defaultIndexMethod ?: ('getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute))).'Name'); + } } public function getTag() { return $this->tag; } + + public function getIndexAttribute(): ?string + { + return $this->indexAttribute; + } + + public function getDefaultIndexMethod(): ?string + { + return $this->defaultIndexMethod; + } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php index adb99f0d54..f9046d3c32 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php @@ -11,7 +11,9 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; /** @@ -31,18 +33,59 @@ trait PriorityTaggedServiceTrait * @see https://bugs.php.net/bug.php?id=53710 * @see https://bugs.php.net/bug.php?id=60926 * - * @param string $tagName - * @param ContainerBuilder $container + * @param string|TaggedIteratorArgument $tagName + * @param ContainerBuilder $container * * @return Reference[] */ private function findAndSortTaggedServices($tagName, ContainerBuilder $container) { + $indexAttribute = $defaultIndexMethod = null; + if ($tagName instanceof TaggedIteratorArgument) { + $indexAttribute = $tagName->getIndexAttribute(); + $defaultIndexMethod = $tagName->getDefaultIndexMethod(); + $tagName = $tagName->getTag(); + } $services = []; foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) { $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; - $services[$priority][] = new Reference($serviceId); + + if (null === $indexAttribute) { + $services[$priority][] = new Reference($serviceId); + + continue; + } + + if (isset($attributes[0][$indexAttribute])) { + $services[$priority][$attributes[0][$indexAttribute]] = new Reference($serviceId); + + continue; + } + + if (!$r = $container->getReflectionClass($class = $container->getDefinition($serviceId)->getClass())) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $serviceId)); + } + + if (!$r->hasMethod($defaultIndexMethod)) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" not found: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); + } + + 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 Reference($serviceId); } if ($services) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveTaggedIteratorArgumentPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveTaggedIteratorArgumentPass.php index 009cee9bf5..a4305722f7 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveTaggedIteratorArgumentPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveTaggedIteratorArgumentPass.php @@ -31,7 +31,7 @@ class ResolveTaggedIteratorArgumentPass extends AbstractRecursivePass return parent::processValue($value, $isRoot); } - $value->setValues($this->findAndSortTaggedServices($value->getTag(), $this->container)); + $value->setValues($this->findAndSortTaggedServices($value, $this->container)); return $value; } diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php index f1593e4f69..f75ba1be85 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php @@ -116,9 +116,9 @@ function iterator(array $values): IteratorArgument /** * Creates a lazy iterator by tag name. */ -function tagged(string $tag): TaggedIteratorArgument +function tagged(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null): TaggedIteratorArgument { - return new TaggedIteratorArgument($tag); + return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod); } /** diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index e14d38d49d..700cd79be5 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -710,11 +710,17 @@ class YamlFileLoader extends FileLoader } } if ('tagged' === $value->getTag()) { - if (!\is_string($argument) || !$argument) { - throw new InvalidArgumentException(sprintf('"!tagged" tag only accepts non empty string in "%s".', $file)); + if (\is_string($argument) && $argument) { + return new TaggedIteratorArgument($argument); } + if (\is_array($argument) && isset($argument['name']) && $argument['name']) { + if (array_diff(array_keys($argument), ['name', 'index_by', 'default_index_method'])) { + throw new InvalidArgumentException('"!tagged" tag contains unsupported keys. Supported are: "name, index_by, default_index_method".'); + } - return new TaggedIteratorArgument($argument); + return new TaggedIteratorArgument($argument['name'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null); + } + throw new InvalidArgumentException(sprintf('"!tagged" tag only accepts a non empty string or an array with a key "name" in "%s".', $file)); } if ('service' === $value->getTag()) { if ($isParameter) {