Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit dd919a7

Browse files
feature #40406 [DependencyInjection] Autowire arguments using attributes (derrabus, nicolas-grekas)
This PR was merged into the 5.3-dev branch. Discussion ---------- [DependencyInjection] Autowire arguments using attributes | Q | A | ------------- | --- | Branch? | 5.x | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | N/A | License | MIT | Doc PR | TODO This PR allows us to bind arguments via attributes. This mechanism is enabled for tagged iterators and service locators for now. To receive an iterator with all services tagged with `my_tag`: ```php use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; class MyService { public function __construct( #[TaggedIterator('my_tag')] private iterator $taggedServices, ) { } } ``` To receive a locator with all services tagged with `my_tag`: ```php use Psr\Container\ContainerInterface; use Symfony\Component\DependencyInjection\Attribute\TaggedLocator; class MyService { public function __construct( #[TaggedLocator('my_tag')] private ContainerInterface $taggedServices, ) { } } ``` Commits ------- 91fbc90 Autowire arguments using attributes b86aa3d [DependencyInjection] Bind constructor arguments via attributes
2 parents 87d031c + 91fbc90 commit dd919a7

13 files changed

+336
-24
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Attribute;
13+
14+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
15+
class TaggedIterator
16+
{
17+
public function __construct(
18+
public string $tag,
19+
public ?string $indexAttribute = null,
20+
) {
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Attribute;
13+
14+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
15+
class TaggedLocator
16+
{
17+
public function __construct(
18+
public string $tag,
19+
public ?string $indexAttribute = null,
20+
) {
21+
}
22+
}

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
* 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 autowiring tagged iterators and locators via attributes on PHP 8
1213
* Add support for per-env configuration in loaders
1314
* Add `ContainerBuilder::willBeAvailable()` to help with conditional configuration
1415
* Add support an integer return value for default_index_method

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

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,44 @@
1313

1414
use Symfony\Component\DependencyInjection\ChildDefinition;
1515
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Definition;
1617

1718
/**
1819
* @author Alexander M. Turek <[email protected]>
1920
*/
20-
final class AttributeAutoconfigurationPass implements CompilerPassInterface
21+
final class AttributeAutoconfigurationPass extends AbstractRecursivePass
2122
{
2223
public function process(ContainerBuilder $container): void
2324
{
24-
if (80000 > \PHP_VERSION_ID) {
25+
if (80000 > \PHP_VERSION_ID || !$container->getAutoconfiguredAttributes()) {
2526
return;
2627
}
2728

28-
$autoconfiguredAttributes = $container->getAutoconfiguredAttributes();
29+
parent::process($container);
30+
}
2931

30-
foreach ($container->getDefinitions() as $id => $definition) {
31-
if (!$definition->isAutoconfigured()
32-
|| $definition->isAbstract()
33-
|| $definition->hasTag('container.ignore_attributes')
34-
|| !($reflector = $container->getReflectionClass($definition->getClass(), false))
35-
) {
36-
continue;
37-
}
32+
protected function processValue($value, bool $isRoot = false)
33+
{
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))
39+
) {
40+
return parent::processValue($value, $isRoot);
41+
}
3842

39-
$instanceof = $definition->getInstanceofConditionals();
40-
$conditionals = $instanceof[$reflector->getName()] ?? new ChildDefinition('');
41-
foreach ($reflector->getAttributes() as $attribute) {
42-
if ($configurator = $autoconfiguredAttributes[$attribute->getName()] ?? null) {
43-
$configurator($conditionals, $attribute->newInstance(), $reflector);
44-
}
43+
$autoconfiguredAttributes = $this->container->getAutoconfiguredAttributes();
44+
$instanceof = $value->getInstanceofConditionals();
45+
$conditionals = $instanceof[$reflector->getName()] ?? new ChildDefinition('');
46+
foreach ($reflector->getAttributes() as $attribute) {
47+
if ($configurator = $autoconfiguredAttributes[$attribute->getName()] ?? null) {
48+
$configurator($conditionals, $attribute->newInstance(), $reflector);
4549
}
46-
$instanceof[$reflector->getName()] = $conditionals;
47-
$definition->setInstanceofConditionals($instanceof);
4850
}
51+
$instanceof[$reflector->getName()] = $conditionals;
52+
$value->setInstanceofConditionals($instanceof);
53+
54+
return parent::processValue($value, $isRoot);
4955
}
5056
}

src/Symfony/Component/DependencyInjection/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;

src/Symfony/Component/DependencyInjection/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(),

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

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
2929
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedForDefaultPriorityClass;
3030
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooTagClass;
31+
use Symfony\Component\DependencyInjection\Tests\Fixtures\IteratorConsumer;
32+
use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumer;
33+
use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumerConsumer;
34+
use Symfony\Component\DependencyInjection\Tests\Fixtures\LocatorConsumerFactory;
3135
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService1;
3236
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2;
3337
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3;
@@ -317,6 +321,33 @@ public function testTaggedServiceWithIndexAttributeAndDefaultMethod()
317321
$this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $param);
318322
}
319323

324+
/**
325+
* @requires PHP 8
326+
*/
327+
public function testTaggedServiceWithIndexAttributeAndDefaultMethodConfiguredViaAttribute()
328+
{
329+
$container = new ContainerBuilder();
330+
$container->register(BarTagClass::class)
331+
->setPublic(true)
332+
->addTag('foo_bar', ['foo' => 'bar_tab_class_with_defaultmethod'])
333+
;
334+
$container->register(FooTagClass::class)
335+
->setPublic(true)
336+
->addTag('foo_bar', ['foo' => 'foo'])
337+
;
338+
$container->register(IteratorConsumer::class)
339+
->setAutowired(true)
340+
->setPublic(true)
341+
;
342+
343+
$container->compile();
344+
345+
$s = $container->get(IteratorConsumer::class);
346+
347+
$param = iterator_to_array($s->getParam()->getIterator());
348+
$this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $param);
349+
}
350+
320351
public function testTaggedIteratorWithMultipleIndexAttribute()
321352
{
322353
$container = new ContainerBuilder();
@@ -343,6 +374,88 @@ public function testTaggedIteratorWithMultipleIndexAttribute()
343374
$this->assertSame(['bar' => $container->get(BarTagClass::class), 'bar_duplicate' => $container->get(BarTagClass::class), 'foo_tag_class' => $container->get(FooTagClass::class)], $param);
344375
}
345376

377+
/**
378+
* @requires PHP 8
379+
*/
380+
public function testTaggedLocatorConfiguredViaAttribute()
381+
{
382+
$container = new ContainerBuilder();
383+
$container->register(BarTagClass::class)
384+
->setPublic(true)
385+
->addTag('foo_bar', ['foo' => 'bar_tab_class_with_defaultmethod'])
386+
;
387+
$container->register(FooTagClass::class)
388+
->setPublic(true)
389+
->addTag('foo_bar', ['foo' => 'foo'])
390+
;
391+
$container->register(LocatorConsumer::class)
392+
->setAutowired(true)
393+
->setPublic(true)
394+
;
395+
396+
$container->compile();
397+
398+
/** @var LocatorConsumer $s */
399+
$s = $container->get(LocatorConsumer::class);
400+
401+
$locator = $s->getLocator();
402+
self::assertSame($container->get(BarTagClass::class), $locator->get('bar_tab_class_with_defaultmethod'));
403+
self::assertSame($container->get(FooTagClass::class), $locator->get('foo'));
404+
}
405+
406+
/**
407+
* @requires PHP 8
408+
*/
409+
public function testNestedDefinitionWithAutoconfiguredConstructorArgument()
410+
{
411+
$container = new ContainerBuilder();
412+
$container->register(FooTagClass::class)
413+
->setPublic(true)
414+
->addTag('foo_bar', ['foo' => 'foo'])
415+
;
416+
$container->register(LocatorConsumerConsumer::class)
417+
->setPublic(true)
418+
->setArguments([
419+
(new Definition(LocatorConsumer::class))
420+
->setAutowired(true),
421+
])
422+
;
423+
424+
$container->compile();
425+
426+
/** @var LocatorConsumerConsumer $s */
427+
$s = $container->get(LocatorConsumerConsumer::class);
428+
429+
$locator = $s->getLocatorConsumer()->getLocator();
430+
self::assertSame($container->get(FooTagClass::class), $locator->get('foo'));
431+
}
432+
433+
/**
434+
* @requires PHP 8
435+
*/
436+
public function testFactoryWithAutoconfiguredArgument()
437+
{
438+
$container = new ContainerBuilder();
439+
$container->register(FooTagClass::class)
440+
->setPublic(true)
441+
->addTag('foo_bar', ['key' => 'my_service'])
442+
;
443+
$container->register(LocatorConsumerFactory::class);
444+
$container->register(LocatorConsumer::class)
445+
->setPublic(true)
446+
->setAutowired(true)
447+
->setFactory(new Reference(LocatorConsumerFactory::class))
448+
;
449+
450+
$container->compile();
451+
452+
/** @var LocatorConsumer $s */
453+
$s = $container->get(LocatorConsumer::class);
454+
455+
$locator = $s->getLocator();
456+
self::assertSame($container->get(FooTagClass::class), $locator->get('my_service'));
457+
}
458+
346459
public function testTaggedServiceWithDefaultPriorityMethod()
347460
{
348461
$container = new ContainerBuilder();
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Tests\Fixtures;
13+
14+
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
15+
16+
final class IteratorConsumer
17+
{
18+
public function __construct(
19+
#[TaggedIterator('foo_bar', indexAttribute: 'foo')]
20+
private iterable $param,
21+
) {
22+
}
23+
24+
public function getParam(): iterable
25+
{
26+
return $this->param;
27+
}
28+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Tests\Fixtures;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
16+
17+
final class LocatorConsumer
18+
{
19+
public function __construct(
20+
#[TaggedLocator('foo_bar', indexAttribute: 'foo')]
21+
private ContainerInterface $locator,
22+
) {
23+
}
24+
25+
public function getLocator(): ContainerInterface
26+
{
27+
return $this->locator;
28+
}
29+
}

0 commit comments

Comments
 (0)