8000 [DependencyInjection] Autoconfigurable attributes · symfony/symfony@df793fe · GitHub
[go: up one dir, main page]

Skip to content

Commit df793fe

Browse files
committed
[DependencyInjection] Autoconfigurable attributes
1 parent b8e76de commit df793fe

File tree

25 files changed

+535
-11
lines changed

25 files changed

+535
-11
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
use Symfony\Component\DependencyInjection\Parameter;
5757
use Symfony\Component\DependencyInjection\Reference;
5858
use Symfony\Component\DependencyInjection\ServiceLocator;
59+
use Symfony\Component\EventDispatcher\Attribute\EventListener;
60+
use Symfony\Component\EventDispatcher\DependencyInjection\EventListenerAutoconfigurator;
5961
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
6062
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
6163
use Symfony\Component\Finder\Finder;
@@ -68,11 +70,13 @@
6870
use Symfony\Component\HttpClient\RetryableHttpClient;
6971
use Symfony\Component\HttpClient\ScopingHttpClient;
7072
use Symfony\Component\HttpFoundation\Request;
73+
use Symfony\Component\HttpKernel\Attribute\Reset;
7174
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
7275
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
7376
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
7477
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
7578
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
79+
use Symfony\Component\HttpKernel\DependencyInjection\ResetAutoconfigurator;
7680
use Symfony\Component\Lock\Lock;
7781
use Symfony\Component\Lock\LockFactory;
7882
use Symfony\Component\Lock\LockInterface;
@@ -529,6 +533,9 @@ public function load(array $configs, ContainerBuilder $container)
529533
$container->registerForAutoconfiguration(LoggerAwareInterface::class)
530534
->addMethodCall('setLogger', [new Reference('logger')]);
531535

536+
$container->registerAttributeForAutoconfiguration(EventListener::class, new EventListenerAutoconfigurator());
537+
$container->registerAttributeForAutoconfiguration(Reset::class, new ResetAutoconfigurator());
538+
532539
if (!$container->getParameter('kernel.debug')) {
533540
// remove tagged iterator argument for resource checkers
534541
$container->getDefinition('config_cache_factory')->setArguments([]);

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@
2020
"ext-xml": "*",
2121
"symfony/cache": "^5.2",
2222
"symfony/config": "^5.0",
23-
"symfony/dependency-injection": "^5.2",
23+
"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.2.1",
28-
"symfony/http-kernel": "^5.2.1",
28+
"symfony/http-kernel": "^5.3",
2929
"symfony/polyfill-mbstring": "~1.0",
3030
"symfony/polyfill-php80": "^1.15",
3131
"symfony/filesystem": "^4.4|^5.0",
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\ContainerBuilder;
15+
16+
/**
17+
* @author Alexander M. Turek <me@derrabus.de>
18+
*/
19+
final class AttributeAutoconfigurationPass implements CompilerPassInterface
20+
{
21+
public function process(ContainerBuilder $container): void
22+
{
23+
if (80000 > \PHP_VERSION_ID) {
24+
return;
25+
}
26+
27+
$autoconfiguredAttributes = $container->getAutoconfiguredAttributes();
28+
29+
foreach ($container->getDefinitions() as $definition) {
30+
if (!$definition->isAutoconfigured()) {
31+
continue;
32+
}
33+
34+
if (!$class = $container->getParameterBag()->resolveValue($definition->getClass())) {
35+
continue;
36+
}
37+
38+
try {
39+
$reflector = new \ReflectionClass($class);
40+
} catch (\ReflectionException $e) {
41+
continue;
42+
}
43+
44+
foreach ($reflector->getAttributes() as $attribute) {
45+
if (!($configurator = $autoconfiguredAttributes[$attribute->getName()] ?? null)) {
46+
continue;
47+
}
48+
49+
$configurator($definition, $attribute->newInstance());
50+
}
51+
}
52+
}
53+
}

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: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,19 @@
1616
use Symfony\Component\DependencyInjection\Alias;
1717
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1818
use Symfony\Component\DependencyInjec 10000 tion\Argument\TaggedIteratorArgument;
19+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1920
use Symfony\Component\DependencyInjection\ContainerBuilder;
21+
use Symfony\Component\DependencyInjection\Definition;
2022
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
2123
use Symfony\Component\DependencyInjection\Reference;
2224
use Symfony\Component\DependencyInjection\ServiceLocator;
25+
use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomAutoconfiguration;
2326
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass;
2427
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
2528
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedForDefaultPriorityClass;
2629
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooTagClass;
30+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService1;
31+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2;
2732
use Symfony\Contracts\Service\ServiceProviderInterface;
2833
use Symfony\Contracts\Service\ServiceSubscriberInterface;
2934

@@ -506,6 +511,42 @@ public function testTaggedServiceLocatorWithDefaultIndex()
506511
];
507512
$this->assertSame($expected, ['baz' => $serviceLocator->get('baz')]);
508513
}
514+
515+
/**
516+
* @requires PHP 8
517+
*/
518+
public function testTagsViaAttribute()
519+
{
520+
$container = new ContainerBuilder();
521+
$container->registerAttributeForAutoconfiguration(
522+
CustomAutoconfiguration::class,
523+
static function (Definition $definition, CustomAutoconfiguration $attribute) {
524+
$definition->addTag('app.custom_tag', get_object_vars($attribute));
525+
}
526+
);
527+
528+
$container->register('one', TaggedService1::class)
529+
->setPublic(true)
530+
->setAutoconfigured(true);
531+
$container->register('two', TaggedService2::class)
532+
->setPublic(true)
533+
->setAutoconfigured(true);
534+
535+
$collector = new TagCollector();
536+
$container->addCompilerPass($collector);
537+
538+
$container->compile();
539+
540+
self::assertSame([
541+
'one' => [
542+
['someAttribute' => 'one', 'priority' => 0],
543+
['someAttribute' => 'two', 'priority' => 0],
544+
],
545+
'two' => [
546+
['someAttribute' => 'prio 100', 'priority' => 100],
547+
],
548+
], $collector->collectedTags);
549+
}
509550
}
510551

511552
class ServiceSubscriberStub implements ServiceSubscriberInterface
@@ -566,3 +607,13 @@ public function setSunshine($type)
566607
{
567608
}
568609
}
610+
611+
final class TagCollector implements CompilerPassInterface
612+
{
613+
public $collectedTags;
614+
615+
public function process(ContainerBuilder $container): void
616+
{
617+
$this->collectedTags = $container->findTaggedServiceIds('app.custom_tag');
618+
}
619+
}
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 <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\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 <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\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 <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\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 <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\EventDispatcher\Attribute;
13+
14+
/**
15+
* Service tag to autoconfigure event listeners.
16+
*
17+
* @author Alexander M. Turek <me@derrabus.de>
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)
0