8000 [DependencyInjection] Bind constructor arguments via attributes · symfony/symfony@e14884b · GitHub
[go: up one dir, main page]

Skip to content

Commit e14884b

Browse files
committed
[DependencyInjection] Bind constructor arguments via attributes
1 parent 10d869d commit e14884b

File tree

9 files changed

+176
-5
lines changed

9 files changed

+176
-5
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

+2-3
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@
4444
use Symfony\Component\Console\Command\Command;
4545
use Symfony\Component\DependencyInjection\Alias;
4646
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
47-
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
48-
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
47+
use Symfony\Component\DependencyInjection\Argument\TaggedLocatorArgument;
4948
use Symfony\Component\DependencyInjection\ChildDefinition;
5049
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
5150
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -2363,7 +2362,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $
23632362

23642363
if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages)) {
23652364
$container->getDefinition($classToServices[MercureTransportFactory::class])
2366-
->replaceArgument('$publisherLocator', new ServiceLocatorArgument(new TaggedIteratorArgument('mercure.publisher', null, null, true)));
2365+
->replaceArgument('$publisherLocator', new TaggedLocatorArgument('mercure.publisher', null, null, true));
23672366
} elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages)) {
23682367
$container->removeDefinition($classToServices[MercureTransportFactory::class]);
23692368
}

src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*
1717
* @author Roland Franssen <franssen.roland@gmail.com>
1818
*/
19+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
1920
class TaggedIteratorArgument extends IteratorArgument
2021
{
2122
private $tag;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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\Argument;
13+
14+
/**
15+
* Represents a closure acting as a locator for services found by tag name.
16+
*
17+
* @author Alexander M. Turek <me@derrabus.de>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
20+
class TaggedLocatorArgument extends ServiceLocatorArgument
21+
{
22+
/**
23+
* @param string $tag The name of the tag identifying the target services
24+
* @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection
25+
* @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute
26+
* @param bool $needsIndexes Whether indexes are required and should be generated when computing the map
27+
* @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute
28+
*/
29+
public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false, string $defaultPriorityMethod = null)
30+
{
31+
parent::__construct(
32+
new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, $needsIndexes, $defaultPriorityMethod)
33+
);
34+
}
35+
}

src/Symfony/Component/DependencyInjection/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CHANGELOG
88
* Add `%env(not:...)%` processor to negate boolean values
99
* Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8
1010
* Add autoconfigurable attributes
11+
* Add support for binding constructor arguments via attributes
1112
* Add support for per-env configuration in loaders
1213
* Add `ContainerBuilder::willBeAvailable()` to help with conditional configuration
1314

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

+21-1
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
15+
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
1416
use Symfony\Component\DependencyInjection\ChildDefinition;
1517
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\Definition;
1619

1720
/**
1821
* @author Alexander M. Turek <me@derrabus.de>
@@ -34,7 +37,7 @@ public function process(ContainerBuilder $container): void
3437

3538
$autoconfiguredAttributes = $container->getAutoconfiguredAttributes();
3639

37-
foreach ($container->getDefinitions() as $id => $definition) {
40+
foreach ($container->getDefinitions() as $definition) {
3841
if (!$definition->isAutoconfigured()
3942
|| $definition->isAbstract()
4043
|| $definition->hasTag($this->ignoreAttributesTag)
@@ -50,8 +53,25 @@ public function process(ContainerBuilder $container): void
5053
$configurator($conditionals, $attribute->newInstance(), $reflector);
5154
}
5255
}
56+
57+
if ($constructor = $reflector->getConstructor()) {
58+
$this->bindArguments($definition, $constructor);
59+
}
60+
5361
$instanceof[$reflector->getName()] = $conditionals;
5462
$definition->setInstanceofConditionals($instanceof);
5563
}
5664
}
65+
66+
private function bindArguments(Definition $definition, \ReflectionMethod $constructor): void
67+
{
68+
$bindings = $definition->getBindings();
69+
foreach ($constructor->getParameters() as $reflectionParameter) {
70+
foreach ($reflectionParameter->getAttributes(ArgumentInterface::class, \ReflectionAttribute::IS_INSTANCEOF) as $argumentAttribute) {
71+
$bindings['$'.$reflectionParameter->getName()] = new BoundArgument($argumentAttribute->newInstance());
72+
}
73+
}
74+
75+
$definition->setBindings($bindings);
76+
}
5777
}

src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1616
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1717
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
18+
use Symfony\Component\DependencyInjection\Argument\TaggedLocatorArgument;
1819
use Symfony\Component\DependencyInjection\ContainerBuilder;
1920
use Symfony\Component\DependencyInjection\Definition;
2021
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@@ -183,7 +184,7 @@ function tagged_iterator(string $tag, string $indexAttribute = null, string $def
183184
*/
184185
function tagged_locator(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null): ServiceLocatorArgument
185186
{
186-
return new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, true));
187+
return new TaggedLocatorArgument($tag, $indexAttribute, $defaultIndexMethod, true);
187188
}
188189

189190
/**

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

+57
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
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;
3133
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService1;
3234
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2;
3335
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3;
@@ -317,6 +319,33 @@ public function testTaggedServiceWithIndexAttributeAndDefaultMethod()
317319
$this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $param);
318320
}
319321

322+
/**
323+
* @requires PHP 8
324+
*/
325+
public function testTaggedServiceWithIndexAttributeAndDefaultMethodConfiguredViaAttribute()
326+
{
327+
$container = new ContainerBuilder();
328+
$container->register(BarTagClass::class)
329+
->setPublic(true)
330+
->addTag('foo_bar')
331+
;
332+
$container->register(FooTagClass::class)
333+
->setPublic(true)
334+
->addTag('foo_bar', ['foo' => 'foo'])
335+
;
336+
$container->register(IteratorConsumer::class)
337+
->setAutoconfigured(true)
338+
->setPublic(true)
339+
;
340+
341+
$container->compile();
342+
343+
$s = $container->get(IteratorConsumer::class);
344+
345+
$param = iterator_to_array($s->getParam()->getIterator());
346+
$this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $param);
347+
}
348+
320349
public function testTaggedIteratorWithMultipleIndexAttribute()
321350
{
322351
$container = new ContainerBuilder();
@@ -343,6 +372,34 @@ public function testTaggedIteratorWithMultipleIndexAttribute()
343372
$this->assertSame(['bar' => $container->get(BarTagClass::class), 'bar_duplicate' => $container->get(BarTagClass::class), 'foo_tag_class' => $container->get(FooTagClass::class)], $param);
344373
}
345374

375+
/**
376+
* @requires PHP 8
377+
*/
378+
public function testTaggedLocatorConfiguredViaAttribute()
379+
{
380+
$container = new ContainerBuilder();
381+
$container->register(BarTagClass::class)
382+
->setPublic(true)
383+
->addTag('foo_bar')
384+
;
385+
$container->register(FooTagClass::class)
386+
->setPublic(true)
387+
->addTag('foo_bar', ['foo' => 'foo'])
388+
;
389+
$container->register(LocatorConsumer::class)
390+
->setAutoconfigured(true)
391+
->setPublic(true)
392+
;
393+
394+
$container->compile();
395+
396+
$s = $container->get(LocatorConsumer::class);
397+
398+
$locator = $s->getLocator();
399+
self::assertSame($container->get(BarTagClass::class), $locator->get('bar_tab_class_with_defaultmethod'));
400+
self::assertSame($container->get(FooTagClass::class), $locator->get('foo'));
401+
}
402+
346403
public function testTaggedServiceWithDefaultPriorityMethod()
347404
{
348405
$container = new ContainerBuilder();
< 10000 /tr>
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 <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\Tests\Fixtures;
13+
14+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
15+
16+
final class IteratorConsumer
17+
{
18+
public function __construct(
19+
#[TaggedIteratorArgument('foo_bar', indexAttribute: 'foo', defaultIndexMethod: 'getFooBar')]
20+
private iterable $param,
21+
) {
22+
}
23+
24+
public function getParam(): iterable
25+
{
26+
return $this->param;
27+
}
28+
}
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 <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\Tests\Fixtures;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\DependencyInjection\Argument\TaggedLocatorArgument;
16+
17+
final class LocatorConsumer
18+
{
19+
public function __construct(
20+
#[TaggedLocatorArgument('foo_bar', indexAttribute: 'foo', defaultIndexMethod: 'getFooBar')]
21+
private ContainerInterface $locator,
22+
) {
23+
}
24+
25+
public function getLocator(): ContainerInterface
26+
{
27+
return $this->locator;
28+
}
29+
}

0 commit comments

Comments
 (0)
0