8000 [DependencyInjection] Fix autowiring tagged arguments from attributes · symfony/symfony@9aa90b8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9aa90b8

Browse files
Okhoshinicolas-grekas
authored andcommitted
[DependencyInjection] Fix autowiring tagged arguments from attributes
1 parent 67f43b0 commit 9aa90b8

File tree

7 files changed

+173
-41
lines changed

7 files changed

+173
-41
lines changed

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

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@
1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

1414
use Symfony\Component\Config\Resource\ClassExistenceResource;
15-
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
16-
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
17-
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
18-
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
1915
use Symfony\Component\DependencyInjection\Attribute\Target;
2016
use Symfony\Component\DependencyInjection\ContainerBuilder;
2117
use Symfony\Component\DependencyInjection\Definition;
@@ -135,8 +131,7 @@ private function doProcessValue($value, bool $isRoot = false)
135131
array_unshift($this->methodCalls, [$constructor, $value->getArguments()]);
136132
}
137133

138-
$checkAttributes = 80000 <= \PHP_VERSION_ID && !$value->hasTag('container.ignore_attributes');
139-
$this->methodCalls = $this->autowireCalls($reflectionClass, $isRoot, $checkAttributes);
134+
$this->methodCalls = $this->autowireCalls($reflectionClass, $isRoot);
140135

141136
if ($constructor) {
142137
[, $arguments] = array_shift($this->methodCalls);
@@ -153,16 +148,14 @@ private function doProcessValue($value, bool $isRoot = false)
153148
return $value;
154149
}
155150

156-
private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot, bool $checkAttributes): array
151+
private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot): array
157152
{
158153
$this->decoratedId = null;
159154
$this->decoratedClass = null;
160155
$this->getPreviousValue = null;
161156

162-
if ($isRoot && ($definition = $this->container->getDefinition($this->currentId)) && ($decoratedDefinition = $definition->getDecoratedService()) && null !== ($innerId = $decoratedDefinition[0]) && $this->container->has($innerId)) {
163-
// If the class references to itself and is decorated, provide the inner service id and class to not get a circular reference
164-
$this->decoratedClass = $this->container->findDefinition($innerId)->getClass();
165-
$this->decoratedId = $decoratedDefinition[1] ?? $this->currentId.'.inner';
157+
if ($isRoot && ($definition = $this->container->getDefinition($this->currentId)) && null !== ($this->decoratedId = $definition->innerServiceId) && $this->container->has($this->decoratedId)) {
158+
$this->decoratedClass = $this->container->findDefinition($this->decoratedId)->getClass();
166159
}
167160

168161
$patchedIndexes = [];
@@ -184,7 +177,7 @@ private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot,
184177
}
185178
}
186179

187-
$arguments = $this->autowireMethod($reflectionMethod, $arguments, $checkAttributes, $i);
180+
$arguments = $this->autowireMethod($reflectionMethod, $arguments, $i);
188181

189182
if ($arguments !== $call[1]) {
190183
$this->methodCalls[$i][1] = $arguments;
@@ -227,7 +220,7 @@ private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot,
227220
*
228221
* @throws AutowiringFailedException
229222
*/
230-
private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments, bool $checkAttributes, int $methodIndex): array
223+
private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments, int $methodIndex): array
231224
{
232225
$class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId;
233226
$method = $reflectionMethod->name;
@@ -246,26 +239,6 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
246239

247240
$type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true);
248241

249-
if ($checkAttributes) {
250-
foreach ($parameter->getAttributes() as $attribute) {
251-
if (TaggedIterator::class === $attribute->getName()) {
252-
$attribute = $attribute->newInstance();
253-
$arguments[$index] = new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute);
254-
break;
255-
}
256-
257-
if (TaggedLocator::class === $attribute->getName()) {
258-
$attribute = $attribute->newInstance();
259-
$arguments[$index] = new ServiceLocatorArgument(new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute, null, true));
260-
break;
261-
}
262-
}
263-
264-
if ('' !== ($arguments[$index] ?? '')) {
265-
continue;
266-
}
267-
}
268-
269242
if (!$type) {
270243
if (isset($arguments[$index])) {
271244
continue;
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
15+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
16+
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
17+
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
18+
use Symfony\Component\DependencyInjection\ContainerBuilder;
19+
use Symfony\Component\DependencyInjection\Definition;
20+
use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
21+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
22+
23+
final class AutowireTaggedArgumentsPass extends AbstractRecursivePass
24+
{
25+
public function process(ContainerBuilder $container): void
26+
{
27+
if (\PHP_VERSION_ID < 80000) {
28+
return;
29+
}
30+
31+
parent::process($container);
32+
}
33+
34+
protected function processValue($value, bool $isRoot = false)
35+
{
36+
$value = parent::processValue($value, $isRoot);
37+
38+
if (!$value instanceof Definition
39+
|| !$value->isAutowired()
40+
|| $value->isAbstract()
41+
|| $value->hasTag('container.ignore_attributes')
42+
|| !($reflectionClass = $this->container->getReflectionClass($value->getClass(), false))
43+
) {
44+
return $value;
45+
}
46+
47+
$methodCalls = $value->getMethodCalls();
48+
49+
try {
50+
if ($constructor = $this->getConstructor($value, false)) {
51+
array_unshift($methodCalls, [$constructor, $value->getArguments()]);
52+
}
53+
} catch (RuntimeException $e) {
54+
throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e);
55+
}
56+
57+
$methodCalls = $this->autowireCalls($methodCalls, $reflectionClass, $isRoot);
58+
59+
if ($constructor) {
60+
[, $arguments] = array_shift($methodCalls);
61+
62+
if ($arguments !== $value->getArguments()) {
63+
$value->setArguments($arguments);
64+
}
65+
}
66+
67+
if ($methodCalls !== $value->getMethodCalls()) {
68+
$value->setMethodCalls($methodCalls);
69+
}
70+
71+
return $value;
72+
}
73+
74+
private function autowireCalls(array $methodCalls, \ReflectionClass $reflectionClass, bool $isRoot): array
75+
{
76+
foreach ($methodCalls as $i => $call) {
77+
[$method, $arguments] = $call;
78+
79+
if ($method instanceof \ReflectionFunctionAbstract) {
80+
$reflectionMethod = $method;
81+
} else {
82+
$definition = new Definition($reflectionClass->name);
83+
try {
84+
$reflectionMethod = $this->getReflectionMethod($definition, $method);
85+
} catch (RuntimeException $e) {
86+
if ($definition->getFactory()) {
87+
continue;
88+
}
89+
throw $e;
90+
}
91+
}
92+
93+
$arguments = $this->autowireMethod($reflectionMethod, $arguments);
94+
95+
if ($arguments !== $call[1]) {
96+
$methodCalls[$i][1] = $arguments;
97+
}
98+
}
99+
100+
return $methodCalls;
101+
}
102+
103+
private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments): array
104+
{
105+
$parameters = $reflectionMethod->getParameters();
106+
if ($reflectionMethod->isVariadic()) {
107+
array_pop($parameters);
108+
}
109+
110+
foreach ($parameters as $index => $parameter) {
111+
if (\array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
112+
continue;
113+
}
114+
115+
foreach ($parameter->getAttributes() as $attribute) {
116+
if (TaggedIterator::class === $attribute->getName()) {
117+
$attribute = $attribute->newInstance();
118+
$arguments[$index] = new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute);
119+
break;
120+
}
121+
122+
if (TaggedLocator::class === $attribute->getName()) {
123+
$attribute = $attribute->newInstance();
124+
$arguments[$index] = new ServiceLocatorArgument(new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute, null, true));
125+
break;
126+
}
127+
}
128+
}
129+
130+
return $arguments;
131+
}
132+
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,12 @@ public function __construct()
6161
new ResolveNamedArgumentsPass(),
6262
new AutowireRequiredMethodsPass(),
6363
new AutowireRequiredPropertiesPass(),
64+
new AutowireTaggedArgumentsPass(),
6465
new ResolveBindingsPass(),
65-
new CheckDefinitionValidityPass(),
66-
new AutowirePass(false),
6766
new ServiceLocatorTagPass(),
6867
new DecoratorServicePass(),
68+
new CheckDefinitionValidityPass(),
69+
new AutowirePass(false),
6970
new ResolveTaggedIteratorArgumentPass(),
7071
new ResolveServiceSu 10000 bscribersPass(),
7172
new ResolveReferencesToAliasesPass(),

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -985,8 +985,8 @@ public function testAutowireDecorator()
985985
->setAutowired(true)
986986
;
987987

988-
(new AutowirePass())->process($container);
989988
(new DecoratorServicePass())->process($container);
989+
(new AutowirePass())->process($container);
990990

991991
$definition = $container->getDefinition(Decorator::class);
992992
$this->assertSame(Decorator::class.'.inner', (string) $definition->getArgument(1));
@@ -1008,8 +1008,8 @@ public function testAutowireDecoratorChain()
10081008
->setAutowired(true)
10091009
;
10101010

1011-
(new AutowirePass())->process($container);
10121011
(new DecoratorServicePass())->process($container);
1012+
(new AutowirePass())->process($container);
10131013

10141014
$definition = $container->getDefinition(DecoratedDecorator::class);
10151015
$this->assertSame(DecoratedDecorator::class.'.inner', (string) $definition->getArgument(0));
@@ -1026,8 +1026,8 @@ public function testAutowireDecoratorRenamedId()
10261026
->setAutowired(true)
10271027
;
10281028

1029-
(new AutowirePass())->process($container);
10301029
(new DecoratorServicePass())->process($container);
1030+
(new AutowirePass())->process($container);
10311031

10321032
$definition = $container->getDefinition(Decorator::class);
10331033
$this->assertSame('renamed', (string) $definition->getArgument(1));
@@ -1044,11 +1044,12 @@ public function testDoNotAutowireDecoratorWhenSeveralArgumentOfTheType()
10441044
->setAutowired(true)
10451045
;
10461046

1047+
(new DecoratorServicePass())->process($container);
10471048
try {
10481049
(new AutowirePass())->process($container);
10491050
$this->fail('AutowirePass should have thrown an exception');
10501051
} catch (AutowiringFailedException $e) {
1051-
$this->assertSame('Cannot autowire service "Symfony\Component\DependencyInjection\Tests\Compiler\NonAutowirableDecorator": argument "$decorated1" of method "__construct()" references interface "Symfony\Component\DependencyInjection\Tests\Compiler\DecoratorInterface" but no such service exists. You should maybe alias this interface to one of these existing services: "Symfony\Component\DependencyInjection\Tests\Compiler\Decorated", "Symfony\Component\DependencyInjection\Tests\Compiler\NonAutowirableDecorator".', (string) $e->getMessage());
1052+
$this->assertSame('Cannot autowire service "Symfony\Component\DependencyInjection\Tests\Compiler\NonAutowirableDecorator": argument "$decorated1" of method "__construct()" references interface "Symfony\Component\DependencyInjection\Tests\Compiler\DecoratorInterface" but no such service exists. You should maybe alias this interface to one of these existing services: "Symfony\Component\DependencyInjection\Tests\Compiler\NonAutowirableDecorator", "Symfony\Component\DependencyInjection\Tests\Compiler\NonAutowirableDecorator.inner".', (string) $e->getMessage());
10521053
}
10531054
}
10541055

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,33 @@ public function testCanDecorateServiceLocator()
175175
$this->assertSame($container->get('foo'), $container->get(DecoratedServiceLocator::class)->get('foo'));
176176
}
177177

178+
public function testAliasDecoratedService()
179+
{
180+
$container = new ContainerBuilder();
181+
182+
$container->register('service', ServiceLocator::class)
183+
->setPublic(true)
184+
->setArguments([[]])
185+
;
186+
$container->register('decorator', DecoratedServiceLocator::class)
187+
->setDecoratedService('service')
188+
->setAutowired(true)
189+
->setPublic(true)
190+
;
191+
$container->setAlias(ServiceLocator::class, 'decorator.inner')
192+
->setPublic(true)
193+
;
194+
$container->register('user_service', DecoratedServiceLocato C2EE r::class)
195+
->setAutowired(true)
196+
;
197+
198+
$container->compile();
199+
200+
$this->assertInstanceOf(DecoratedServiceLocator::class, $container->get('service'));
201+
$this->assertInstanceOf(ServiceLocator::class, $container->get(ServiceLocator::class));
202+
$this->assertSame($container->get('service'), $container->get('decorator'));
203+
}
204+
178205
/**
179206
* @dataProvider getYamlCompileTests
180207
*/

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ public function getRemovedIds(): array
4646
return [
4747
'.service_locator.DlIAmAe' => true,
4848
'.service_locator.DlIAmAe.foo_service' => true,
49-
'.service_locator.t5IGRMW' => true,
5049
'Psr\\Container\\ContainerInterface' => true,
5150
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
5251
'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => true,

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber_php81.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ public function getRemovedIds(): array
4646
return [
4747
'.service_locator.JmEob1b' => true,
4848
'.service_locator.JmEob1b.foo_service' => true,
49-
'.service_locator.KIgkoLM' => true,
5049
'Psr\\Container\\ContainerInterface' => true,
5150
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
5251
'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => true,

0 commit comments

Comments
 (0)
0