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

Skip to content

Commit d5d49c2

Browse files
committed
[DependencyInjection] Configure service tags via attributes.
1 parent d093475 commit d5d49c2

31 files changed

+581
-22
lines changed
Lines changed: 47 additions & 0 deletions
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 <[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\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

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

Lines changed: 54 additions & 0 deletions
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,10 @@
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;
31+
use Symfony\Contracts\Service\Attribute\TagInterface;
2732
use Symfony\Contracts\Service\ServiceProviderInterface;
2833
use Symfony\Contracts\Service\ServiceSubscriberInterface;
2934

@@ -506,6 +511,45 @@ 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+
if (!\interface_exists(TagInterface::class)) {
521+
self::markTestSkipped('This test requires symfony/service-contracts 2.4 or newer.');
522+
}
523+
524+
$container = new ContainerBuilder();
525+
$container->register('one', TaggedService1::class)
526+
->setPublic(true)
527+
->setAutoconfigured(true);
528+
$container->register('two', TaggedService2::class)
529+
->setPublic(true)
530+
->setAutoconfigured(true);
531+
$container->register('three', TaggedService3::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+
['foo' => 'bar', 'priority' => 0],
543+
['bar' => 'baz', 'priority' => 0],
544+
],
545+
'two' => [
546+
['someAttribute' => 'prio 100', 'priority' => 100],
547+
],
548+
'three' => [
549+
['someAttribute' => 'custom_tag_class'],
550+
],
551+
], $collector->collectedTags);
552+
}
509553
}
510554

511555
class ServiceSubscriberStub implements ServiceSubscriberInterface
@@ -566,3 +610,13 @@ public function setSunshine($type)
566610
{
567611
}
568612
}
613+
614+
final class TagCollector implements CompilerPassInterface
615+
{
616+
public $collectedTags;
617+
618+
public function process(ContainerBuilder $container): void
619+
{
620+
$this->collectedTags = $container->findTaggedServiceIds('app.custom_tag');
621+
}
622+
}
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\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+
}
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\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+
}
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\Contracts\Service\Attribute\Tag;
15+
16+
#[Tag(name: 'app.custom_tag', priority: 100, attributes: ['someAttribute' => 'prio 100'])]
17+
final class TaggedService2
18+
{
19+
}
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\CustomTag;
15+
16+
#[CustomTag]
17+
final class TaggedService3
18+
{
19+
}
Lines changed: 44 additions & 0 deletions
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 <[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+
use Symfony\Contracts\Service\Attribute\TagInterface;
15+
16+
/**
17+
* Service tag to autoconfigure event listeners.
18+
*
19+
* @author Alexander M. Turek <[email protected]>
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

Lines changed: 5 additions & 0 deletions
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

src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
16+
use Symfony\Component\DependencyInjection\Compiler\AttributeAutoconfigurationPass;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
1718
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1819
use Symfony\Component\DependencyInjection\Reference;
1920
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
2021
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
2122
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
23+
use Symfony\Component\EventDispatcher\Tests\Fixtures\CustomEvent;
24+
use Symfony\Component\EventDispatcher\Tests\Fixtures\TaggedInvokableListener;
25+
use Symfony\Component\EventDispatcher\Tests\Fixtures\TaggedMultiListener;
26+
use Symfony\Contracts\Service\Attribute\TagInterface;
2227

2328
class RegisterListenersPassTest extends TestCase
2429
{
@@ -231,6 +236,82 @@ public function testInvokableEventListener()
231236
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
232237
}
233238

239+
/**
240+
* @requires PHP 8
241+
*/
242+
public function testTaggedInvokableEventListener()
243+
{
244+
if (!\interface_exists(TagInterface::class)) {
245+
self::markTestSkipped('This test requires symfony/service-contracts 2.4 or newer.');
246+
}
247+
248+
$container = new ContainerBuilder();
249+
$container->register('foo', TaggedInvokableListener::class)->setAutoconfigured(true);
250+
$container->register('event_dispatcher', \stdClass::class);
251+
252+
(new AttributeAutoconfigurationPass())->process($container);
253+
(new RegisterListenersPass())->process($container);
254+
255+
$definition = $container->getDefinition('event_dispatcher');
256+
$expectedCalls = [
257+
[
258+
'addListener',
259+
[
260+
CustomEvent::class,
261+
[new ServiceClosureArgument(new Reference('foo')), '__invoke'],
262+
0,
263+
],
264+
],
265+
];
266+
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
267+
}
268+
269+
/**
270+
* @requires PHP 8
271+
*/
272+
public function testTaggedMultiEventListener()
273+
{
274+
if (!\interface_exists(TagInterface::class)) {
275+
self::markTestSkipped('This test requires symfony/service-contracts 2.4 or newer.');
276+
}
277+
278+
$container = new ContainerBuilder();
279+
$container->register('foo', TaggedMultiListener::class)->setAutoconfigured(true);
280+
$container->register('event_dispatcher', \stdClass::class);
281+
282+
(new AttributeAutoconfigurationPass())->process($container);
283+
(new RegisterListenersPass())->process($container);
284+
285+
$definition = $container->getDefinition('event_dispatcher');
286+
$expectedCalls = [
287+
[
288+
'addListener',
289+
[
290+
CustomEvent::class,
291+
[new ServiceClosureArgument(new Reference('foo')), 'onCustomEvent'],
292+
0,
293+
],
294+
],
295+
[
296+
'addListener',
297+
[
298+
'foo',
299+
[new ServiceClosureArgument(new Reference('foo')), 'onFoo'],
300+
42,
301+
],
302+
],
303+
[
304+
'addListener',
305+
[
306+
'bar',
307+
[new ServiceClosureArgument(new Reference('foo')), 'onBarEvent'],
308+
0,
309+
],
310+
],
311+
];
312+
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
313+
}
314+
234315
public function testAliasedEventListener()
235316
{
236317
$container = new ContainerBuilder();
@@ -416,10 +497,6 @@ final class AliasedEvent
416497
{
417498
}
418499

419-
final class CustomEvent
420-
{
421-
}
422-
423500
final class TypedListener
424501
{
425502
public function __invoke(AliasedEvent $event): void

0 commit comments

Comments
 (0)