Skip to content

Commit 85882e3

Browse files
nicolas-grekasderrabus
authored andcommitted
Autowire arguments using attributes
1 parent f2d25f5 commit 85882e3

12 files changed

+57
-105
lines changed

Attribute/BindTaggedLocator.php renamed to Attribute/TaggedIterator.php

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

1414
#[\Attribute(\Attribute::TARGET_PARAMETER)]
15-
class BindTaggedLocator
15+
class TaggedIterator
1616
{
1717
public function __construct(
1818
public string $tag,

Attribute/BindTaggedIterator.php renamed to Attribute/TaggedLocator.php

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

1414
#[\Attribute(\Attribute::TARGET_PARAMETER)]
15-
class BindTaggedIterator
15+
class TaggedLocator
1616
{
1717
public function __construct(
1818
public string $tag,

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ CHANGELOG
99
* Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8
1010
* Add `#[AsTaggedItem]` attribute for defining the index and priority of classes found in tagged iterators/locators
1111
* Add autoconfigurable attributes
12-
* Add support for binding tagged iterators and locators to constructor arguments via attributes
12+
* Add support for autowiring tagged iterators and locators via attributes on PHP 8
1313
* Add support for per-env configuration in loaders
1414
* Add `ContainerBuilder::willBeAvailable()` to help with conditional configuration
1515
* Add support an integer return value for default_index_method

Compiler/AttributeAutoconfigurationPass.php

Lines changed: 10 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -11,102 +11,46 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14-
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
15-
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
16-
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
17-
use Symfony\Component\DependencyInjection\Attribute\BindTaggedIterator;
18-
use Symfony\Component\DependencyInjection\Attribute\BindTaggedLocator;
1914
use Symfony\Component\DependencyInjection\ChildDefinition;
2015
use Symfony\Component\DependencyInjection\ContainerBuilder;
2116
use Symfony\Component\DependencyInjection\Definition;
22-
use Symfony\Component\DependencyInjection\Exception\LogicException;
2317

2418
/**
2519
* @author Alexander M. Turek <me@derrabus.de>
2620
*/
2721
final class AttributeAutoconfigurationPass extends AbstractRecursivePass
2822
{
29-
/** @var array<string, callable>|null */
30-
private $argumentConfigurators;
31-
3223
public function process(ContainerBuilder $container): void
3324
{
34-
if (80000 > \PHP_VERSION_ID) {
25+
if (80000 > \PHP_VERSION_ID || !$container->getAutoconfiguredAttributes()) {
3526
return;
3627
}
3728

38-
$this->argumentConfigurators = [
39-
BindTaggedIterator::class => static function (BindTaggedIterator $attribute) {
40-
return new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute);
41-
},
42-
BindTaggedLocator::class => static function (BindTaggedLocator $attribute) {
43-
return new ServiceLocatorArgument(new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute));
44-
},
45-
];
46-
4729
parent::process($container);
48-
49-
$this->argumentConfigurators = null;
5030
}
5131

5232
protected function processValue($value, bool $isRoot = false)
5333
{
54-
if ($value instanceof Definition
55-
&& $value->isAutoconfigured()
56-
&& !$value->isAbstract()
57-
&& !$value->hasTag('container.ignore_attributes')
34+
if (!$value instanceof Definition
35+
|| !$value->isAutoconfigured()
36+
|| $value->isAbstract()
37+
|| $value->hasTag('container.ignore_attributes')
38+
|| !($reflector = $this->container->getReflectionClass($value->getClass(), false))
5839
) {
59-
$value = $this->processDefinition($value);
60-
}
61-
62-
return parent::processValue($value, $isRoot);
63-
}
64-
65-
private function processDefinition(Definition $definition): Definition
66-
{
67-
if (!$reflector = $this->container->getReflectionClass($definition->getClass(), false)) {
68-
return $definition;
40+
return parent::processValue($value, $isRoot);
6941
}
7042

7143
$autoconfiguredAttributes = $this->container->getAutoconfiguredAttributes();
72-
73-
$instanceof = $definition->getInstanceofConditionals();
44+
$instanceof = $value->getInstanceofConditionals();
7445
$conditionals = $instanceof[$reflector->getName()] ?? new ChildDefinition('');
7546
foreach ($reflector->getAttributes() as $attribute) {
7647
if ($configurator = $autoconfiguredAttributes[$attribute->getName()] ?? null) {
7748
$configurator($conditionals, $attribute->newInstance(), $reflector);
7849
}
7950
}
80-
81-
if ($constructor = $this->getConstructor($definition, false)) {
82-
$definition = $this->bindArguments($definition, $constructor);
83-
}
84-
8551
$instanceof[$reflector->getName()] = $conditionals;
86-
$definition->setInstanceofConditionals($instanceof);
87-
88-
return $definition;
89-
}
90-
91-
private function bindArguments(Definition $definition, \ReflectionFunctionAbstract $constructor): Definition
92-
{
93-
$bindings = $definition->getBindings();
94-
foreach ($constructor->getParameters() as $reflectionParameter) {
95-
$argument = null;
96-
foreach ($reflectionParameter->getAttributes() as $attribute) {
97-
if (!$configurator = $this->argumentConfigurators[$attribute->getName()] ?? null) {
98-
continue;
99-
}
100-
if ($argument) {
101-
throw new LogicException(sprintf('Cannot autoconfigure argument "$%s": More than one autoconfigurable attribute found.', $reflectionParameter->getName()));
102-
}
103-
$argument = $configurator($attribute->newInstance());
104-
}
105-
if ($argument) {
106-
$bindings['$'.$reflectionParameter->getName()] = new BoundArgument($argument);
107-
}
108-
}
52+
$value->setInstanceofConditionals($instanceof);
10953

110-
return $definition->setBindings($bindings);
54+
return parent::processValue($value, $isRoot);
11155
}
11256
}

Compiler/AutowirePass.php

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
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;
1519
use Symfony\Component\DependencyInjection\ContainerBuilder;
1620
use Symfony\Component\DependencyInjection\Definition;
1721
use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
@@ -123,7 +127,8 @@ private function doProcessValue($value, bool $isRoot = false)
123127
array_unshift($this->methodCalls, [$constructor, $value->getArguments()]);
124128
}
125129

126-
$this->methodCalls = $this->autowireCalls($reflectionClass, $isRoot);
130+
$checkAttributes = 80000 <= \PHP_VERSION_ID && !$value->hasTag('container.ignore_attributes');
131+
$this->methodCalls = $this->autowireCalls($reflectionClass, $isRoot, $checkAttributes);
127132

128133
if ($constructor) {
129134
[, $arguments] = array_shift($this->methodCalls);
@@ -140,7 +145,7 @@ private function doProcessValue($value, bool $isRoot = false)
140145
return $value;
141146
}
142147

143-
private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot): array
148+
private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot, bool $checkAttributes): array
144149
{
145150
$this->decoratedId = null;
146151
$this->decoratedClass = null;
@@ -168,7 +173,7 @@ private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot):
168173
}
169174
}
170175

171-
$arguments = $this->autowireMethod($reflectionMethod, $arguments);
176+
$arguments = $this->autowireMethod($reflectionMethod, $arguments, $checkAttributes);
172177

173178
if ($arguments !== $call[1]) {
174179
$this->methodCalls[$i][1] = $arguments;
@@ -185,7 +190,7 @@ private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot):
185190
*
186191
* @throws AutowiringFailedException
187192
*/
188-
private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments): array
193+
private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments, bool $checkAttributes): array
189194
{
190195
$class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId;
191196
$method = $reflectionMethod->name;
@@ -201,6 +206,26 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a
201206

202207
$type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true);
203208

209+
if ($checkAttributes) {
210+
foreach ($parameter->getAttributes() as $attribute) {
211+
if (TaggedIterator::class === $attribute->getName()) {
212+
$attribute = $attribute->newInstance();
213+
$arguments[$index] = new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute);
214+
break;
215+
}
216+
217+
if (TaggedLocator::class === $attribute->getName()) {
218+
$attribute = $attribute->newInstance();
219+
$arguments[$index] = new ServiceLocatorArgument(new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute));
220+
break;
221+
}
222+
}
223+
224+
if ('' !== ($arguments[$index] ?? '')) {
225+
continue;
226+
}
227+
}
228+
204229
if (!$type) {
205230
if (isset($arguments[$index])) {
206231
continue;

Compiler/PassConfig.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ public function __construct()
6262
new AutowireRequiredMethodsPass(),
6363
new AutowireRequiredPropertiesPass(),
6464
new ResolveBindingsPass(),
65-
new ServiceLocatorTagPass(),
6665
new DecoratorServicePass(),
6766
new CheckDefinitionValidityPass(),
6867
new AutowirePass(false),
68+
new ServiceLocatorTagPass(),
6969
new ResolveTaggedIteratorArgumentPass(),
7070
new ResolveServiceSubscribersPass(),
7171
new ResolveReferencesToAliasesPass(),

Tests/Compiler/IntegrationTest.php

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
2121
use Symfony\Component\DependencyInjection\ContainerBuilder;
2222
use Symfony\Component\DependencyInjection\Definition;
23-
use Symfony\Component\DependencyInjection\Exception\LogicException;
2423
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
2524
use Symfony\Component\DependencyInjection\Reference;
2625
use Symfony\Component\DependencyInjection\ServiceLocator;
@@ -33,7 +32,6 @@
3332
use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumer;
3433
use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumerConsumer;
3534
use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumerFactory;
36-
use Symfony\Component\DependencyInjection\Tests\Fixtures\MultipleArgumentBindings;
3735
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService1;
3836
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2;
3937
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3;
@@ -338,7 +336,7 @@ public function testTaggedServiceWithIndexAttributeAndDefaultMethodConfiguredVia
338336
->addTag('foo_bar', ['foo' => 'foo'])
339337
;
340338
$container->register(IteratorConsumer::class)
341-
->setAutoconfigured(true)
339+
->setAutowired(true)
342340
->setPublic(true)
343341
;
344342

@@ -391,7 +389,7 @@ public function testTaggedLocatorConfiguredViaAttribute()
391389
->addTag('foo_bar', ['foo' => 'foo'])
392390
;
393391
$container->register(LocatorConsumer::class)
394-
->setAutoconfigured(true)
392+
->setAutowired(true)
395393
->setPublic(true)
396394
;
397395

@@ -419,7 +417,7 @@ public function testNestedDefinitionWithAutoconfiguredConstructorArgument()
419417
->setPublic(true)
420418
->setArguments([
421419
(new Definition(LocatorConsumer::class))
422-
->setAutoconfigured(true),
420+
->setAutowired(true),
423421
])
424422
;
425423

@@ -445,7 +443,7 @@ public function testFactoryWithAutoconfiguredArgument()
445443
$container->register(LocatorConsumerFactory::class);
446444
$container->register(LocatorConsumer::class)
447445
->setPublic(true)
448-
->setAutoconfigured(true)
446+
->setAutowired(true)
449447
->setFactory(new Reference(LocatorConsumerFactory::class))
450448
;
451449

@@ -458,22 +456,6 @@ public function testFactoryWithAutoconfiguredArgument()
458456
self::assertSame($container->get(FooTagClass::class), $locator->get('my_service'));
459457
}
460458

461-
/**
462-
* @requires PHP 8
463-
*/
464-
public function testMultipleArgumentBindings()
465-
{
466-
$container = new ContainerBuilder();
467-
$container->register(MultipleArgumentBindings::class)
468-
->setPublic(true)
469-
->setAutoconfigured(true)
470-
;
471-
472-
$this->expectException(LogicException::class);
473-
$this->expectExceptionMessage('Cannot autoconfigure argument "$collection": More than one autoconfigurable attribute found.');
474-
$container->compile();
475-
}
476-
477459
public function testTaggedServiceWithDefaultPriorityMethod()
478460
{
479461
$container = new ContainerBuilder();

Tests/Fixtures/IteratorConsumer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
1313

14-
use Symfony\Component\DependencyInjection\Attribute\BindTaggedIterator;
14+
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
1515

1616
final class IteratorConsumer
1717
{
1818
public function __construct(
19-
#[BindTaggedIterator('foo_bar', indexAttribute: 'foo')]
19+
#[TaggedIterator('foo_bar', indexAttribute: 'foo')]
2020
private iterable $param,
2121
) {
2222
}

Tests/Fixtures/LocatorConsumer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
1313

1414
use Psr\Container\ContainerInterface;
15-
use Symfony\Component\DependencyInjection\Attribute\BindTaggedLocator;
15+
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
1616

1717
final class LocatorConsumer
1818
{
1919
public function __construct(
20-
#[BindTaggedLocator('foo_bar', indexAttribute: 'foo')]
20+
#[TaggedLocator('foo_bar', indexAttribute: 'foo')]
2121
private ContainerInterface $locator,
2222
) {
2323
}

Tests/Fixtures/LocatorConsumerFactory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
1313

1414
use Psr\Container\ContainerInterface;
15-
use Symfony\Component\DependencyInjection\Attribute\BindTaggedLocator;
15+
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
1616

1717
final class LocatorConsumerFactory
1818
{
1919
public function __invoke(
20-
#[BindTaggedLocator('foo_bar', indexAttribute: 'key')]
20+
#[TaggedLocator('foo_bar', indexAttribute: 'key')]
2121
ContainerInterface $locator
2222
): LocatorConsumer {
2323
return new LocatorConsumer($locator);

0 commit comments

Comments
 (0)