8000 [DI] Allow tagged_locator tag to be used as an argument · symfony/symfony@250a2c8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 250a2c8

Browse files
author
Anthony MARTIN
committed
[DI] Allow tagged_locator tag to be used as an argument
Signed-off-by: Anthony MARTIN <anthony.martin@sensiolabs.com>
1 parent f54c89c commit 250a2c8

18 files changed

+187
-19
lines changed

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Argument;
1313

14+
use Symfony\Component\DependencyInjection\Reference;
15+
1416
/**
1517
* Represents a closure acting as a service locator.
1618
*
@@ -19,4 +21,24 @@
1921
class ServiceLocatorArgument implements ArgumentInterface
2022
{
2123
use ReferenceSetArgumentTrait;
24+
25+
private $taggedIteratorArgument;
26+
27+
/**
28+
* @param Reference[]|TaggedIteratorArgument $values
29+
*/
30+
public function __construct($values = [])
31+
{
32+
if ($values instanceof TaggedIteratorArgument) {
33+
$this->taggedIteratorArgument = $values;
34+
$this->values = [];
35+
} else {
36+
$this->setValues($values);
37+
}
38+
}
39+
40+
public function getTaggedIteratorArgument(): ?TaggedIteratorArgument
41+
{
42+
return $this->taggedIteratorArgument;
43+
}
2244
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ class TaggedIteratorArgument extends IteratorArgument
2323
private $defaultIndexMethod;
2424

2525
/**
26-
* TaggedIteratorArgument constructor.
27-
*
2826
* @param string $tag The name of the tag identifying the target services
2927
* @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection
3028
* @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

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* added support for deprecating aliases
1010
* made `ContainerParametersResource` final and not implement `Serializable` anymore
1111
* added ability to define an index for a tagged collection
12+
* added ability to define an index for services in an injected service locator argument
1213

1314
4.2.0
1415
-----

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,18 @@
2727
*/
2828
final class ServiceLocatorTagPass extends AbstractRecursivePass
2929
{
30+
use PriorityTaggedServiceTrait;
31+
3032
protected function processValue($value, $isRoot = false)
3133
{
3234
if ($value instanceof ServiceLocatorArgument) {
35+
if ($value->getTaggedIteratorArgument()) {
36+
$value->setValues($this->findAndSortTaggedServices($value->getTaggedIteratorArgument(), $this->container));
37+
}
38+
3339
return self::register($this->container, $value->getValues());
3440
}
41+
3542
if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) {
3643
return parent::processValue($value, $isRoot);
3744
}

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,18 @@ private function convertParameters(array $parameters, $type, \DOMElement $parent
298298
$element->setAttribute('type', 'iterator');
299299
$this->convertParameters($value->getValues(), $type, $element, 'key');
300300
} elseif ($value instanceof ServiceLocatorArgument) {
301-
$element->setAttribute('type', 'service_locator');
302-
$this->convertParameters($value->getValues(), $type, $element, 'key');
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+
}
303313
} elseif ($value instanceof Reference) {
304314
$element->setAttribute('type', 'service');
305315
$element->setAttribute('id', (string) $value);

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,18 @@ private function dumpValue($value)
252252
$tag = 'iterator';
253253
} elseif ($value instanceof ServiceLocatorArgument) {
254254
$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+
}
255267
} else {
256268
throw new RuntimeException(sprintf('Unspecified Yaml tag for type "%s".', \get_class($value)));
257269
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,14 @@ function tagged(string $tag, string $indexAttribute = null, string $defaultIndex
121121
return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod);
122122
}
123123

124+
/**
125+
* Creates a service locator by tag name.
126+
*/
127+
function tagged_locator(string $tag, string $indexAttribute, string $defaultIndexMethod = null): ServiceLocatorArgument
128+
{
129+
return new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod));
130+
}
131+
124132
/**
125133
* Creates an expression.
126134
*/

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,13 @@ 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_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+
}
540+
541+
$arguments[$key] = new ServiceLocatorArgument(new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by'), $arg->getAttribute('default-index-method') ?: null));
542+
break;
536543
case 'tagged':
537544
if (!$arg->getAttribute('tag')) {
538545
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="tagged" has no or empty "tag" attribute in "%s".', $name, $file));

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,9 @@ private function resolveServices($value, $file, $isParameter = false)
702702
if (!\is_array($argument)) {
703703
throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps in "%s".', $file));
704704
}
705+
705706
$argument = $this->resolveServices($argument, $file, $isParameter);
707+
706708
try {
707709
return new ServiceLocatorArgument($argument);
708710
} catch (InvalidArgumentException $e) {
@@ -724,6 +726,17 @@ private function resolveServices($value, $file, $isParameter = false)
724726

725727
throw new InvalidArgumentException(sprintf('"!tagged" tags only accept a non empty string or an array with a key "tag" in "%s".', $file));
726728
}
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)));
733+
}
734+
735+
return new ServiceLocatorArgument(new TaggedIteratorArgument($argument['tag'], $argument['index_by'], $argument['default_index_method'] ?? null));
736+
}
737+
738+
throw new InvalidArgumentException(sprintf('"!tagged_locator" tags only accept an array with at least keys "tag" and "index_by" in "%s".', $file));
739+
}
727740
if ('service' === $value->getTag()) {
728741
if ($isParameter) {
729742
throw new InvalidArgumentException(sprintf('Using an anonymous service in a parameter is not allowed in "%s".', $file));

src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@
264264
<xsd:enumeration value="iterator" />
265265
<xsd:enumeration value="service_locator" />
266266
<xsd:enumeration value="tagged" />
267+
<xsd:enumeration value="tagged_locator" />
267268
</xsd:restriction>
268269
</xsd:simpleType>
269270

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

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Config\FileLocator;
1616
use Symfony\Component\DependencyInjection\Alias;
17+
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1718
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
1819
use Symfony\Component\DependencyInjection\ContainerBuilder;
1920
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
2021
use Symfony\Component\DependencyInjection\Reference;
22+
use Symfony\Component\DependencyInjection\ServiceLocator;
2123
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
2224
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass;
2325
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
@@ -242,15 +244,15 @@ public function getYamlCompileTests()
242244
public function testTaggedServiceWithIndexAttribute()
243245
{
244246
$container = new ContainerBuilder();
245-
$container->register(BarTagClass::class, BarTagClass::class)
247+
$container->register(BarTagClass::class)
246248
->setPublic(true)
247249
->addTag('foo_bar', ['foo' => 'bar'])
248250
;
249-
$container->register(FooTagClass::class, FooTagClass::class)
251+
$container->register(FooTagClass::class)
250252
->setPublic(true)
251253
->addTag('foo_bar')
252254
;
253-
$container->register(FooBarTaggedClass::class, FooBarTaggedClass::class)
255+
$container->register(FooBarTaggedClass::class)
254256
->addArgument(new TaggedIteratorArgument('foo_bar', 'foo'))
255257
->setPublic(true)
256258
;
@@ -266,15 +268,15 @@ public function testTaggedServiceWithIndexAttribute()
266268
public function testTaggedServiceWithIndexAttributeAndDefaultMethod()
267269
{
268270
$container = new ContainerBuilder();
269-
$container->register(BarTagClass::class, BarTagClass::class)
271+
$container->register(BarTagClass::class)
270272
->setPublic(true)
271273
->addTag('foo_bar')
272274
;
273-
$container->register(FooTagClass::class, FooTagClass::class)
275+
$container->register(FooTagClass::class)
274276
->setPublic(true)
275277
->addTag('foo_bar', ['foo' => 'foo'])
276278
;
277-
$container->register(FooBarTaggedClass::class, FooBarTaggedClass::class)
279+
$container->register(FooBarTaggedClass::class)
278280
->addArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar'))
279281
->setPublic(true)
280282
;
@@ -286,6 +288,68 @@ public function testTaggedServiceWithIndexAttributeAndDefaultMethod()
286288
$param = iterator_to_array($s->getParam()->getIterator());
287289
$this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $param);
288290
}
291+
292+
public function testTaggedServiceLocatorWithIndexAttribute()
293+
{
294+
$container = new ContainerBuilder();
295+
$container->register(BarTagClass::class)
296+
->setPublic(true)
297+
->addTag('foo_bar', ['foo' => 'bar'])
298+
;
299+
$container->register(FooTagClass::class)
300+
->setPublic(true)
301+
->addTag('foo_bar')
302+
;
303+
$container->register(FooBarTaggedClass::class)
304+
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo')))
305+
->setPublic(true)
306+
;
307+
308+
$container->compile();
309+
310+
$s = $container->get(FooBarTaggedClass::class);
311+
312+
/** @var ServiceLocator $serviceLocator */
313+
$serviceLocator = $s->getParam();
314+
$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)));
315+
316+
$same = [
317+
'bar' => $serviceLocator->get('bar'),
318+
'foo_tag_class' => $serviceLocator->get('foo_tag_class'),
319+
];
320+
$this->assertSame(['bar' => $container->get(BarTagClass::class), 'foo_tag_class' => $container->get(FooTagClass::class)], $same);
321+
}
322+
323+
public function testTaggedServiceLocatorWithIndexAttributeAndDefaultMethod()
324+
{
325+
$container = new ContainerBuilder();
326+
$container->register(BarTagClass::class)
327+
->setPublic(true)
328+
->addTag('foo_bar')
329+
;
330+
$container->register(FooTagClass::class)
331+
->setPublic(true)
332+
->addTag('foo_bar', ['foo' => 'foo'])
333+
;
334+
$container->register(FooBarTaggedClass::class)
335+
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar')))
336+
->setPublic(true)
337+
;
338+
339+
$container->compile();
340+
341+
$s = $container->get(FooBarTaggedClass::class);
342+
343+
/** @var ServiceLocator $serviceLocator */
344+
$serviceLocator = $s->getParam();
345+
$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)));
346+
347+
$same = [
348+
'bar_tab_class_with_defaultmethod' => $serviceLocator->get('bar_tab_class_with_defaultmethod'),
349+
'foo' => $serviceLocator->get('foo'),
350+
];
351+
$this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $same);
352+
}
289353
}
290354

291355
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 $this->findAndSortTaggedServices($tagName, $container);
96+
return self::findAndSortTaggedServices($tagName, $container);
9797
}
9898
}

src/Symfony/Component/DependencyInjection/Tests/Dumper/XmlDumperTest.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Config\FileLocator;
16+
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1617
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
1718
use Symfony\Component\DependencyInjection\ContainerBuilder;
1819
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -201,13 +202,18 @@ public function testDumpLoad()
201202
$this->assertStringEqualsFile(self::$fixturesPath.'/xml/services_dump_load.xml', $dumper->dump());
202203
}
203204

204-
public function testTaggedArgument()
205+
public function testTaggedArguments()
205206
{
207+
$taggedIterator = new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar');
206208
$container = new ContainerBuilder();
207209
$container->register('foo', 'Foo')->addTag('foo_tag');
208210
$container->register('foo_tagged_iterator', 'Bar')
209211
->setPublic(true)
210-
->addArgument(new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar'))
212+
->addArgument($taggedIterator)
213+
;
214+
$container->register('foo_tagged_locator', 'Bar')
215+
->setPublic(true)
216+
->addArgument(new ServiceLocatorArgument($taggedIterator))
211217
;
212218

213219
$dumper = new XmlDumper($container);

src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Config\FileLocator;
16+
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1617
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
1718
use Symfony\Component\DependencyInjection\ContainerBuilder;
1819
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -96,11 +97,13 @@ public function testInlineServices()
9697
$this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services_inline.yml', $dumper->dump());
9798
}
9899

99-
public function testTaggedArgument()
100+
public function testTaggedArguments()
100101
{
102+
$taggedIterator = new TaggedIteratorArgument('foo', 'barfoo', 'foobar');
101103
$container = new ContainerBuilder();
102104
$container->register('foo_service', 'Foo')->addTag('foo');
103-
$container->register('foo_service_tagged', 'Bar')->addArgument(new TaggedIteratorArgument('foo', 'barfoo', 'foobar'));
105+
$container->register('foo_service_tagged_iterator', 'Bar')->addArgument($taggedIterator);
106+
$container->register('foo_service_tagged_locator', 'Bar')->addArgument(new ServiceLocatorArgument($taggedIterator));
104107

105108
$dumper = new YamlDumper($container);
106109
$this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services_with_tagged_argument.yml', $dumper->dump());

src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_tagged_arguments.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
<service id="foo_tagged_iterator" class="Bar" public="true">
99
<argument type="tagged" tag="foo_tag" index-by="barfoo" default-index-method="foobar"/>
1010
</service>
11+
<service id="foo_tagged_locator" class="Bar" public="true">
12+
<argument type="tagged_locator" tag="foo_tag" index-by="barfoo" default-index-method="foobar"/>
13+
</service>
1114
<service id="Psr\Container\ContainerInterface" alias="service_container" public="false"/>
1215
<service id="Symfony\Component\DependencyInjection\ContainerInterface" alias="service_container" public="false"/>
1316
</services>

src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_tagged_argument.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ services:
88
class: Foo
99
tags:
1010
- { name: foo }
11-
foo_service_tagged:
11+
foo_service_tagged_iterator:
1212
class: Bar
1313
arguments: [!tagged { tag: foo, index_by: barfoo, default_index_method: foobar }]
14+
foo_service_tagged_locator:
15+
class: Bar
16+
arguments: [!tagged_locator { tag: foo, index_by: barfoo, default_index_method: foobar }]
1417
Psr\Container\ContainerInterface:
1518
alias: service_container
1619
public: false

0 commit comments

Comments
 (0)
0