Skip to content

Commit 84b5696

Browse files
kbondnicolas-grekas
authored andcommitted
[DependencyInjection] add Autowire parameter attribute
1 parent 0a62931 commit 84b5696

File tree

2 files changed

+66
-1
lines changed

2 files changed

+66
-1
lines changed

DependencyInjection/RegisterControllerArgumentLocatorsPass.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\HttpKernel\DependencyInjection;
1313

1414
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
15+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
1516
use Symfony\Component\DependencyInjection\Attribute\Target;
1617
use Symfony\Component\DependencyInjection\ChildDefinition;
1718
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -49,6 +50,8 @@ public function process(ContainerBuilder $container)
4950
}
5051
}
5152

53+
$emptyAutowireAttributes = class_exists(Autowire::class) ? null : [];
54+
5255
foreach ($container->findTaggedServiceIds('controller.service_arguments', true) as $id => $tags) {
5356
$def = $container->getDefinition($id);
5457
$def->setPublic(true);
@@ -122,6 +125,7 @@ public function process(ContainerBuilder $container)
122125
/** @var \ReflectionParameter $p */
123126
$type = ltrim($target = (string) ProxyHelper::getTypeHint($r, $p), '\\');
124127
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
128+
$autowireAttributes = $autowire ? $emptyAutowireAttributes : [];
125129

126130
if (isset($arguments[$r->name][$p->name])) {
127131
$target = $arguments[$r->name][$p->name];
@@ -148,7 +152,7 @@ public function process(ContainerBuilder $container)
148152
}
149153

150154
continue;
151-
} elseif (!$type || !$autowire || '\\' !== $target[0]) {
155+
} elseif (!$autowire || (!($autowireAttributes ??= $p->getAttributes(Autowire::class)) && (!$type || '\\' !== $target[0]))) {
152156
continue;
153157
} elseif (is_subclass_of($type, \UnitEnum::class)) {
154158
// do not attempt to register enum typed arguments if not already present in bindings
@@ -161,6 +165,21 @@ public function process(ContainerBuilder $container)
161165
continue;
162166
}
163167

168+
if ($autowireAttributes) {
169+
$value = $autowireAttributes[0]->newInstance()->value;
170+
171+
if ($value instanceof Reference) {
172+
$args[$p->name] = $type ? new TypedReference($value, $type, $invalidBehavior, $p->name) : new Reference($value, $invalidBehavior);
173+
} else {
174+
$args[$p->name] = new Reference('.value.'.$container->hash($value));
175+
$container->register((string) $args[$p->name], 'mixed')
176+
->setFactory('current')
177+
->addArgument([$value]);
178+
}
179+
180+
continue;
181+
}
182+
164183
if ($type && !$p->isOptional() && !$p->allowsNull() && !class_exists($type) && !interface_exists($type, false)) {
165184
$message = sprintf('Cannot determine controller argument for "%s::%s()": the $%s argument is type-hinted with the non-existent class or interface: "%s".', $class, $r->name, $p->name, $type);
166185

Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
16+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
1617
use Symfony\Component\DependencyInjection\Attribute\Target;
1718
use Symfony\Component\DependencyInjection\ChildDefinition;
1819
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
@@ -434,6 +435,36 @@ public function testBindWithTarget()
434435
$expected = ['apiKey' => new ServiceClosureArgument(new Reference('the_api_key'))];
435436
$this->assertEquals($expected, $locator->getArgument(0));
436437
}
438+
439+
public function testAutowireAttribute()
440+
{
441+
if (!class_exists(Autowire::class)) {
442+
$this->markTestSkipped('#[Autowire] attribute not available.');
443+
}
444+
445+
$container = new ContainerBuilder();
446+
$resolver = $container->register('argument_resolver.service', 'stdClass')->addArgument([]);
447+
448+
$container->register('some.id', \stdClass::class);
449+
$container->setParameter('some.parameter', 'foo');
450+
451+
$container->register('foo', WithAutowireAttribute::class)
452+
->addTag('controller.service_arguments');
453+
454+
(new RegisterControllerArgumentLocatorsPass())->process($container);
455+
456+
$locatorId = (string) $resolver->getArgument(0);
457+
$container->getDefinition($locatorId)->setPublic(true);
458+
459+
$container->compile();
460+
461+
$locator = $container->get($locatorId)->get('foo::fooAction');
462+
463+
$this->assertInstanceOf(\stdClass::class, $locator->get('service1'));
464+
$this->assertSame('foo/bar', $locator->get('value'));
465+
$this->assertSame('foo', $locator->get('expression'));
466+
$this->assertFalse($locator->has('service2'));
467+
}
437468
}
438469

439470
class RegisterTestController
@@ -511,3 +542,18 @@ public function fooAction(
511542
) {
512543
}
513544
}
545+
546+
class WithAutowireAttribute
547+
{
548+
public function fooAction(
549+
#[Autowire(service: 'some.id')]
550+
\stdClass $service1,
551+
#[Autowire(value: '%some.parameter%/bar')]
552+
string $value,
553+
#[Autowire(expression: "parameter('some.parameter')")]
554+
string $expression,
555+
#[Autowire(service: 'invalid.id')]
556+
\stdClass $service2 = null,
557+
) {
558+
}
559+
}

0 commit comments

Comments
 (0)