Skip to content

Commit f85a598

Browse files
committed
[DependencyInjection] Allow multiple #[AsDecorator] attributes
1 parent 9e86767 commit f85a598

File tree

5 files changed

+58
-5
lines changed

5 files changed

+58
-5
lines changed

src/Symfony/Component/DependencyInjection/Attribute/AsDecorator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
/**
1717
* Declares a decorating service.
1818
*/
19-
#[\Attribute(\Attribute::TARGET_CLASS)]
19+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
2020
class AsDecorator
2121
{
2222
/**

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CHANGELOG
88
* Allow `#[AsAlias]` to be extended
99
* Add argument `$target` to `ContainerBuilder::registerAliasForArgument()`
1010
* Deprecate registering a service without a class when its id is a non-existing FQCN
11+
* Allow multiple `#[AsDecorator]` attributes
1112

1213
7.3
1314
---

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ final class AutowireAsDecoratorPass implements CompilerPassInterface
2323
{
2424
public function process(ContainerBuilder $container): void
2525
{
26-
foreach ($container->getDefinitions() as $definition) {
26+
foreach ($container->getDefinitions() as $id => $definition) {
2727
if ($this->accept($definition) && $reflectionClass = $container->getReflectionClass($definition->getClass(), false)) {
28-
$this->processClass($definition, $reflectionClass);
28+
$this->processClass($id, $container, $definition, $reflectionClass);
2929
}
3030
}
3131
}
@@ -35,12 +35,29 @@ private function accept(Definition $definition): bool
3535
return !$definition->hasTag('container.ignore_attributes') && $definition->isAutowired();
3636
}
3737

38-
private function processClass(Definition $definition, \ReflectionClass $reflectionClass): void
38+
private function processClass(string $id, ContainerBuilder $container, Definition $definition, \ReflectionClass $reflectionClass): void
3939
{
40-
foreach ($reflectionClass->getAttributes(AsDecorator::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
40+
$attributes = $reflectionClass->getAttributes(AsDecorator::class, \ReflectionAttribute::IS_INSTANCEOF);
41+
42+
if ([] === $attributes) {
43+
return;
44+
}
45+
46+
if (1 === count($attributes)) {
47+
$attribute = $attributes[0]->newInstance();
48+
$definition->setDecoratedService($attribute->decorates, null, $attribute->priority, $attribute->onInvalid);
49+
50+
return;
51+
}
52+
53+
foreach ($attributes as $attribute) {
4154
$attribute = $attribute->newInstance();
4255

56+
$definition = clone $definition;
4357
$definition->setDecoratedService($attribute->decorates, null, $attribute->priority, $attribute->onInvalid);
58+
$container->setDefinition($id.'.'.ContainerBuilder::hash($attribute->decorates), $definition);
4459
}
60+
61+
$container->removeDefinition($id);
4562
}
4663
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,25 @@ public function testAsDecoratorAttribute()
13461346
$this->assertSame(2, $container->getDefinition(AsDecoratorBaz::class)->getArgument(0)->getInvalidBehavior());
13471347
}
13481348

1349+
public function testMultipleAsDecoratorAttribute()
1350+
{
1351+
$container = new ContainerBuilder();
1352+
1353+
$container->register(AsDecoratorMultipleFoo::class);
1354+
$container->register(AsDecoratorMultipleBar::class);
1355+
$container->register(AsDecoratorMultiple::class)->setAutowired(true)->setArgument(0, 'arg1');
1356+
1357+
(new ResolveClassPass())->process($container);
1358+
(new AutowireAsDecoratorPass())->process($container);
1359+
(new DecoratorServicePass())->process($container);
1360+
(new AutowirePass())->process($container);
1361+
1362+
$this->assertSame(AsDecoratorMultiple::class.'.brovYQA', (string) $container->getAlias(AsDecoratorMultipleFoo::class));
1363+
$this->assertSame(AsDecoratorMultiple::class.'.brovYQA.inner', (string) $container->getDefinition(AsDecoratorMultiple::class.'.brovYQA')->getArgument(1));
1364+
$this->assertSame(AsDecoratorMultiple::class.'.NeMNE1z', (string) $container->getAlias(AsDecoratorMultipleBar::class));
1365+
$this->assertSame(AsDecoratorMultiple::class.'.NeMNE1z.inner', (string) $container->getDefinition(AsDecoratorMultiple::class.'.NeMNE1z')->getArgument(1));
1366+
}
1367+
13491368
public function testTypeSymbolExcluded()
13501369
{
13511370
$container = new ContainerBuilder();

src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,22 @@ public function __construct(#[AutowireDecorated] ?AsDecoratorInterface $inner =
125125
}
126126
}
127127

128+
class AsDecoratorMultipleFoo implements AsDecoratorInterface
129+
{
130+
}
131+
class AsDecoratorMultipleBar implements AsDecoratorInterface
132+
{
133+
}
134+
135+
#[AsDecorator(decorates: AsDecoratorMultipleFoo::class)]
136+
#[AsDecorator(decorates: AsDecoratorMultipleBar::class)]
137+
class AsDecoratorMultiple implements AsDecoratorInterface
138+
{
139+
public function __construct(string $arg1, #[AutowireDecorated] AsDecoratorInterface $inner)
140+
{
141+
}
142+
}
143+
128144
#[AsDecorator(decorates: AsDecoratorFoo::class)]
129145
class AutowireNestedAttributes implements AsDecoratorInterface
130146
{

0 commit comments

Comments
 (0)