Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cbfbe0e

Browse files
Support indexing tagged locators by FQCN as fallback
1 parent 4cdcff7 commit cbfbe0e

11 files changed

+106
-78
lines changed

src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,22 @@ class TaggedIteratorArgument extends IteratorArgument
2121
private $tag;
2222
private $indexAttribute;
2323
private $defaultIndexMethod;
24+
private $useFqcnAsFallback = false;
2425

2526
/**
2627
* @param string $tag The name of the tag identifying the target services
2728
* @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection
2829
* @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute
30+
* @param bool $useFqcnAsFallback Whether the FQCN of the service should be used as index when neither the attribute nor the method are defined
2931
*/
30-
public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null)
32+
public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $useFqcnAsFallback = false)
3133
{
3234
parent::__construct([]);
3335

3436
$this->tag = $tag;
35-
36-
if ($indexAttribute) {
37-
$this->indexAttribute = $indexAttribute;
38-
$this->defaultIndexMethod = $defaultIndexMethod ?: ('getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute))).'Name');
39-
}
37+
$this->indexAttribute = $indexAttribute;
38+
$this->defaultIndexMethod = $defaultIndexMethod ?: ('getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute ?? ''))).'Name');
39+
$this->useFqcnAsFallback = $useFqcnAsFallback;
4040
}
4141

4242
public function getTag()
@@ -53,4 +53,9 @@ public function getDefaultIndexMethod(): ?string
5353
{
5454
return $this->defaultIndexMethod;
5555
}
56+
57+
public function useFqcnAsFallback(): bool
58+
{
59+
return $this->useFqcnAsFallback;
60+
}
5661
}

src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\DependencyInjection\ContainerBuilder;
1616
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1717
use Symfony\Component\DependencyInjection\Reference;
18+
use Symfony\Component\DependencyInjection\TypedReference;
1819

1920
/**
2021
* Trait that allows a generic method to find and sort service by priority option in the tag.
@@ -40,34 +41,47 @@ trait PriorityTaggedServiceTrait
4041
*/
4142
private function findAndSortTaggedServices($tagName, ContainerBuilder $container)
4243
{
43-
$indexAttribute = $defaultIndexMethod = null;
44+
$indexAttribute = $defaultIndexMethod = $useFqcnAsFallback = null;
45+
4446
if ($tagName instanceof TaggedIteratorArgument) {
4547
$indexAttribute = $tagName->getIndexAttribute();
4648
$defaultIndexMethod = $tagName->getDefaultIndexMethod();
49+
$useFqcnAsFallback = $tagName->useFqcnAsFallback();
4750
$tagName = $tagName->getTag();
4851
}
52+
4953
$services = [];
5054

5155
foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) {
5256
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
5357

54-
if (null === $indexAttribute) {
58+
if (null === $indexAttribute && !$useFqcnAsFallback) {
5559
$services[$priority][] = new Reference($serviceId);
5660

5761
continue;
5862
}
5963

60-
if (isset($attributes[0][$indexAttribute])) {
61-
$services[$priority][$attributes[0][$indexAttribute]] = new Reference($serviceId);
64+
$class = $container->getDefinition($serviceId)->getClass();
65+
66+
if (null !== $indexAttribute && isset($attributes[0][$indexAttribute])) {
67+
$services[$priority][$attributes[0][$indexAttribute]] = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $attributes[0][$indexAttribute]);
6268

6369
continue;
6470
}
6571

66-
if (!$r = $container->getReflectionClass($class = $container->getDefinition($serviceId)->getClass())) {
72+
if (!$r = $container->getReflectionClass($class)) {
6773
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $serviceId));
6874
}
6975

76+
$class = $r->name;
77+
7078
if (!$r->hasMethod($defaultIndexMethod)) {
79+
if ($useFqcnAsFallback) {
80+
$services[$priority][$class] = new TypedReference($serviceId, $class);
81+
82+
continue;
83+
}
84+
7185
throw new InvalidArgumentException(sprintf('Method "%s::%s()" not found: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute));
7286
}
7387

@@ -85,7 +99,7 @@ private function findAndSortTaggedServices($tagName, ContainerBuilder $container
8599
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));
86100
}
87101

88-
$services[$priority][$key] = new Reference($serviceId);
102+
$services[$priority][$key] = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $key);
89103
}
90104

91105
if ($services) {

src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -280,36 +280,26 @@ private function convertParameters(array $parameters, $type, \DOMElement $parent
280280
if ($value instanceof ServiceClosureArgument) {
281281
$value = $value->getValues()[0];
282282
}
283-
if (\is_array($value)) {
283+
if (\is_array($tag = $value)) {
284284
$element->setAttribute('type', 'collection');
285285
$this->convertParameters($value, $type, $element, 'key');
286-
} elseif ($value instanceof TaggedIteratorArgument) {
287-
$element->setAttribute('type', 'tagged');
288-
$element->setAttribute('tag', $value->getTag());
286+
} elseif ($value instanceof TaggedIteratorArgument || ($value instanceof ServiceLocatorArgument && $tag = $value->getTaggedIteratorArgument())) {
287+
$element->setAttribute('type', $value instanceof TaggedIteratorArgument ? 'tagged' : 'tagged_locator');
288+
$element->setAttribute('tag', $tag->getTag());
289289

290-
if (null !== $value->getIndexAttribute()) {
291-
$element->setAttribute('index-by', $value->getIndexAttribute());
292-
}
290+
if (null !== $tag->getIndexAttribute()) {
291+
$element->setAttribute('index-by', $tag->getIndexAttribute());
293292

294-
if (null !== $value->getDefaultIndexMethod()) {
295-
$element->setAttribute('default-index-method', $value->getDefaultIndexMethod());
293+
if (null !== $tag->getDefaultIndexMethod()) {
294+
$element->setAttribute('default-index-method', $tag->getDefaultIndexMethod());
295+
}
296296
}
297297
} elseif ($value instanceof IteratorArgument) {
298298
$element->setAttribute('type', 'iterator');
299299
$this->convertParameters($value->getValues(), $type, $element, 'key');
300300
} elseif ($value instanceof ServiceLocatorArgument) {
301-
if ($value->getTaggedIteratorArgument()) {
302-
$element->setAttribute('type', 'tagged_locator');
303-
$element->setAttribute('tag', $value->getTaggedIteratorArgument()->getTag());
304-
$element->setAttribute('index-by', $value->getTaggedIteratorArgument()->getIndexAttribute());
305-
306-
if (null !== $value->getTaggedIteratorArgument()->getDefaultIndexMethod()) {
307-
$element->setAttribute('default-index-method', $value->getTaggedIteratorArgument()->getDefaultIndexMethod());
308-
}
309-
} else {
310-
$element->setAttribute('type', 'service_locator');
311-
$this->convertParameters($value->getValues(), $type, $element, 'key');
312-
}
301+
$element->setAttribute('type', 'service_locator');
302+
$this->convertParameters($value->getValues(), $type, $element, 'key');
313303
} elseif ($value instanceof Reference) {
314304
$element->setAttribute('type', 'service');
315305
$element->setAttribute('id', (string) $value);

src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -232,38 +232,29 @@ private function dumpValue($value)
232232
$value = $value->getValues()[0];
233233
}
234234
if ($value instanceof ArgumentInterface) {
235-
if ($value instanceof TaggedIteratorArgument) {
236-
if (null !== $value->getIndexAttribute()) {
237-
$taggedValueContent = [
238-
'tag' => $value->getTag(),
239-
'index_by' => $value->getIndexAttribute(),
235+
$tag = $value;
236+
237+
if ($value instanceof TaggedIteratorArgument || ($value instanceof ServiceLocatorArgument && $tag = $value->getTaggedIteratorArgument())) {
238+
if (null === $tag->getIndexAttribute()) {
239+
$content = $tag->getTag();
240+
} else {
241+
$content = [
242+
'tag' => $tag->getTag(),
243+
'index_by' => $tag->getIndexAttribute(),
240244
];
241245

242-
if (null !== $value->getDefaultIndexMethod()) {
243-
$taggedValueContent['default_index_method'] = $value->getDefaultIndexMethod();
246+
if (null !== $tag->getDefaultIndexMethod()) {
247+
$content['default_index_method'] = $tag->getDefaultIndexMethod();
244248
}
245-
246-
return new TaggedValue('tagged', $taggedValueContent);
247249
}
248250

249-
return new TaggedValue('tagged', $value->getTag());
251+
return new TaggedValue($value instanceof TaggedIteratorArgument ? 'tagged' : 'tagged_locator', $content);
250252
}
253+
251254
if ($value instanceof IteratorArgument) {
252255
$tag = 'iterator';
253256
} elseif ($value instanceof ServiceLocatorArgument) {
254257
$tag = 'service_locator';
255-
if ($value->getTaggedIteratorArgument()) {
256-
$taggedValueContent = [
257-
'tag' => $value->getTaggedIteratorArgument()->getTag(),
258-
'index_by' => $value->getTaggedIteratorArgument()->getIndexAttribute(),
259-
];
260-
261-
if (null !== $value->getTaggedIteratorArgument()->getDefaultIndexMethod()) {
262-
$taggedValueContent['default_index_method'] = $value->getTaggedIteratorArgument()->getDefaultIndexMethod();
263-
}
264-
265-
return new TaggedValue('tagged_locator', $taggedValueContent);
266-
}
267258
} else {
268259
throw new RuntimeException(sprintf('Unspecified Yaml tag for type "%s".', \get_class($value)));
269260
}

src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ function tagged(string $tag, string $indexAttribute = null, string $defaultIndex
126126
*/
127127
function tagged_locator(string $tag, string $indexAttribute, string $defaultIndexMethod = null): ServiceLocatorArgument
128128
{
129-
return new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod));
129+
return new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, true));
130130
}
131131

132132
/**

src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -533,19 +533,20 @@ private function getArgumentsAsPhp(\DOMElement $node, $name, $file, $lowercase =
533533
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="service_locator" only accepts maps of type="service" references in "%s".', $name, $file));
534534
}
535535
break;
536+
case 'tagged':
536537
case 'tagged_locator':
537-
if (!$arg->getAttribute('tag') || !$arg->getAttribute('index-by')) {
538-
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="tagged_locator" has no or empty "tag" or "index-by" attributes in "%s".', $name, $file));
539-
}
538+
$type = $arg->getAttribute('type');
539+
$forLocator = 'tagged_locator' === $type;
540540

541-
$arguments[$key] = new ServiceLocatorArgument(new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by'), $arg->getAttribute('default-index-method') ?: null));
542-
break;
543-
case 'tagged':
544541
if (!$arg->getAttribute('tag')) {
545-
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="tagged" has no or empty "tag" attribute in "%s".', $name, $file));
542+
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="%s" has no or empty "tag" attribute in "%s".', $name, $type, $file));
546543
}
547544

548-
$arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null);
545+
$arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator);
546+
547+
if ($forLocator) {
548+
$arguments[$key] = new ServiceLocatorArgument($arguments[$key]);
549+
}
549550
break;
550551
case 'binary':
551552
if (false === $value = base64_decode($arg->nodeValue)) {

src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -711,31 +711,28 @@ private function resolveServices($value, $file, $isParameter = false)
711711
throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps of "@service" references in "%s".', $file));
712712
}
713713
}
714-
if ('tagged' === $value->getTag()) {
714+
if (\in_array($value->getTag(), ['tagged', 'tagged_locator'], true)) {
715+
$forLocator = 'tagged_locator' === $value->getTag();
716+
715717
if (\is_string($argument) && $argument) {
716-
return new TaggedIteratorArgument($argument);
718+
return new TaggedIteratorArgument($argument, null, null, $forLocator);
717719
}
718720

719721
if (\is_array($argument) && isset($argument['tag']) && $argument['tag']) {
720722
if ($diff = array_diff(array_keys($argument), ['tag', 'index_by', 'default_index_method'])) {
721-
throw new InvalidArgumentException(sprintf('"!tagged" tag contains unsupported key "%s"; supported ones are "tag", "index_by" and "default_index_method".', implode('"", "', $diff)));
723+
throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "tag", "index_by" and "default_index_method".', $value->getTag(), implode('"", "', $diff)));
722724
}
723725

724-
return new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null);
725-
}
726+
$argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'], $argument['default_index_method'] ?? null, $forLocator);
726727

727-
throw new InvalidArgumentException(sprintf('"!tagged" tags only accept a non empty string or an array with a key "tag" in "%s".', $file));
728-
}
729-
if ('tagged_locator' === $value->getTag()) {
730-
if (\is_array($argument) && isset($argument['tag'], $argument['index_by']) && $argument['tag'] && $argument['index_by']) {
731-
if ($diff = array_diff(array_keys($argument), ['tag', 'index_by', 'default_index_method'])) {
732-
throw new InvalidArgumentException(sprintf('"!tagged_locator" tag contains unsupported key "%s"; supported ones are "tag", "index_by" and "default_index_method".', implode('"", "', $diff)));
728+
if ($forLocator) {
729+
$argument = new ServiceLocatorArgument($argument);
733730
}
734731

735-
return new ServiceLocatorArgument(new TaggedIteratorArgument($argument['tag'], $argument['index_by'], $argument['default_index_method'] ?? null));
732+
return $argument;
736733
}
737734

738-
throw new InvalidArgumentException(sprintf('"!tagged_locator" tags only accept an array with at least keys "tag" and "index_by" in "%s".', $file));
735+
throw new InvalidArgumentException(sprintf('"!%s" tags only accept a non empty string or an array with a key "tag" in "%s".', $value->getTag(), $file));
739736
}
740737
if ('service' === $value->getTag()) {
741738
if ($isParameter) {

src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,32 @@ public function testTaggedServiceLocatorWithIndexAttributeAndDefaultMethod()
350350
];
351351
$this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $same);
352352
}
353+
354+
public function testTaggedServiceLocatorWithFqcnFallback()
355+
{
356+
$container = new ContainerBuilder();
357+
$container->register(BarTagClass::class)
358+
->setPublic(true)
359+
->addTag('foo_bar')
360+
;
361+
$container->register(FooBarTaggedClass::class)
362+
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', null, null, true)))
363+
->setPublic(true)
364+
;
365+
366+
$container->compile();
367+
368+
$s = $container->get(FooBarTaggedClass::class);
369+
370+
/** @var ServiceLocator $serviceLocator */
371+
$serviceLocator = $s->getParam();
372+
$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)));
373+
374+
$same = [
375+
BarTagClass::class => $serviceLocator->get(BarTagClass::class),
376+
];
377+
$this->assertSame([BarTagClass::class => $container->get(BarTagClass::class)], $same);
378+
}
353379
}
354380

355381
class ServiceSubscriberStub implements ServiceSubscriberInterface

src/Symfony/Component/DependencyInjection/Tests/Compiler/PriorityTaggedServiceTraitTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,6 @@ class PriorityTaggedServiceTraitImplementation
9393

9494
public function test($tagName, ContainerBuilder $container)
9595
{
96-
return self::findAndSortTaggedServices($tagName, $container);
96+
return $this->findAndSortTaggedServices($tagName, $container);
9797
}
9898
}

src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,8 @@ public function testParseTaggedArgumentsWithIndexBy()
329329

330330
$taggedIterator = new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar');
331331
$this->assertEquals($taggedIterator, $container->getDefinition('foo_tagged_iterator')->getArgument(0));
332+
333+
$taggedIterator = new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar', true);
332334
$this->assertEquals(new ServiceLocatorArgument($taggedIterator), $container->getDefinition('foo_tagged_locator')->getArgument(0));
333335
}
334336

src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ public function testTaggedArgumentsWithIndex()
293293

294294
$taggedIterator = new TaggedIteratorArgument('foo', 'barfoo', 'foobar');
295295
$this->assertEquals($taggedIterator, $container->getDefinition('foo_service_tagged_iterator')->getArgument(0));
296+
297+
$taggedIterator = new TaggedIteratorArgument('foo', 'barfoo', 'foobar', true);
296298
$this->assertEquals(new ServiceLocatorArgument($taggedIterator), $container->getDefinition('foo_service_tagged_locator')->getArgument(0));
297299
}
298300

0 commit comments

Comments
 (0)