8000 feature #59704 [DependencyInjection] Add `Definition::addExcludedTag(… · symfonyaml/symfony@09ba274 · GitHub
[go: up one dir, main page]

Skip to content

Commit 09ba274

Browse files
feature symfony#59704 [DependencyInjection] Add Definition::addExcludedTag() and ContainerBuilder::findExcludedServiceIds() for auto-discovering value-objects (GromNaN)
This PR was squashed before being merged into the 7.3 branch. Discussion ---------- [DependencyInjection] Add `Definition::addExcludedTag()` and `ContainerBuilder::findExcludedServiceIds()` for auto-discovering value-objects | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | - | License | MIT We could **not** use the method `findTaggedServiceIds` in symfony#59401 (comment), same for api-platform/core#6943. As "using the container loading tools to do resource discovery quite seamlessly" [seems to be a good idea](symfony#59401 (review)), this changes make it easier. I'm not closed to alternative ideas if we want to go further with this use-case. ### Usage Let's create a `AppModel` attribute class and use it on any class of the project. In the extension class: ```php $this->registerAttributeForAutoconfiguration(AppModel::class, static function (ChildDefinition $definition) { $definition->addExcludedTag('app.model'); }); ``` In a compiler pass: ```php $classes = []; foreach($containerBuilder->findExcludedServiceIds('app.model') as $id => $tags) { $classes[] = $containerBuilder->getDefinition($id)->getClass(); } $containerBuilder->setParameter('.app.model_classes', $classes); ``` And this parameter can be injected into a service, or directly update a service definition to inject this list of classes. The attribute parameters can be injected into the tag, and retrieved in the compiler pass, for more advanced configuration. Commits ------- 7a0443b [DependencyInjection] Add `Definition::addExcludedTag()` and `ContainerBuilder::findExcludedServiceIds()` for auto-discovering value-objects
2 parents 4b6f05d + 7a0443b commit 09ba274

File tree

5 files changed

+104
-9
lines changed

5 files changed

+104
-9
lines changed

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ CHANGELOG
77
* Make `#[AsTaggedItem]` repeatable
88
* Support `@>` as a shorthand for `!service_closure` in yaml files
99
* Don't skip classes with private constructor when autodiscovering
10+
* Add `Definition::addExcludeTag()` and `ContainerBuilder::findExcludedServiceIds()`
11+
for auto-configuration of classes excluded from the service container
1012

1113
7.2
1214
---

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1351,6 +1351,38 @@ public function findTaggedServiceIds(string $name, bool $throwOnAbstract = false
13511351
return $tags;
13521352
}
13531353

1354+
/**
1355+
* Returns service ids for a given tag, asserting they have the "container.excluded" tag.
1356+
*
1357+
* Example:
1358+
*
1359+
* $container->register('foo')->addExcludeTag('my.tag', ['hello' => 'world'])
1360+
*
1361+
* $serviceIds = $container->findExcludedServiceIds('my.tag');
1362+
* foreach ($serviceIds as $serviceId => $tags) {
1363+
* foreach ($tags as $tag) {
1364+
* echo $tag['hello'];
1365+
* }
1366+
* }
1367+
*
1368+
* @return array<string, array> An array of tags with the tagged service as key, holding a list of attribute arrays
< 10000 code>1369+
*/
1370+
public function findExcludedServiceIds(string $tagName): array
1371+
{
1372+
$this->usedTags[] = $tagName;
1373+
$tags = [];
1374+
foreach ($this->getDefinitions() as $id => $definition) {
1375+
if ($definition->hasTag($tagName)) {
1376+
if (!$definition->hasTag('container.excluded')) {
1377+
throw new InvalidArgumentException(\sprintf('The service "%s" tagged "%s" is missing the "container.excluded" tag.', $id, $tagName));
1378+
}
1379+
$tags[$id] = $definition->getTag($tagName);
1380+
}
1381+
}
1382+
1383+
return $tags;
1384+
}
1385+
13541386
/**
13551387
* Returns all tags the defined services use.
13561388
*

src/Symfony/Component/DependencyInjection/Definition.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,20 @@ public function addTag(string $name, array $attributes = []): static
455455
return $this;
456456
}
457457

458+
/**
459+
* Adds a tag to the definition and marks it as excluded.
460+
*
461+
* These definitions should be processed using {@see ContainerBuilder::findExcludedServiceIds()}
462+
*
463+
* @return $this
464+
*/
465+
public function addExcludeTag(string $name, array $attributes = []): static
466+
{
467+
return $this->addTag($name, $attributes)
468+
->addTag('container.excluded', ['source' => \sprintf('by tag "%s"', $name)])
469+
->setAbstract(true);
470+
}
471+
458472
/**
459473
* Whether this definition has a tag with the given name.
460474
*/

src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,20 +1062,18 @@ public function testMergeLogicException()
10621062
$container->merge(new ContainerBuilder());
10631063
}
10641064

1065-
public function testfindTaggedServiceIds()
1065+
public function testFindTaggedServiceIds()
10661066
{
10671067
$builder = new ContainerBuilder();
1068-
$builder
1069-
->register('foo', 'Bar\FooClass')
1068+
$builder->register('foo', 'Bar\FooClass')
1069+
->setAbstract(true)
10701070
->addTag('foo', ['foo' => 'foo'])
10711071
->addTag('bar', ['bar' => 'bar'])
1072-
->addTag('foo', ['foofoo' => 'foofoo'])
1073-
;
1074-
$builder
1075-
->register('bar', 'Bar\FooClass')
1072+
->addTag('foo', ['foofoo' => 'foofoo']);
1073+
$builder->register('bar', 'Bar\FooClass')
10761074
->addTag('foo')
1077-
->addTag('container.excluded')
1078-
;
1075+
->addTag('container.excluded');
1076+
10791077
$this->assertEquals([
10801078
'foo' => [
10811079
['foo' => 'foo'],
@@ -1085,6 +1083,45 @@ public function testfindTaggedServiceIds()
10851083
$this->assertEquals([], $builder->findTaggedServiceIds('foobar'), '->findTaggedServiceIds() returns an empty array if there is annotated services');
10861084
}
10871085

1086+
public function testFindTaggedServiceIdsThrowsWhenAbstract()
1087+
{
1088+
$builder = new ContainerBuilder();
1089+
$builder->register('foo', 'Bar\FooClass')
1090+
->setAbstract(true)
1091+
->addTag('foo', ['foo' => 'foo']);
1092+
1093+
$this->expectException(InvalidArgumentException::class);
1094+
$this->expectExceptionMessage('The service "foo" tagged "foo" must not be abstract.');
1095+
$builder->findTaggedServiceIds('foo', true);
1096+
}
1097+
1098+
public function testFindExcludedServiceIds()
1099+
{
1100+
$builder = new ContainerBuilder();
1101+
$builder->register('myservice', 'Bar\FooClass')
1102+
->addTag('foo', ['foo' => 'foo'])
1103+
->addTag('bar', ['bar' => 'bar'])
1104+
->addTag('foo', ['foofoo' => 'foofoo'])
1105+
->addExcludeTag('container.excluded');
1106+
1107+
$expected = ['myservice' => [['foo' => 'foo'], ['foofoo' => 'foofoo']]];
1108+
$this->assertSame($expected, $builder->findExcludedServiceIds('foo'));
1109+
$this->assertSame([], $builder->findExcludedServiceIds('foofoo'));
1110+
}
1111+
1112+
public function testFindExcludedServiceIdsThrowsWhenNotExcluded()
1113+
{
1114+
$builder = new ContainerBuilder();
1115+
$builder->register('myservice', 'Bar\FooClass')
1116+
->addTag('foo', ['foo' => 'foo'])
1117+
->addTag('bar', ['bar' => 'bar'])
1118+
->addTag('foo', ['foofoo' => 'foofoo']);
1119+
1120+
$this->expectException(InvalidArgumentException::class);
1121+
$this->expectExceptionMessage('The service "myservice" tagged "foo" is missing the "container.excluded" tag.');
1122+
$builder->findExcludedServiceIds('foo', true);
1123+
}
1124+
10881125
public function testFindUnusedTags()
10891126
{
10901127
$builder = new ContainerBuilder();

src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,16 @@ public function testTags()
258258
], $def->getTags(), '->getTags() returns all tags');
259259
}
260260

261+
public function testAddExcludeTag()
262+
{
263+
$def = new Definition('stdClass');
264+
$def->addExcludeTag('foo', ['bar' => true]);
265+
266+
$this->assertSame([['bar' => true]], $def->getTag('foo'));
267+
$this->assertTrue($def->isAbstract());
268+
$this->assertSame([['source' => 'by tag "foo"']], $def->getTag('container.excluded'));
269+
}
270+
261271
public function testSetArgument()
262272
{
263273
$def = new Definition('stdClass');

0 commit comments

Comments
 (0)
0