Skip to content

Commit 5bb81bd

Browse files
yoeunesnicolas-grekas
authored andcommitted
[DependencyInjection] Call default index method when index is not provided by tag
1 parent 638bb2a commit 5bb81bd

File tree

2 files changed

+206
-18
lines changed

2 files changed

+206
-18
lines changed

Compiler/PriorityTaggedServiceTrait.php

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,12 @@ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagNam
5959
continue;
6060
}
6161

62-
$defaultPriority = null;
63-
$defaultIndex = null;
62+
$defaultPriority = $defaultAttributePriority = null;
63+
$defaultIndex = $defaultAttributeIndex = null;
6464
$definition = $container->getDefinition($serviceId);
6565
$class = $definition->getClass();
6666
$class = $container->getParameterBag()->resolveValue($class) ?: null;
6767
$reflector = null !== $class ? $container->getReflectionClass($class) : null;
68-
$loadFromDefaultMethods = $reflector && null !== $defaultPriorityMethod;
6968
$phpAttributes = $definition->isAutoconfigured() && !$definition->hasTag('container.ignore_attributes') ? $reflector?->getAttributes(AsTaggedItem::class) : [];
7069

7170
foreach ($phpAttributes ??= [] as $i => $attribute) {
@@ -74,9 +73,9 @@ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagNam
7473
'priority' => $attribute->priority,
7574
$indexAttribute ?? '' => $attribute->index,
7675
];
77-
if (null === $defaultPriority) {
78-
$defaultPriority = $attribute->priority ?? 0;
79-
$defaultIndex = $attribute->index;
76+
if (null === $defaultAttributePriority) {
77+
$defaultAttributePriority = $attribute->priority ?? 0;
78+
$defaultAttributeIndex = $attribute->index;
8079
}
8180
}
8281
if (1 >= \count($phpAttributes)) {
@@ -93,10 +92,8 @@ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagNam
9392

9493
if (isset($attribute['priority'])) {
9594
$priority = $attribute['priority'];
96-
} elseif ($loadFromDefaultMethods) {
97-
$defaultPriority = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultPriorityMethod, $tagName, 'priority') ?? $defaultPriority;
98-
$defaultIndex = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute) ?? $defaultIndex;
99-
$loadFromDefaultMethods = false;
95+
} elseif (null === $defaultPriority && $defaultPriorityMethod && $reflector) {
96+
$defaultPriority = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultPriorityMethod, $tagName, 'priority') ?? $defaultAttributePriority;
10097
}
10198
$priority ??= $defaultPriority ??= 0;
10299

@@ -108,10 +105,8 @@ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagNam
108105
if (null !== $indexAttribute && isset($attribute[$indexAttribute])) {
109106
$index = $parameterBag->resolveValue($attribute[$indexAttribute]);
110107
}
111-
if (null === $index && $loadFromDefaultMethods) {
112-
$defaultPriority = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultPriorityMethod, $tagName, 'priority') ?? $defaultPriority;
113-
$defaultIndex = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute) ?? $defaultIndex;
114-
$loadFromDefaultMethods = false;
108+
if (null === $index && null === $defaultIndex && $defaultPriorityMethod && $reflector) {
109+
$defaultIndex = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute) ?? $defaultAttributeIndex;
115110
}
116111
$index ??= $defaultIndex ??= $definition->getTag('container.decorator')[0]['id'] ?? $serviceId;
117112

@@ -147,13 +142,10 @@ class PriorityTaggedServiceUtil
147142
{
148143
public static function getDefault(string $serviceId, \ReflectionClass $r, string $defaultMethod, string $tagName, ?string $indexAttribute): string|int|null
149144
{
150-
if (!$r->hasMethod($defaultMethod)) {
145+
if ($r->isInterface() || !$r->hasMethod($defaultMethod)) {
151146
return null;
152147
}
153148

154-
if ($r->isInterface()) {
155-
return null;
156-
}
157149
$class = $r->name;
158150

159151
if (null !== $indexAttribute) {

Tests/Compiler/PriorityTaggedServiceTraitTest.php

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,145 @@ public function testAttributesAreFallbacks()
310310

311311
$this->assertEquals(['z' => new TypedReference('service_attr_first', MultiTagHelloNamedService::class)], $services);
312312
}
313+
314+
public function testTaggedIteratorWithDefaultNameMethod()
315+
{
316+
$container = new ContainerBuilder();
317+
$container->register('service', ClassWithDefaultNameMethod::class)->addTag('my_custom_tag');
318+
319+
$priorityTaggedServiceTraitImplementation = new PriorityTaggedServiceTraitImplementation();
320+
321+
$tag = new TaggedIteratorArgument('my_custom_tag');
322+
$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);
323+
$this->assertEquals([new Reference('service')], $services);
324+
}
325+
326+
public function testIndexedIteratorUsesTagAttributeOverDefaultMethod()
327+
{
328+
$container = new ContainerBuilder();
329+
$container->register('service.a', ServiceWithStaticGetType::class)
330+
->addTag('my_tag', ['type' => 'from_tag']);
331+
332+
$priorityTaggedServiceTraitImplementation = new PriorityTaggedServiceTraitImplementation();
333+
334+
$tag = new TaggedIteratorArgument('my_tag', 'type', 'getType');
335+
$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);
336+
337+
$this->assertArrayHasKey('from_tag', $services);
338+
$this->assertArrayNotHasKey('from_static_method', $services);
339+
$this->assertInstanceOf(TypedReference::class, $services['from_tag']);
340+
$this->assertSame('service.a', (string) $services['from_tag']);
341+
}
342+
343+
public function testIndexedIteratorUsesDefaultMethodAsFallback()
344+
{
345+
$container = new ContainerBuilder();
346+
$container->register('service.a', ServiceWithStaticGetType::class)
347+
->addTag('my_tag');
348+
349+
$priorityTaggedServiceTraitImplementation = new PriorityTaggedServiceTraitImplementation();
350+
351+
$tag = new TaggedIteratorArgument('my_tag', 'type', 'getType');
352+
$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);
353+
354+
$this->assertArrayHasKey('from_static_method', $services);
355+
$this->assertArrayNotHasKey('from_tag', $services);
356+
$this->assertInstanceOf(TypedReference::class, $services['from_static_method']);
357+
}
358+
359+
public function testIndexedIteratorUsesTagIndexAndDefaultPriorityMethod()
360+
{
361+
$container = new ContainerBuilder();
362+
363+
$container->register('service.a', ServiceWithStaticPriority::class)
364+
->addTag('my_tag', ['type' => 'tag_index']);
365+
366+
$container->register('service.b', \stdClass::class)
367+
->addTag('my_tag', ['type' => 'another_index']);
368+
369+
$priorityTaggedServiceTraitImplementation = new PriorityTaggedServiceTraitImplementation();
370+
371+
$tag = new TaggedIteratorArgument('my_tag', 'type', null, 'getPriority');
372+
$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);
373+
374+
$this->assertArrayHasKey('tag_index', $services);
375+
$this->assertSame('service.a', (string) $services['tag_index']);
376+
377+
$this->assertSame(['tag_index', 'another_index'], array_keys($services));
378+
}
379+
380+
public function testTaggedLocatorWithProvidedIndexAttributeAndNonStaticDefaultIndexMethod()
381+
{
382+
$container = new ContainerBuilder();
383+
$container->register('service', NonStaticDefaultIndexClass::class)
384+
->addTag('my_custom_tag', ['type' => 'foo']);
385+
386+
$priorityTaggedServiceTraitImplementation = new PriorityTaggedServiceTraitImplementation();
387+
$tag = new TaggedIteratorArgument('my_custom_tag', 'type', 'getType');
388+
389+
$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);
390+
$this->assertEquals(['foo' => new TypedReference('service', NonStaticDefaultIndexClass::class)], $services);
391+
}
392+
393+
public function testTaggedLocatorWithoutIndexAttributeAndNonStaticDefaultIndexMethod()
394+
{
395+
$this->expectException(InvalidArgumentException::class);
396+
$this->expectExceptionMessage(\sprintf('Either method "%s::getType()" should be static or tag "my_custom_tag" on service "service" is missing attribute "type".', NonStaticDefaultIndexClass::class));
397+
398+
$container = new ContainerBuilder();
399+
$container->register('service', NonStaticDefaultIndexClass::class)
400+
->addTag('my_custom_tag');
401+
402+
$priorityTaggedServiceTraitImplementation = new PriorityTaggedServiceTraitImplementation();
403+
$tag = new TaggedIteratorArgument('my_custom_tag', 'type', 'getType');
404+
405+
$priorityTaggedServiceTraitImplementation->test($tag, $container);
406+
}
407+
408+
public function testMergingAsTaggedItemWithEmptyTagAndNonStaticBusinessMethod()
409+
{
410+
$container = new ContainerBuilder();
411+
$container->register('service', AsTaggedItemClassWithBusinessMethod::class)
412+
->setAutoconfigured(true)
413+
->addTag('my_custom_tag');
414+
415+
(new ResolveInstanceofConditionalsPass())->process($container);
416+
417+
$priorityTaggedServiceTraitImplementation = new PriorityTaggedServiceTraitImplementation();
418+
$tag = new TaggedIteratorArgument('my_custom_tag', 'index');
419+
420+
$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);
421+
$this->assertEquals(['bar' => new TypedReference('service', AsTaggedItemClassWithBusinessMethod::class)], $services);
422+
}
423+
424+
public function testPriorityFallbackWithoutIndexAndStaticPriorityMethod()
425+
{
426+
$container = new ContainerBuilder();
427+
$container->register('service', StaticPriorityClass::class)
428+
->addTag('my_custom_tag');
429+
430+
$priorityTaggedServiceTraitImplementation = new PriorityTaggedServiceTraitImplementation();
431+
$tag = new TaggedIteratorArgument('my_custom_tag', null, null, false, 'getDefaultPriority');
432+
433+
$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);
434+
$this->assertEquals([new Reference('service')], $services);
435+
}
436+
437+
public function testMultiTagsWithMixedAttributesAndNonStaticDefault()
438+
{
439+
$container = new ContainerBuilder();
440+
$container->register('service', MultiTagNonStaticClass::class)
441+
->addTag('my_custom_tag', ['type' => 'foo'])
442+
->addTag('my_custom_tag');
443+
444+
$priorityTaggedServiceTraitImplementation = new PriorityTaggedServiceTraitImplementation();
445+
$tag = new TaggedIteratorArgument('my_custom_tag', 'type', 'getType');
446+
447+
$services = $priorityTaggedServiceTraitImplementation->test($tag, $container);
448+
$this->assertCount(2, $services);
449+
$this->assertArrayHasKey('foo', $services);
450+
$this->assertArrayHasKey('default', $services);
451+
}
313452
}
314453

315454
class PriorityTaggedServiceTraitImplementation
@@ -343,3 +482,60 @@ interface HelloInterface
343482
{
344483
public static function getFooBar(): string;
345484
}
485+
486+
class ClassWithDefaultNameMethod
487+
{
488+
public function getDefaultName(): string
489+
{
490+
return 'foo';
491+
}
492+
}
493+
494+
class ServiceWithStaticGetType
495+
{
496+
public static function getType(): string
497+
{
498+
return 'from_static_method';
499+
}
500+
}
501+
502+
class ServiceWithStaticPriority
503+
{
504+
public static function getPriority(): int
505+
{
506+
return 10;
507+
}
508+
}
509+
510+
class NonStaticDefaultIndexClass
511+
{
512+
public function getType(): string
513+
{
514+
return 'foo';
515+
}
516+
}
517+
518+
#[AsTaggedItem(index: 'bar')]
519+
class AsTaggedItemClassWithBusinessMethod
520+
{
521+
public function getDefaultName(): string
522+
{
523+
return 'ignored';
524+
}
525+
}
526+
527+
class StaticPriorityClass
528+
{
529+
public static function getDefaultPriority(): int
530+
{
531+
return 10;
532+
}
533+
}
534+
535+
class MultiTagNonStaticClass
536+
{
537+
public static function getType(): string
538+
{
539+
return 'default';
540+
}
541+
}

0 commit comments

Comments
 (0)