Skip to content

Commit ac8e19e

Browse files
alex-devnicolas-grekas
authored andcommitted
[DI] Fix support for multiple tags for locators and iterators
1 parent e8258bd commit ac8e19e

File tree

2 files changed

+123
-60
lines changed

2 files changed

+123
-60
lines changed

Compiler/PriorityTaggedServiceTrait.php

Lines changed: 61 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -54,78 +54,81 @@ private function findAndSortTaggedServices($tagName, ContainerBuilder $container
5454

5555
foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) {
5656
$class = $r = null;
57-
$priority = 0;
58-
if (isset($attributes[0]['priority'])) {
59-
$priority = $attributes[0]['priority'];
60-
} elseif ($defaultPriorityMethod) {
61-
$class = $container->getDefinition($serviceId)->getClass();
62-
$class = $container->getParameterBag()->resolveValue($class) ?: null;
63-
64-
if (($r = $container->getReflectionClass($class)) && $r->hasMethod($defaultPriorityMethod)) {
65-
if (!($rm = $r->getMethod($defaultPriorityMethod))->isStatic()) {
66-
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId));
67-
}
68-
69-
if (!$rm->isPublic()) {
70-
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId));
71-
}
72-
73-
$priority = $rm->invoke(null);
74-
75-
if (!\is_int($priority)) {
76-
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return an integer, got %s: tag "%s" on service "%s".', $class, $defaultPriorityMethod, \gettype($priority), $tagName, $serviceId));
77-
}
78-
}
79-
}
80-
81-
if (null === $indexAttribute && !$needsIndexes) {
82-
$services[$priority][] = new Reference($serviceId);
83-
84-
continue;
85-
}
8657

87-
if (!$class) {
88-
$class = $container->getDefinition($serviceId)->getClass();
89-
$class = $container->getParameterBag()->resolveValue($class) ?: null;
90-
}
58+
$defaultPriority = null;
59+
$defaultIndex = null;
9160

92-
if (null !== $indexAttribute && isset($attributes[0][$indexAttribute])) {
93-
$services[$priority][$attributes[0][$indexAttribute]] = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $attributes[0][$indexAttribute]);
61+
foreach ($attributes as $attribute) {
62+
$index = $priority = null;
9463

95-
continue;
96-
}
64+
if (isset($attribute['priority'])) {
65+
$priority = $attribute['priority'];
66+
} elseif (null === $defaultPriority && $defaultPriorityMethod) {
67+
$class = $container->getDefinition($serviceId)->getClass();
68+
$class = $container->getParameterBag()->resolveValue($class) ?: null;
9769

98-
if (!$r && !$r = $container->getReflectionClass($class)) {
99-
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $serviceId));
100-
}
70+
if (($r = ($r ?? $container->getReflectionClass($class))) && $r->hasMethod($defaultPriorityMethod)) {
71+
if (!($rm = $r->getMethod($defaultPriorityMethod))->isStatic()) {
72+
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId));
73+
}
10174

102-
$class = $r->name;
75+
if (!$rm->isPublic()) {
76+
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId));
77+
}
10378

104-
if (!$r->hasMethod($defaultIndexMethod)) {
105-
if ($needsIndexes) {
106-
$services[$priority][$serviceId] = new TypedReference($serviceId, $class);
79+
$defaultPriority = $rm->invoke(null);
10780

108-
continue;
81+
if (!\is_int($defaultPriority)) {
82+
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return an integer, got %s: tag "%s" on service "%s".', $class, $defaultPriorityMethod, \gettype($priority), $tagName, $serviceId));
83+
}
84+
}
10985
}
11086

111-
throw new InvalidArgumentException(sprintf('Method "%s::%s()" not found: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute));
112-
}
87+
$priority = $priority ?? $defaultPriority ?? 0;
88+
89+
if (null !== $indexAttribute && isset($attribute[$indexAttribute])) {
90+
$index = $attribute[$indexAttribute];
91+
} elseif (null === $defaultIndex && null === $indexAttribute && !$needsIndexes) {
92+
// With partially associative array, insertion to get next key is simpler.
93+
$services[$priority][] = null;
94+
end($services[$priority]);
95+
$defaultIndex = key($services[$priority]);
96+
} elseif (null === $defaultIndex && $defaultIndexMethod) {
97+
$class = $container->getDefinition($serviceId)->getClass();
98+
$class = $container->getParameterBag()->resolveValue($class) ?: null;
99+
100+
if (($r = ($r ?? $container->getReflectionClass($class))) && $r->hasMethod($defaultIndexMethod)) {
101+
if (!($rm = $r->getMethod($defaultIndexMethod))->isStatic()) {
102+
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute));
103+
}
104+
105+
if (!$rm->isPublic()) {
106+
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute));
107+
}
108+
109+
$defaultIndex = $rm->invoke(null);
110+
111+
if (!\is_string($defaultIndex)) {
112+
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));
113+
}
114+
}
113115

114-
if (!($rm = $r->getMethod($defaultIndexMethod))->isStatic()) {
115-
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute));
116-
}
116+
$defaultIndex = $defaultIndex ?? $serviceId;
117+
}
117118

118-
if (!$rm->isPublic()) {
119-
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute));
120-
}
119+
$index = $index ?? $defaultIndex;
121120

122-
$key = $rm->invoke(null);
121+
$reference = null;
122+
if (!$class || 'stdClass' === $class) {
123+
$reference = new Reference($serviceId);
124+
} elseif ($index === $serviceId) {
125+
$reference = new TypedReference($serviceId, $class);
126+
} else {
127+
$reference = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, \is_string($index) ? $index : null);
128+
}
123129

124-
if (!\is_string($key)) {
125-
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));
130+
$services[$priority][$index] = $reference;
126131
}
127-
128-
$services[$priority][$key] = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $key);
129132
}
130133

131134
if ($services) {

Tests/Compiler/IntegrationTest.php

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,32 @@ public function testTaggedServiceWithIndexAttributeAndDefaultMethod()
314314
$this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $param);
315315
}
316316

317+
public function testTaggedIteratorWithMultipleIndexAttribute()
318+
{
319+
$container = new ContainerBuilder();
320+
$container->register(BarTagClass::class)
321+
->setPublic(true)
322+
->addTag('foo_bar', ['foo' => 'bar'])
323+
->addTag('foo_bar', ['foo' => 'bar_duplicate'])
324+
;
325+
$container->register(FooTagClass::class)
326+
->setPublic(true)
327+
->addTag('foo_bar')
328+
->addTag('foo_bar')
329+
;
330+
$container->register(FooBarTaggedClass::class)
331+
->addArgument(new TaggedIteratorArgument('foo_bar', 'foo'))
332+
->setPublic(true)
333+
;
334+
335+
$container->compile();
336+
337+
$s = $container->get(FooBarTaggedClass::class);
338+
339+
$param = iterator_to_array($s->getParam()->getIterator());
340+
$this->assertSame(['bar' => $container->get(BarTagClass::class), 'bar_duplicate' => $container->get(BarTagClass::class), 'foo_tag_class' => $container->get(FooTagClass::class)], $param);
341+
}
342+
317343
public function testTaggedServiceWithDefaultPriorityMethod()
318344
{
319345
$container = new ContainerBuilder();
@@ -350,7 +376,7 @@ public function testTaggedServiceLocatorWithIndexAttribute()
350376
->addTag('foo_bar')
351377
;
352378
$container->register('foo_bar_tagged', FooBarTaggedClass::class)
353-
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo')))
379+
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', null, true)))
354380
->setPublic(true)
355381
;
356382

@@ -369,6 +395,40 @@ public function testTaggedServiceLocatorWithIndexAttribute()
369395
$this->assertSame(['bar' => $container->get('bar_tag'), 'foo_tag_class' => $container->get('foo_tag')], $same);
370396
}
371397

398+
public function testTaggedServiceLocatorWithMultipleIndexAttribute()
399+
{
400+
$container = new ContainerBuilder();
401+
$container->register('bar_tag', BarTagClass::class)
402+
->setPublic(true)
403+
->addTag('foo_bar', ['foo' => 'bar'])
404+
->addTag('foo_bar', ['foo' => 'bar_duplicate'])
405+
;
406+
$container->register('foo_tag', FooTagClass::class)
407+
->setPublic(true)
408+
->addTag('foo_bar')
409+
->addTag('foo_bar')
410+
;
411+
$container->register('foo_bar_tagged', FooBarTaggedClass::class)
412+
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', null, true)))
413+
->setPublic(true)
414+
;
415+
416+
$container->compile();
417+
418+
$s = $container->get('foo_bar_tagged');
419+
420+
/** @var ServiceLocator $serviceLocator */
421+
$serviceLocator = $s->getParam();
422+
$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)));
423+
424+
$same = [
425+
'bar' => $serviceLocator->get('bar'),
426+
'bar_duplicate' => $serviceLocator->get('bar_duplicate'),
427+
'foo_tag_class' => $serviceLocator->get('foo_tag_class'),
428+
];
429+
$this->assertSame(['bar' => $container->get('bar_tag'), 'bar_duplicate' => $container->get('bar_tag'), 'foo_tag_class' => $container->get('foo_tag')], $same);
430+
}
431+
372432
public function testTaggedServiceLocatorWithIndexAttributeAndDefaultMethod()
373433
{
374434
$container = new ContainerBuilder();
@@ -381,7 +441,7 @@ public function testTaggedServiceLocatorWithIndexAttributeAndDefaultMethod()
381441
->addTag('foo_bar', ['foo' => 'foo'])
382442
;
383443
$container->register('foo_bar_tagged', FooBarTaggedClass::class)
384-
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar')))
444+
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar', true)))
385445
->setPublic(true)
386446
;
387447

0 commit comments

Comments
 (0)