Skip to content

Commit 02c1ca3

Browse files
committed
[DependencyInjection] Configure service tags via attributes.
1 parent 76f02fd commit 02c1ca3

32 files changed

+571
-22
lines changed

.github/patch-types.php

+4
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@
2828
case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ParentNotExists.php'):
2929
case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Preload/'):
3030
case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/BadClasses/MissingParent.php'):
31+
case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TaggedService'):
3132
case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/WitherStaticReturnType.php'):
3233
case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/'):
3334
case false !== strpos($file, '/src/Symfony/Component/ErrorHandler/Tests/Fixtures/'):
35+
case false !== strpos($file, '/src/Symfony/Component/EventDispatcher/Attribute/EventListener.php'):
36+
case false !== strpos($file, '/src/Symfony/Component/HttpKernel/Attribute/Reset.php'):
3437
case false !== strpos($file, '/src/Symfony/Component/HttpKernel/Tests/Fixtures/Controller/AttributeController.php'):
3538
case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php'):
3639
case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php'):
@@ -44,6 +47,7 @@
4447
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/NotLoadableClass.php'):
4548
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/Php74.php') && \PHP_VERSION_ID < 70400:
4649
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/RepeatableAttribute.php'):
50+
case false !== strpos($file, '/src/Symfony/Contracts/Service/Attribute/Tag.php'):
4751
continue 2;
4852
}
4953

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
use Symfony\Contracts\Service\Attribute\TagInterface;
16+
17+
final class AttributeAutoconfigurationPass implements CompilerPassInterface
18+
{
19+
public function process(ContainerBuilder $container): void
20+
{
21+
if (80000 > \PHP_VERSION_ID || !interface_exists(TagInterface::class)) {
22+
return;
23+
}
24+
25+
foreach ($container->getDefinitions() as $definition) {
26+
if (!$definition->isAutoconfigured()) {
27+
continue;
28+
}
29+
30+
if (!$class = $container->getParameterBag()->resolveValue($definition->getClass())) {
31+
continue;
32+
}
33+
34+
try {
35+
$reflector = new \ReflectionClass($class);
36+
} catch (\ReflectionException $e) {
37+
continue;
38+
}
39+
40+
foreach ($reflector->getAttributes(TagInterface::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
41+
/** @var TagInterface $tag */
42+
$tag = $attribute->newInstance();
43+
$definition->addTag($tag->getName(), $tag->getAttributes());
44+
}
45+
}
46+
}
47+
}

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

+1
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/Tests/Compiler/IntegrationTest.php

+49
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
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\Compiler\CompilerPassInterface;
1920
use Symfony\Component\DependencyInjection\ContainerBuilder;
2021
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
2122
use Symfony\Component\DependencyInjection\Reference;
@@ -24,6 +25,9 @@
2425
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
2526
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedForDefaultPriorityClass;
2627
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooTagClass;
28+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService1;
29+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2;
30+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3;
2731
use Symfony\Contracts\Service\ServiceProviderInterface;
2832
use Symfony\Contracts\Service\ServiceSubscriberInterface;
2933

@@ -506,6 +510,41 @@ public function testTaggedServiceLocatorWithDefaultIndex()
506510
];
507511
$this->assertSame($expected, ['baz' => $serviceLocator->get('baz')]);
508512
}
513+
514+
/**
515+
* @requires PHP 8
516+
*/
517+
public function testTagsViaAttribute()
518+
{
519+
$container = new ContainerBuilder();
520+
$container->register('one', TaggedService1::class)
521+
->setPublic(true)
522+
->setAutoconfigured(true);
523+
$container->register('two', TaggedService2::class)
524+
->setPublic(true)
525+
->setAutoconfigured(true);
526+
$container->register('three', TaggedService3::class)
527+
->setPublic(true)
528+
->setAutoconfigured(true);
529+
530+
$collector = new TagCollector();
531+
$container->addCompilerPass($collector);
532+
533+
$container->compile();
534+
535+
self::assertSame([
536+
'one' => [
537+
['foo' => 'bar', 'priority' => 0],
538+
['bar' => 'baz', 'priority' => 0],
539+
],
540+
'two' => [
541+
['someAttribute' => 'prio 100', 'priority' => 100],
542+
],
543+
'three' => [
544+
['someAttribute' => 'custom_tag_class'],
545+
],
546+
], $collector->collectedTags);
547+
}
509548
}
510549

511550
class ServiceSubscriberStub implements ServiceSubscriberInterface
@@ -566,3 +605,13 @@ public function setSunshine($type)
566605
{
567606
}
568607
}
608+
609+
final class TagCollector implements CompilerPassInterface
610+
{
611+
public $collectedTags;
612+
613+
public function process(ContainerBuilder $container): void
614+
{
615+
$this->collectedTags = $container->findTaggedServiceIds('app.custom_tag');
616+
}
617+
}
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\Attribute;
13+
14+
use Symfony\Contracts\Service\Attribute\TagInterface;
15+
16+
#[\Attribute(\Attribute::TARGET_CLASS)]
17+
final class CustomTag implements TagInterface
18+
{
19+
public function getName(): string
20+
{
21+
return 'app.custom_tag';
22+
}
23+
24+
public function getAttributes(): array
25+
{
26+
return ['someAttribute' => 'custom_tag_class'];
27+
}
28+
}
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\Contracts\Service\Attribute\Tag;
15+
16+
#[Tag(name: 'app.custom_tag', attributes: ['foo' => 'bar'])]
17+
#[Tag(name: 'app.custom_tag', attributes: ['bar' => 'baz'])]
18+
final class TaggedService1
19+
{
20+
}
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\Contracts\Service\Attribute\Tag;
15+
16+
#[Tag(name: 'app.custom_tag', priority: 100, attributes: ['someAttribute' => 'prio 100'])]
17+
final class TaggedService2
18+
{
19+
}
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\CustomTag;
15+
16+
#[CustomTag]
17+
final class TaggedService3
18+
{
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
use Symfony\Contracts\Service\Attribute\TagInterface;
15+
16+
/**
17+
* Service tag to autoconfigure event listeners.
18+
*
19+
* @author Alexander M. Turek <me@derrabus.de>
20+
*/
21+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
22+
class EventListener implements TagInterface
23+
{
24+
public function __construct(
25+
private ?string $event = null,
26+
private ?string $method = null,
27+
private int $priority = 0
28+
) {
29+
}
30+
31+
public function getName(): string
32+
{
33+
return 'kernel.event_listener';
34+
}
35+
36+
public function getAttributes(): array
37+
{
38+
return [
39+
'event' => $this->event,
40+
'method' => $this->method,
41+
'priority' => $this->priority,
42+
];
43+
}
44+
}

src/Symfony/Component/EventDispatcher/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.3.0
5+
-----
6+
7+
* Added the `EventListener` service tag attribute for PHP 8.
8+
49
5.1.0
510
-----
611

0 commit comments

Comments
 (0)