Skip to content

Commit 09ba274

Browse files
feature #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 #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](#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
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy