8000 [DI] Reference tagged services in config · symfony/symfony@979e58f · GitHub
[go: up one dir, main page]

Skip to content

Commit 979e58f

Browse files
ro0NLnicolas-grekas
authored andcommitted
[DI] Reference tagged services in config
1 parent d7885ec commit 979e58f

27 files changed

+271
-15
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Argument;
13+
14+
/**
15+
* Represents a collection of services found by tag name to lazily iterate over.
16+
*
17+
* @author Roland Franssen <franssen.roland@gmail.com>
18+
*/
19+
class TaggedIteratorArgument extends IteratorArgument
20+
{
21+
private $tag;
22+
23+
/**
24+
* @param string $tag
25+
*/
26+
public function __construct($tag)
27+
{
28+
parent::__construct(array());
29+
30+
$this->tag = (string) $tag;
31+
}
32+
33+
public function getTag()
34+
{
35+
return $this->tag;
36+
}
37+
}

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines ch 10000 anged: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ CHANGELOG
1313
* deprecated support for top-level anonymous services in XML
1414
* deprecated case insensitivity of parameter names
1515
* deprecated the `ResolveDefinitionTemplatesPass` class in favor of `ResolveChildDefinitionsPass`
16+
* added `TaggedIteratorArgument` with YAML (`!tagged foo`) and XML (`<service type="tagged"/>`) support
1617

1718
3.3.0
1819
-----

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public function __construct()
6161
new AutowireRequiredMethodsPass(),
6262
new ResolveBindingsPass(),
6363
new AutowirePass(false),
64+
new ResolveTaggedIteratorArgumentPass(),
6465
new ResolveServiceSubscribersPass(),
6566
new ResolveReferencesToAliasesPass(),
6667
new ResolveInvalidReferencesPass(),
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
15+
16+
/**
17+
* Resolves all TaggedIteratorArgument arguments.
18+
*
19+
* @author Roland Franssen <franssen.roland@gmail.com>
20+
*/
21+
class ResolveTaggedIteratorArgumentPass extends AbstractRecursivePass
22+
{
23+
use PriorityTaggedServiceTrait;
24+
25+
/**
26+
* {@inheritdoc}
27+
*/
28+
protected function processValue($value, $isRoot = false)
29+
{
30+
if (!$value instanceof TaggedIteratorArgument) {
31+
return parent::processValue($value, $isRoot);
32+
}
33+
34+
$value->setValues($this->findAndSortTaggedServices($value->getTag(), $this->container));
35+
36+
return $value;
37+
}
38+
}

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

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

1414
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1515
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
16+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
1617
use Symfony\Component\DependencyInjection\ContainerInterface;
1718
use Symfony\Component\DependencyInjection\Parameter;
1819
use Symfony\Component\DependencyInjection\Reference;
@@ -298,6 +299,9 @@ private function convertParameters(array $parameters, $type, \DOMElement $parent
298299
if (is_array($value)) {
299300
$element->setAttribute('type', 'collection');
300301
$this->convertParameters($value, $type, $element, 'key');
302+
} elseif ($value instanceof TaggedIteratorArgument) {
303+
$element->setAttribute('type', 'tagged');
304+
$element->setAttribute('tag', $value->getTag());
301305
} elseif ($value instanceof IteratorArgument) {
302306
$element->setAttribute('type', 'iterator');
303307
$this->convertParameters($value->getValues(), $type, $element, 'key');

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
2020
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
2121
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
22+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
2223
use Symfony\Component\DependencyInjection\ContainerInterface;
2324
use Symfony\Component\DependencyInjection\Definition;
2425
use Symfony\Component\DependencyInjection\Parameter;
@@ -263,6 +264,9 @@ private function dumpValue($value)
263264
$value = $value->getValues()[0];
264265
}
265266
if ($value instanceof ArgumentInterface) {
267+
if ($value instanceof TaggedIteratorArgument) {
268+
return new TaggedValue('tagged', $value->getTag());
269+
}
266270
if ($value instanceof IteratorArgument) {
267271
$tag = 'iterator';
268272
} else {

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

1414
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
15+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
1516
use Symfony\Component\DependencyInjection\ContainerBuilder;
1617
use Symfony\Component\DependencyInjection\Definition;
1718
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@@ -115,6 +116,18 @@ function iterator(array $values)
115116
return new IteratorArgument(AbstractConfigurator::processValue($values, true));
116117
}
117118

119+
/**
120+
* Creates a lazy iterator by tag name.
121+
*
122+
* @param string $tag
123+
*
124+
* @return TaggedIteratorArgument
125+
*/
126+
function tagged($tag)
127+
{
128+
return new TaggedIteratorArgument($tag);
129+
}
130+
118131
/**
119132
* Creates an expression.
120133
*

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Loader;
1313

1414
use Symfony\Component\Config\Util\XmlUtils;
15+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
1516
use Symfony\Component\DependencyInjection\ContainerInterface;
1617
use Symfony\Component\DependencyInjection\Alias;
1718
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
@@ -518,6 +519,12 @@ private function getArgumentsAsPhp(\DOMElement $node, $name, $file, $lowercase =
518519
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="iterator" only accepts collections of type="service" references in "%s".', $name, $file));
519520
}
520521
break;
522+
case 'tagged':
523+
if (!$arg->getAttribute('tag')) {
524+
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="tagged" has no or empty "tag" attribute in "%s".', $name, $file));
525+
}
526+
$arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'));
527+
break;
521528
case 'string':
522529
$arguments[$key] = $arg->nodeValue;
523530
break;

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
1616
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
1717
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
18+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
1819
use Symfony\Component\DependencyInjection\ChildDefinition;
1920
use Symfony\Component\DependencyInjection\ContainerBuilder;
2021
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -726,6 +727,13 @@ private function resolveServices($value, $file, $isParameter = false)
726727
throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts arrays of "@service" references in "%s".', $file));
727728
}
728729
}
730+
if ('tagged' === $value->getTag()) {
731+
if (!is_string($argument) || !$argument) {
732+
throw new InvalidArgumentException(sprintf('"!tagged" tag only accepts non empty string in "%s".', $file));
733+
}
734+
735+
return new TaggedIteratorArgument($argument);
736+
}
729737
if ('service' === $value->getTag()) {
730738
if ($isParameter) {
731739
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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@
208208
<xsd:attribute name="name" type="xsd:string" />
209209
<xsd:attribute name="on-invalid" type="invalid_sequence" />
210210
<xsd:attribute name="strict" type="boolean" />
211+
<xsd:attribute name="tag" type="xsd:string" />
211212
</xsd:complexType>
212213

213214
<xsd:complexType name="bind" mixed="true">
@@ -233,6 +234,7 @@
233234
<xsd:attribute name="index" type="xsd:integer" />
234235
<xsd:attribute name="on-invalid" type="invalid_sequence" />
235236
<xsd:attribute name="strict" type="boolean" />
237+
<xsd:attribute name="tag" type="xsd:string" />
236238
</xsd:complexType>
237239

238240
<xsd:complexType name="call">
@@ -258,6 +260,7 @@
258260
<xsd:enumeration value="string" />
259261
<xsd:enumeration value="constant" />
260262
<xsd:enumeration value="iterator" />
263+
<xsd:enumeration value="tagged" />
261264
</xsd:restriction>
262265
</xsd:simpleType>
263266

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
16+
use Symfony\Component\DependencyInjection\Compiler\ResolveTaggedIteratorArgumentPass;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\Reference;
19+
20+
/**
21+
* @author Roland Franssen <franssen.roland@gmail.com>
22+
*/
23+
class ResolveTaggedIteratorArgumentPassTest extends TestCase
24+
{
25+
public function testProcess()
26+
{
27+
$container = new ContainerBuilder();
28+
$container->register('a', 'stdClass')->addTag('foo');
29+
$container->register('b', 'stdClass')->addTag('foo', array('priority' => 20));
30+
$container->register('c', 'stdClass')->addTag('foo', array('priority' => 10));
31+
$container->register('d', 'stdClass')->setProperty('foos', new TaggedIteratorArgument('foo'));
32+
33+
(new ResolveTaggedIteratorArgumentPass())->process($container);
34+
35+
$properties = $container->getDefinition('d')->getProperties();
36+
$expected = new TaggedIteratorArgument('foo');
37+
$expected->setValues(array(new Reference('b'), new Reference('c'), new Reference('a')));
38+
$this->assertEquals($expected, $properties['foos']);
39+
}
40+
}

src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/basic.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
use App\BarService;
66

77
return function (ContainerConfigurator $c) {
8-
98
$s = $c->services();
109
$s->set(BarService::class)
1110
->args(array(inline('FooClass')));
12-
1311
};

src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/child.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use App\BarService;
66

77
return function (ContainerConfigurator $c) {
8-
98
$c->services()
109
->set('bar', 'Class1')
1110
->set(BarService::class)
@@ -20,5 +19,4 @@
2019
->parent('bar')
2120
->parent(BarService::class)
2221
;
23-
2422
};

src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/defaults.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo;
66

77
return function (ContainerConfigurator $c) {
8-
98
$c->import('basic.php');
109

1110
$s = $c->services()->defaults()
@@ -19,5 +18,4 @@
1918

2019
$s->set(Foo::class)->args(array(ref('bar')))->public();
2120
$s->set('bar', Foo::class)->call('setFoo')->autoconfigure(false);
22-
2321
};

src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype;
77

88
return function (ContainerConfigurator $c) {
9-
109
$s = $c->services();
1110
$s->instanceof(Prototype\Foo::class)
1211
->property('p', 0)
@@ -20,5 +19,4 @@
2019
$s->load(Prototype::class.'\\', '../Prototype')->exclude('../Prototype/*/*');
2120

2221
$s->set('foo', FooService::class);
23-
2422
};

src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/php7.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo;
66

77
return function (ContainerConfigurator $c) {
8-
98
$c->parameters()
109
('foo', 'Foo')
1110
('bar', 'Bar')
@@ -17,5 +16,4 @@
1716
('bar', Foo::class)
1817
->call('setFoo')
1918
;
20-
2119
};

src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype;
66

77
return function (ContainerConfigurator $c) {
8-
98
$di = $c->services()->defaults()
109
->tag('baz');
1110
$di->load(Prototype::class.'\\', '../Prototype')
@@ -20,5 +19,4 @@
2019
->parent('foo');
2120
$di->set('foo')->lazy()->abstract();
2221
$di->get(Prototype\Foo::class)->lazy(false);
23-
2422
};

src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/services9.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
require_once __DIR__.'/../includes/foo.php';
1010

1111
return function (ContainerConfigurator $c) {
12-
1312
$p = $c->parameters();
1413
$p->set('baz_class', 'BazClass');
1514
$p->set('foo_class', FooClass::class)
@@ -119,4 +118,11 @@
119118
$s->set('lazy_context_ignore_invalid_ref', 'LazyContext')
120119
->args(array(iterator(array(ref('foo.baz'), ref('invalid')->ignoreOnInvalid())), iterator(array())));
121120

121+
$s->set('tagged_iterator_foo', 'Bar')
122+
->private()
123+
->tag('foo');
124+
125+
$s->set('tagged_iterator', 'Bar')
126+
->public()
127+
->args(array(tagged('foo')));
122128
};

src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require_once __DIR__.'/../includes/foo.php';
55

66
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
7+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
78
use Symfony\Component\DependencyInjection\ContainerInterface;
89
use Symfony\Component\DependencyInjection\ContainerBuilder;
910
use Symfony\Component\DependencyInjection\Reference;
@@ -161,5 +162,15 @@
161162
->setArguments(array(new IteratorArgument(array(new Reference('foo.baz'), new Reference('invalid', ContainerInterface::IGNORE_ON_INVALID_REFERENCE))), new IteratorArgument(array())))
162163
->setPublic(true)
163164
;
165+
$container
166+
->register('tagged_iterator_foo', 'Bar')
167+
->addTag('foo')
168+
->setPublic(false)
169+
;
170+
$container
171+
->register('tagged_iterator', 'Bar')
172+
->addArgument(new TaggedIteratorArgument('foo'))
173+
->setPublic(true)
174+
;
164175

165176
return $container;

src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ digraph sc {
2929
node_factory_service_simple [label="factory_service_simple\nBar\n", shape=record, fillcolor="#eeeeee", style="filled"];
3030
node_lazy_context [label="lazy_context\nLazyContext\n", shape=record, fillcolor="#eeeeee", style="filled"];
3131
node_lazy_context_ignore_invalid_ref [label="lazy_context_ignore_invalid_ref\nLazyContext\n", shape=record, fillcolor="#eeeeee", style="filled"];
32+
node_tagged_iterator_foo [label="tagged_iterator_foo\nBar\n", shape=record, fillcolor="#eeeeee", style="filled"];
33+
node_tagged_iterator [label="tagged_iterator\nBar\n", shape=record, fillcolor="#eeeeee", style="filled"];
3234
node_foo2 [label="foo2\n\n", shape=record, fillcolor="#ff9999", style="filled"];
3335
node_foo3 [label="foo3\n\n", shape=record, fillcolor="#ff9999", style="filled"];
3436
node_foobaz [label="foobaz\n\n", shape=record, fillcolor="#ff9999", style="filled"];

0 commit comments

Comments
 (0)
0