Skip to content

Commit 54febe6

Browse files
[DependencyInjection] Generalize and simplify parsing of autowiring attributes
1 parent fa88291 commit 54febe6

File tree

3 files changed

+30
-40
lines changed

3 files changed

+30
-40
lines changed

Compiler/AutowirePass.php

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
use Symfony\Component\DependencyInjection\Reference;
2626
use Symfony\Component\DependencyInjection\TypedReference;
2727
use Symfony\Component\VarExporter\ProxyHelper;
28-
use Symfony\Contracts\Service\Attribute\SubscribedService;
2928

3029
/**
3130
* Inspects existing service definitions and wires the autowired ones using the type hints of their classes.
@@ -106,18 +105,19 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
106105
private function doProcessValue(mixed $value, bool $isRoot = false): mixed
107106
{
108107
if ($value instanceof TypedReference) {
109-
if ($attributes = $value->getAttributes()) {
110-
$attribute = array_pop($attributes);
111-
112-
if ($attributes) {
113-
throw new AutowiringFailedException($this->currentId, sprintf('Using multiple attributes with "%s" is not supported.', SubscribedService::class));
108+
foreach ($value->getAttributes() as $attribute) {
109+
if ($attribute === $v = $this->processValue($attribute)) {
110+
continue;
114111
}
115-
116-
if (!$attribute instanceof Target) {
117-
return $this->processAttribute($attribute, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $value->getInvalidBehavior());
112+
if (!$attribute instanceof Autowire || !$v instanceof Reference) {
113+
return $v;
118114
}
119115

120-
$value = new TypedReference($value->getType(), $value->getType(), $value->getInvalidBehavior(), $attribute->name, [$attribute]);
116+
$invalidBehavior = ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE !== $v->getInvalidBehavior() ? $v->getInvalidBehavior() : $value->getInvalidBehavior();
117+
$value = $v instanceof TypedReference
118+
? new TypedReference($v, $v->getType(), $invalidBehavior, $v->getName() ?? $value->getName(), array_merge($v->getAttributes(), $value->getAttributes()))
119+
: new TypedReference($v, $value->getType(), $invalidBehavior, $value->getName(), $value->getAttributes());
120+
break;
121121
}
122122
if ($ref = $this->getAutowiredReference($value, true)) {
123123
return $ref;
@@ -173,22 +173,6 @@ private function doProcessValue(mixed $value, bool $isRoot = false): mixed
173173
return $value;
174174
}
175175

176-
private function processAttribute(object $attribute, bool $isOptional = false): mixed
177-
{
178-
switch (true) {
179-
case $attribute instanceof Autowire:
180-
if ($isOptional && $attribute->value instanceof Reference) {
181-
return new Reference($attribute->value, ContainerInterface::NULL_ON_INVALID_REFERENCE);
182-
}
183-
// no break
184-
case $attribute instanceof AutowireDecorated:
185-
case $attribute instanceof MapDecorated:
186-
return $this->processValue($attribute);
187-
}
188-
189-
throw new AutowiringFailedException($this->currentId, sprintf('"%s" is an unsupported attribute.', $attribute::class));
190-
}
191-
192176
private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot, bool $checkAttributes): array
193177
{
194178
$this->decoratedId = null;
@@ -281,11 +265,15 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
281265
}
282266

283267
$type = ProxyHelper::exportType($parameter, true);
268+
$target = null;
269+
$name = Target::parseName($parameter, $target);
270+
$target = $target ? [$target] : [];
284271

285272
if ($checkAttributes) {
286273
foreach ($parameter->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
287274
$attribute = $attribute->newInstance();
288-
$value = $this->processAttribute($attribute, $parameter->allowsNull());
275+
$invalidBehavior = $parameter->allowsNull() ? ContainerInterface::NULL_ON_INVALID_REFERENCE : ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE;
276+
$value = $this->processValue(new TypedReference($type ?: '?', $type ?: 'mixed', $invalidBehavior, $name, [$attribute, ...$target]));
289277

290278
if ($attribute instanceof AutowireCallable || 'Closure' === $type && \is_array($value)) {
291279
$value = (new Definition('Closure'))
@@ -299,13 +287,13 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
299287
}
300288

301289
foreach ($parameter->getAttributes(AutowireDecorated::class) as $attribute) {
302-
$arguments[$index] = $this->processAttribute($attribute->newInstance(), $parameter->allowsNull());
290+
$arguments[$index] = $this->processValue($attribute->newInstance());
303291

304292
continue 2;
305293
}
306294

307295
foreach ($parameter->getAttributes(MapDecorated::class) as $attribute) {
308-
$arguments[$index] = $this->processAttribute($attribute->newInstance(), $parameter->allowsNull());
296+
$arguments[$index] = $this->processValue($attribute->newInstance());
309297

310298
continue 2;
311299
}
@@ -338,9 +326,8 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
338326
continue;
339327
}
340328

341-
$getValue = function () use ($type, $parameter, $class, $method) {
342-
$name = Target::parseName($parameter, $target);
343-
if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $name, $target ? [$target] : []), false)) {
329+
$getValue = function () use ($type, $parameter, $class, $method, $name, $target) {
330+
if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $name, $target), false)) {
344331
$failureMessage = $this->createTypeNotFoundMessageCallback($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
345332

346333
if ($parameter->isDefaultValueAvailable()) {
@@ -354,7 +341,7 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
354341
return $value;
355342
};
356343

357-
if ($this->decoratedClass && $isDecorated = is_a($this->decoratedClass, $type, true)) {
344+
if ($this->decoratedClass && is_a($this->decoratedClass, $type, true)) {
358345
if ($this->getPreviousValue) {
359346
// The inner service is injected only if there is only 1 argument matching the type of the decorated class
360347
// across all arguments of all autowired methods.
@@ -412,7 +399,9 @@ private function getAutowiredReference(TypedReference $reference, bool $filterTy
412399
$type = implode($m[0], $types);
413400
}
414401

415-
if (null !== $name = $reference->getName()) {
402+
$name = (array_filter($reference->getAttributes(), static fn ($a) => $a instanceof Target)[0] ?? null)?->name;
403+
404+
if (null !== $name ??= $reference->getName()) {
416405
if ($this->container->has($alias = $type.' $'.$name) && !$this->container->findDefinition($alias)->isAbstract()) {
417406
return new TypedReference($alias, $type, $reference->getInvalidBehavior());
418407
}

Tests/Compiler/AutowirePassTest.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Component\Config\Resource\ClassExistenceResource;
2121
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
2222
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
23+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
2324
use Symfony\Component\DependencyInjection\Compiler\AutowireAsDecoratorPass;
2425
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
2526
use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass;
@@ -1302,16 +1303,16 @@ public function testAutowireAttribute()
13021303
$definition = $container->getDefinition(AutowireAttribute::class);
13031304

13041305
$this->assertCount(10, $definition->getArguments());
1305-
$this->assertEquals(new Reference('some.id'), $definition->getArgument(0));
1306+
$this->assertEquals(new TypedReference('some.id', 'stdClass', attributes: [new Autowire(service: 'some.id')]), $definition->getArgument(0));
13061307
$this->assertEquals(new Expression("parameter('some.parameter')"), $definition->getArgument(1));
13071308
$this->assertSame('foo/bar', $definition->getArgument(2));
13081309
$this->assertNull($definition->getArgument(3));
1309-
$this->assertEquals(new Reference('some.id'), $definition->getArgument(4));
1310+
$this->assertEquals(new TypedReference('some.id', 'stdClass', attributes: [new Autowire(service: 'some.id')]), $definition->getArgument(4));
13101311
$this->assertEquals(new Expression("parameter('some.parameter')"), $definition->getArgument(5));
13111312
$this->assertSame('bar', $definition->getArgument(6));
13121313
$this->assertSame('@bar', $definition->getArgument(7));
13131314
$this->assertSame('foo', $definition->getArgument(8));
1314-
$this->assertEquals(new Reference('invalid.id', ContainerInterface::NULL_ON_INVALID_REFERENCE), $definition->getArgument(9));
1315+
$this->assertEquals(new TypedReference('invalid.id', 'stdClass', ContainerInterface::NULL_ON_INVALID_REFERENCE, attributes: [new Autowire(service: 'invalid.id')]), $definition->getArgument(9));
13151316

13161317
$container->compile();
13171318

Tests/Compiler/RegisterServiceSubscribersPassTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -459,11 +459,11 @@ public static function getSubscribedServices(): array
459459
$expected = [
460460
'tagged.iterator' => new ServiceClosureArgument(new TaggedIteratorArgument('tag')),
461461
'tagged.locator' => new ServiceClosureArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('tag', 'tag', needsIndexes: true))),
462-
'autowired' => new ServiceClosureArgument(new Reference('service.id')),
463-
'autowired.nullable' => new ServiceClosureArgument(new Reference('service.id', ContainerInterface::NULL_ON_INVALID_REFERENCE)),
462+
'autowired' => new ServiceClosureArgument(new TypedReference('service.id', 'stdClass', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'autowired', [new Autowire(service: 'service.id')])),
463+
'autowired.nullable' => new ServiceClosureArgument(new TypedReference('service.id', 'stdClass', ContainerInterface::IGNORE_ON_INVALID_REFERENCE, 'autowired.nullable', [new Autowire(service: 'service.id')])),
464464
'autowired.parameter' => new ServiceClosureArgument('foobar'),
465465
'autowire.decorated' => new ServiceClosureArgument(new Reference('.service_locator.QVDPERh.inner', ContainerInterface::NULL_ON_INVALID_REFERENCE)),
466-
'target' => new ServiceClosureArgument(new TypedReference('stdClass', 'stdClass', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'someTarget', [new Target('someTarget')])),
466+
'target' => new ServiceClosureArgument(new TypedReference('stdClass', 'stdClass', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'target', [new Target('someTarget')])),
467467
];
468468
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
469469
}

0 commit comments

Comments
 (0)