diff --git a/src/Symfony/Component/DependencyInjection/Attribute/AsDecorator.php b/src/Symfony/Component/DependencyInjection/Attribute/AsDecorator.php index b5125c8b0f24e..723c2b9e63eca 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/AsDecorator.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/AsDecorator.php @@ -16,7 +16,7 @@ /** * Declares a decorating service. */ -#[\Attribute(\Attribute::TARGET_CLASS)] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] class AsDecorator { /** diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 7212720f826af..5915d227421b1 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * Allow `#[AsAlias]` to be extended * Add argument `$target` to `ContainerBuilder::registerAliasForArgument()` * Deprecate registering a service without a class when its id is a non-existing FQCN + * Allow multiple `#[AsDecorator]` attributes 7.3 --- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowireAsDecoratorPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowireAsDecoratorPass.php index 1e812c7002bdf..62018476ff882 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowireAsDecoratorPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowireAsDecoratorPass.php @@ -23,9 +23,9 @@ final class AutowireAsDecoratorPass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { - foreach ($container->getDefinitions() as $definition) { + foreach ($container->getDefinitions() as $id => $definition) { if ($this->accept($definition) && $reflectionClass = $container->getReflectionClass($definition->getClass(), false)) { - $this->processClass($definition, $reflectionClass); + $this->processClass($id, $container, $definition, $reflectionClass); } } } @@ -35,12 +35,27 @@ private function accept(Definition $definition): bool return !$definition->hasTag('container.ignore_attributes') && $definition->isAutowired(); } - private function processClass(Definition $definition, \ReflectionClass $reflectionClass): void + private function processClass(string $id, ContainerBuilder $container, Definition $definition, \ReflectionClass $reflectionClass): void { - foreach ($reflectionClass->getAttributes(AsDecorator::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + if (!$attributes = $reflectionClass->getAttributes(AsDecorator::class, \ReflectionAttribute::IS_INSTANCEOF)) { + return; + } + + if (1 === \count($attributes)) { + $attribute = $attributes[0]->newInstance(); + $definition->setDecoratedService($attribute->decorates, null, $attribute->priority, $attribute->onInvalid); + + return; + } + + foreach ($attributes as $attribute) { $attribute = $attribute->newInstance(); + $definition = clone $definition; $definition->setDecoratedService($attribute->decorates, null, $attribute->priority, $attribute->onInvalid); + $container->setDefinition(\sprintf('.decorator.%s.%s', $attribute->decorates, $id), $definition); } + + $container->removeDefinition($id); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 458cc91e419c3..f9d6d06d97ce6 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -1346,6 +1346,28 @@ public function testAsDecoratorAttribute() $this->assertSame(2, $container->getDefinition(AsDecoratorBaz::class)->getArgument(0)->getInvalidBehavior()); } + public function testMultipleAsDecoratorAttribute() + { + $container = new ContainerBuilder(); + + $container->register(AsDecoratorMultipleFoo::class); + $container->register(AsDecoratorMultipleBar::class); + $container->register(AsDecoratorMultiple::class)->setAutowired(true)->setArgument(0, 'arg1'); + + (new ResolveClassPass())->process($container); + (new AutowireAsDecoratorPass())->process($container); + (new DecoratorServicePass())->process($container); + (new AutowirePass())->process($container); + + $fooDecoratorName = '.decorator.'.AsDecoratorMultipleFoo::class.'.'.AsDecoratorMultiple::class; + $this->assertSame($fooDecoratorName, (string) $container->getAlias(AsDecoratorMultipleFoo::class)); + $this->assertSame($fooDecoratorName.'.inner', (string) $container->getDefinition($fooDecoratorName)->getArgument(1)); + + $barDecoratorName = '.decorator.'.AsDecoratorMultipleBar::class.'.'.AsDecoratorMultiple::class; + $this->assertSame($barDecoratorName, (string) $container->getAlias(AsDecoratorMultipleBar::class)); + $this->assertSame($barDecoratorName.'.inner', (string) $container->getDefinition($barDecoratorName)->getArgument(1)); + } + public function testTypeSymbolExcluded() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php index a8f66ea519f74..e52892b19604b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php @@ -125,6 +125,23 @@ public function __construct(#[AutowireDecorated] ?AsDecoratorInterface $inner = } } +class AsDecoratorMultipleFoo implements AsDecoratorInterface +{ +} + +class AsDecoratorMultipleBar implements AsDecoratorInterface +{ +} + +#[AsDecorator(decorates: AsDecoratorMultipleFoo::class)] +#[AsDecorator(decorates: AsDecoratorMultipleBar::class)] +class AsDecoratorMultiple implements AsDecoratorInterface +{ + public function __construct(string $arg1, #[AutowireDecorated] AsDecoratorInterface $inner) + { + } +} + #[AsDecorator(decorates: AsDecoratorFoo::class)] class AutowireNestedAttributes implements AsDecoratorInterface {