Skip to content

Commit 18d7668

Browse files
committed
feature symfony#61575 [DependencyInjection] Allow multiple #[AsDecorator] attributes (Jean-Beru)
This PR was squashed before being merged into the 7.4 branch. Discussion ---------- [DependencyInjection] Allow multiple `#[AsDecorator]` attributes | Q | A | ------------- | --- | Branch? | 7.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | | License | MIT Make `#[AsDecorator]` attribute repeatable to allow decorating multiple services at once. Example for a simple HttpClient logger: ```php #[AsDecorator('api1.client')] #[AsDecorator('api2.client')] #[AsDecorator('api3.client')] class LoggableService implements HttpClientInterface { use DecoratorTrait; public function __construct( private HttpClientInterface $client, private readonly LoggerInterface $logger, ) { } public function request(string $method, string $url, array $options = []): ResponseInterface { try { $response = $this->client->request($method, $url, $options); $this->logger->info('API call: {method} {url}.', ['method'= > $method, 'url' => $url]); return $response; } catch (\Throwable $e) { $this->logger->error('API call failed: {method} {url}.', ['method'= > $method, 'url' => $url, 'exception' => $e]); throw $exception; } } } ``` Commits ------- 3986c94 [DependencyInjection] Allow multiple `#[AsDecorator]` attributes
2 parents 145abac + 3986c94 commit 18d7668

File tree

5 files changed

+60
-5
lines changed

5 files changed

+60
-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
@@ -9,6 +9,7 @@ CHANGELOG
99
* Parse attributes found on abstract classes for resource definitions
1010
* Add argument `$target` to `ContainerBuilder::registerAliasForArgument()`
1111
* Deprecate registering a service without a class when its id is a non-existing FQCN
12+
* Allow multiple `#[AsDecorator]` attributes
1213

1314
7.3
1415
---

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

Lines changed: 19 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,27 @@ 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+
if (!$attributes = $reflectionClass->getAttributes(AsDecorator::class, \ReflectionAttribute::IS_INSTANCEOF)) {
41+
return;
42+
}
43+
44+
if (1 === \count($attributes)) {
45+
$attribute = $attributes[0]->newInstance();
46+
$definition->setDecoratedService($attribute->decorates, null, $attribute->priority, $attribute->onInvalid);
47+
48+
return;
49+
}
50+
51+
foreach ($attributes as $attribute) {
4152
$attribute = $attribute->newInstance();
4253

54+
$definition = clone $definition;
4355
$definition->setDecoratedService($attribute->decorates, null, $attribute->priority, $attribute->onInvalid);
56+
$container->setDefinition(\sprintf('.decorator.%s.%s', $attribute->decorates, $id), $definition);
4457
}
58+
59+
$container->removeDefinition($id);
4560
}
4661
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,28 @@ 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+
$fooDecoratorName = '.decorator.'.AsDecoratorMultipleFoo::class.'.'.AsDecoratorMultiple::class;
1363+
$this->assertSame($fooDecoratorName, (string) $container->getAlias(AsDecoratorMultipleFoo::class));
1364+
$this->assertSame($fooDecoratorName.'.inner', (string) $container->getDefinition($fooDecoratorName)->getArgument(1));
1365+
1366+
$barDecoratorName = '.decorator.'.AsDecoratorMultipleBar::class.'.'.AsDecoratorMultiple::class;
1367+
$this->assertSame($barDecoratorName, (string) $container->getAlias(AsDecoratorMultipleBar::class));
1368+
$this->assertSame($barDecoratorName.'.inner', (string) $container->getDefinition($barDecoratorName)->getArgument(1));
1369+
}
1370+
13491371
public function testTypeSymbolExcluded()
13501372
{
13511373
$container = new ContainerBuilder();

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

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

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

0 commit comments

Comments
 (0)