bug #35342 [DI] Fix support for multiple tags for locators and iterators (Alexandre Parent)

This PR was merged into the 4.4 branch.

Discussion
----------

[DI] Fix support for multiple tags for locators and iterators

| Q             | A
| ------------- | ---
| Branch?       | 4.3
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #34462, Fix #35326
| License       | MIT
| Doc PR        | none

Fix PriorityTaggedServiceTrait::findAndSortTaggedServices to work with multiple explicitely tagged services as would be expected by !tagged_locator. Also reorganize PriorityTaggedServiceTrait::findAndSortTaggedServices to be simpler and easier to understand.

Commits
-------

6fc91eb192 [DI] Fix support for multiple tags for locators and iterators
This commit is contained in:
Nicolas Grekas 2020-02-04 16:46:39 +01:00
commit a59ce75722
2 changed files with 121 additions and 58 deletions

View File

@ -54,78 +54,81 @@ trait PriorityTaggedServiceTrait
foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) { foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) {
$class = $r = null; $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)) { $defaultPriority = null;
if (!($rm = $r->getMethod($defaultPriorityMethod))->isStatic()) { $defaultIndex = null;
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId));
}
if (!$rm->isPublic()) { foreach ($attributes as $attribute) {
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId)); $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)) { if (($r = ($r ?? $container->getReflectionClass($class))) && $r->hasMethod($defaultPriorityMethod)) {
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 (!($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) { $priority = $priority ?? $defaultPriority ?? 0;
$services[$priority][] = new Reference($serviceId);
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) { if (($r = ($r ?? $container->getReflectionClass($class))) && $r->hasMethod($defaultIndexMethod)) {
$class = $container->getDefinition($serviceId)->getClass(); if (!($rm = $r->getMethod($defaultIndexMethod))->isStatic()) {
$class = $container->getParameterBag()->resolveValue($class) ?: null; 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])) { if (!$rm->isPublic()) {
$services[$priority][$attributes[0][$indexAttribute]] = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $attributes[0][$indexAttribute]); 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)) { if (!\is_string($defaultIndex)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $serviceId)); 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; $defaultIndex = $defaultIndex ?? $serviceId;
if (!$r->hasMethod($defaultIndexMethod)) {
if ($needsIndexes) {
$services[$priority][$serviceId] = new TypedReference($serviceId, $class);
continue;
} }
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) { if ($services) {

View File

@ -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); $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() public function testTaggedServiceWithDefaultPriorityMethod()
{ {
$container = new ContainerBuilder(); $container = new ContainerBuilder();
@ -350,7 +376,7 @@ class IntegrationTest extends TestCase
->addTag('foo_bar') ->addTag('foo_bar')
; ;
$container->register('foo_bar_tagged', FooBarTaggedClass::class) $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) ->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); $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() public function testTaggedServiceLocatorWithIndexAttributeAndDefaultMethod()
{ {
$container = new ContainerBuilder(); $container = new ContainerBuilder();
@ -381,7 +441,7 @@ class IntegrationTest extends TestCase
->addTag('foo_bar', ['foo' => 'foo']) ->addTag('foo_bar', ['foo' => 'foo'])
; ;
$container->register('foo_bar_tagged', FooBarTaggedClass::class) $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) ->setPublic(true)
; ;