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

Skip to content

Commit ba62349

Browse files
committed
[DependencyInjection] Autoconfigurable attributes
1 parent 9323f41 commit ba62349

File tree

17 files changed

+468
-7
lines changed

17 files changed

+468
-7
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
use Symfony\Component\DependencyInjection\Parameter;
5959
use Symfony\Component\DependencyInjection\Reference;
6060
use Symfony\Component\DependencyInjection\ServiceLocator;
61+
use Symfony\Component\EventDispatcher\Attribute\EventListener;
62+
use Symfony\Component\EventDispatcher\DependencyInjection\EventListenerAutoconfigurator;
6163
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
6264
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
6365
use Symfony\Component\Finder\Finder;
@@ -546,6 +548,8 @@ public function load(array $configs, ContainerBuilder $container)
546548
$container->registerForAutoconfiguration(LoggerAwareInterface::class)
547549
->addMethodCall('setLogger', [new Reference('logger')]);
548550

551+
$container->registerAttributeForAutoconfiguration(EventListener::class, new EventListenerAutoconfigurator());
552+
549553
if (!$container->getParameter('kernel.debug')) {
550554
// remove tagged iterator argument for resource checkers
551555
$container->getDefinition('config_cache_factory')->setArguments([]);

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"symfony/config": "^5.0",
2323
"symfony/dependency-injection": "^5.3",
2424
"symfony/deprecation-contracts": "^2.1",
25-
"symfony/event-dispatcher": "^5.1",
25+
"symfony/event-dispatcher": "^5.3",
2626
"symfony/error-handler": "^4.4.1|^5.0.1",
2727
"symfony/http-foundation": "^5.3",
2828
"symfony/http-kernel": "^5.2.1",
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\ChildDefinition;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Definition;
17+
18+
/**
19+
* @author Alexander M. Turek <[email protected]>
20+
*/
21+
final class AttributeAutoconfigurationPass implements CompilerPassInterface
22+
{
23+
private $ignoreAttributesTag;
24+
25+
public function __construct(string $ignoreAttributesTag = 'container.ignore_attributes')
26+
{
27+
$this->ignoreAttributesTag = $ignoreAttributesTag;
28+
}
29+
30+
public function process(ContainerBuilder $container): void
31+
{
32+
if (80000 > \PHP_VERSION_ID) {
33+
return;
34+
}
35+
36+
$autoconfiguredAttributes = $container->getAutoconfiguredAttributes();
37+
38+
$definitions = array_filter($container->getDefinitions(), function (Definition $definition) {
39+
return $definition->isAutoconfigured()
40+
&& !$definition->isAbstract()
41+
&& !$definition->hasTag($this->ignoreAttributesTag)
42+
&& $definition->getClass();
43+
});
44+
45+
foreach ($definitions as $id => $definition) {
46+
if (!$reflector = $container->getReflectionClass($definition->getClass(), false)) {
47+
continue;
48+
}
49+
50+
foreach ($reflector->getAttributes() as $attribute) {
51+
if (!($configurator = $autoconfiguredAttributes[$attribute->getName()] ?? null)) {
52+
continue;
53+
}
54+
55+
$configurator($childDefinition = new ChildDefinition($id), $attribute->newInstance(), $reflector);
56+
$this->merge($definition, $childDefinition);
57+
}
58+
}
59+
}
60+
61+
private function merge(Definition $definition, ChildDefinition $child): void
62+
{
63+
foreach ($child->getTags() as $tag => $attributes) {
64+
foreach ($attributes as $tagAttributes) {
65+
$definition->addTag($tag, $tagAttributes);
66+
}
67+
}
68+
}
69+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public function __construct()
4242
$this->beforeOptimizationPasses = [
4343
100 => [
4444
new ResolveClassPass(),
45+
new AttributeAutoconfigurationPass(),
4546
new ResolveInstanceofConditionalsPass(),
4647
new RegisterEnvVarProcessorsPass(),
4748
],

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
123123

124124
private $autoconfiguredInstanceof = [];
125125

126+
/**
127+
* @var callable[]
128+
*/
129+
private $autoconfiguredAttributes = [];
130+
126131
private $removedIds = [];
127132

128133
private $removedBindingIds = [];
@@ -671,6 +676,14 @@ public function merge(self $container)
671676

672677
$this->autoconfiguredInstanceof[$interface] = $childDefinition;
673678
}
679+
680+
foreach ($container->getAutoconfiguredAttributes() as $attribute => $configurator) {
681+
if (isset($this->autoconfiguredAttributes[$attribute])) {
682+
throw new InvalidArgumentException(sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same attribute.', $attribute));
683+
}
684+
685+
$this->autoconfiguredAttributes[$attribute] = $configurator;
686+
}
674687
}
675688

676689
/**
@@ -1309,6 +1322,16 @@ public function registerForAutoconfiguration(string $interface)
13091322
return $this->autoconfiguredInstanceof[$interface];
13101323
}
13111324

1325+
/**
1326+
* Registers an attribute that will be used for autoconfiguring annotated classes.
1327+
*
1328+
* The configurator will receive a Definition instance and an instance of the attribute, in that order.
1329+
*/
1330+
public function registerAttributeForAutoconfiguration(string $attributeClass, callable $configurator): void
1331+
{
1332+
$this->autoconfiguredAttributes[$attributeClass] = $configurator;
1333+
}
1334+
13121335
/**
13131336
* Registers an autowiring alias that only binds to a specific argument name.
13141337
*
@@ -1338,6 +1361,14 @@ public function getAutoconfiguredInstanceof()
13381361
return $this->autoconfiguredInstanceof;
13391362
}
13401363

1364+
/**
1365+
* @return callable[]
1366+
*/
1367+
public function getAutoconfiguredAttributes(): array
1368+
{
1369+
return $this->autoconfiguredAttributes;
1370+
}
1371+
13411372
/**
13421373
* Resolves env parameter placeholders in a string or an array.
13431374
*

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

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,20 @@
1616
use Symfony\Component\DependencyInjection\Alias;
1717
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1818
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
19+
use Symfony\Component\DependencyInjection\ChildDefinition;
20+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1921
use Symfony\Component\DependencyInjection\ContainerBuilder;
22+
use Symfony\Component\DependencyInjection\Definition;
2023
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
2124
use Symfony\Component\DependencyInjection\Reference;
2225
use Symfony\Component\DependencyInjection\ServiceLocator;
26+
use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration;
2327
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass;
2428
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
2529
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedForDefaultPriorityClass;
2630
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooTagClass;
31+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService1;
32+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2;
2733
use Symfony\Contracts\Service\ServiceProviderInterface;
2834
use Symfony\Contracts\Service\ServiceSubscriberInterface;
2935

@@ -506,6 +512,77 @@ public function testTaggedServiceLocatorWithDefaultIndex()
506512
];
507513
$this->assertSame($expected, ['baz' => $serviceLocator->get('baz')]);
508514
}
515+
516+
/**
517+
* @requires PHP 8
518+
*/
519+
public function testTagsViaAttribute()
520+
{
521+
$container = new ContainerBuilder();
522+
$container->registerAttributeForAutoconfiguration(
523+
CustomAutoconfiguration::class,
524+
static function (ChildDefinition $definition, CustomAutoconfiguration $attribute, \ReflectionClass $reflector) {
525+
$definition->addTag('app.custom_tag', get_object_vars($attribute) + ['class' => $reflector->getName()]);
526+
}
527+
);
528+
529+
$container->register('one', TaggedService1::class)
530+
->setPublic(true)
531+
->setAutoconfigured(true);
532+
$container->register('two', TaggedService2::class)
533+
->addTag('app.custom_tag', ['info' => 'This tag is not autoconfigured'])
534+
->setPublic(true)
535+
->setAutoconfigured(true);
536+
537+
$collector = new TagCollector();
538+
$container->addCompilerPass($collector);
539+
540+
$container->compile();
541+
542+
self::assertSame([
543+
'one' => [
544+
['someAttribute' => 'one', 'priority' => 0, 'class' => TaggedService1::class],
545+
['someAttribute' => 'two', 'priority' => 0, 'class' => TaggedService1::class],
546+
],
547+
'two' => [
548+
['info' => 'This tag is not autoconfigured'],
549+
['someAttribute' => 'prio 100', 'priority' => 100, 'class' => TaggedService2::class],
550+
],
551+
], $collector->collectedTags);
552+
}
553+
554+
/**
555+
* @requires PHP 8
556+
*/
557+
public function testAttributesAreIgnored()
558+
{
559+
$container = new ContainerBuilder();
560+
$container->registerAttributeForAutoconfiguration(
561+
CustomAutoconfiguration::class,
562+
static function (Definition $definition, CustomAutoconfiguration $attribute) {
563+
$definition->addTag('app.custom_tag', get_object_vars($attribute));
564+
}
565+
);
566+
567+
$container->register('one', TaggedService1::class)
568+
->setPublic(true)
569+
->addTag('container.ignore_attributes')
570+
->setAutoconfigured(true);
571+
$container->register('two', TaggedService2::class)
572+
->setPublic(true)
573+
->setAutoconfigured(true);
574+
575+
$collector = new TagCollector();
576+
$container->addCompilerPass($collector);
577+
578+
$container->compile();
579+
580+
self::assertSame([
581+
'two' => [
582+
['someAttribute' => 'prio 100', 'priority' => 100],
583+
],
584+
], $collector->collectedTags);
585+
}
509586
}
510587

511588
class ServiceSubscriberStub implements ServiceSubscriberInterface
@@ -566,3 +643,13 @@ public function setSunshine($type)
566643
{
567644
}
568645
}
646+
647+
final class TagCollector implements CompilerPassInterface
648+
{
649+
public $collectedTags;
650+
651+
public function process(ContainerBuilder $container): void
652+
{
653+
$this->collectedTags = $container->findTaggedServiceIds('app.custom_tag');
654+
}
655+
}
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\Tests\Fixtures\Attribute;
13+
14+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
15+
final class CustomAutoconfiguration
16+
{
17+
public function __construct(
18+
public string $someAttribute,
19+
public int $priority = 0,
20+
) {
21+
}
22+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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\Tests\Fixtures\Attribute\CustomAutoconfiguration;
15+
16+
#[CustomAutoconfiguration(someAttribute: 'one')]
17+
#[CustomAutoconfiguration(someAttribute: 'two')]
18+
final class TaggedService1
19+
{
20+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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\Tests\Fixtures\Attribute\CustomAutoconfiguration;
15+
16+
#[CustomAutoconfiguration(someAttribute: 'prio 100', priority: 100)]
17+
final class TaggedService2
18+
{
19+
}
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\EventDispatcher\Attribute;
13+
14+
/**
15+
* Service tag to autoconfigure event listeners.
16+
*
17+
* @author Alexander M. Turek <[email protected]>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
20+
class EventListener
21+
{
22+
public function __construct(
23+
public ?string $event = null,
24+
public ?string $method = null,
25+
public int $priority = 0
26+
) {
27+
}
28+
}

0 commit comments

Comments
 (0)