Skip to content

Commit 5d7b14f

Browse files
Fan2Shrekfabpot
authored andcommitted
Add support for union types on AsEventListener
1 parent e840fae commit 5d7b14f

File tree

5 files changed

+129
-5
lines changed

5 files changed

+129
-5
lines changed

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ CHANGELOG
1414
* Add support for configuring workflow places with glob patterns matching consts/backed enums
1515
* Add support for configuring the `CachingHttpClient`
1616
* Add support for weighted transitions in workflows
17+
* Add support for union types with `Symfony\Component\EventDispatcher\Attribute\AsEventListener`
1718

1819
7.3
1920
---

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

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -774,13 +774,32 @@ public function load(array $configs, ContainerBuilder $container): void
774774

775775
$container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) {
776776
$tagAttributes = get_object_vars($attribute);
777-
if ($reflector instanceof \ReflectionMethod) {
778-
if (isset($tagAttributes['method'])) {
779-
throw new LogicException(\sprintf('AsEventListener attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name));
777+
778+
if (!$reflector instanceof \ReflectionMethod) {
779+
$definition->addTag('kernel.event_listener', $tagAttributes);
780+
781+
return;
782+
}
783+
784+
if (isset($tagAttributes['method'])) {
785+
throw new LogicException(\sprintf('AsEventListener attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name));
786+
}
787+
788+
$tagAttributes['method'] = $reflector->getName();
789+
790+
if (!$eventArg = $reflector->getParameters()[0] ?? null) {
791+
throw new LogicException(\sprintf('AsEventListener attribute requires the first argument of "%s::%s()" to be an event object.', $reflector->class, $reflector->name));
792+
}
793+
794+
$types = ($type = $eventArg->getType() instanceof \ReflectionUnionType ? $eventArg->getType()->getTypes() : [$eventArg->getType()]) ?: [];
795+
796+
foreach ($types as $type) {
797+
if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) {
798+
$tagAttributes['event'] = $type->getName();
799+
800+
$definition->addTag('kernel.event_listener', $tagAttributes);
780801
}
781-
$tagAttributes['method'] = $reflector->getName();
782802
}
783-
$definition->addTag('kernel.event_listener', $tagAttributes);
784803
});
785804
$container->registerAttributeForAutoconfiguration(AsController::class, static function (ChildDefinition $definition, AsController $attribute): void {
786805
$definition->addTag('controller.service_arguments');

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
2525
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
2626
use Symfony\Component\EventDispatcher\Tests\Fixtures\CustomEvent;
27+
use Symfony\Component\EventDispatcher\Tests\Fixtures\DummyEvent;
2728
use Symfony\Component\EventDispatcher\Tests\Fixtures\TaggedInvokableListener;
2829
use Symfony\Component\EventDispatcher\Tests\Fixtures\TaggedMultiListener;
30+
use Symfony\Component\EventDispatcher\Tests\Fixtures\TaggedUnionTypeListener;
2931

3032
class RegisterListenersPassTest extends TestCase
3133
{
@@ -356,6 +358,70 @@ static function (ChildDefinition $definition, AsEventListener $attribute, \Refle
356358
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
357359
}
358360

361+
public function testTaggedMethodUnionTypeEventListener()
362+
{
363+
$container = new ContainerBuilder();
364+
$container->registerAttributeForAutoconfiguration(AsEventListener::class,
365+
static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) {
366+
$tagAttributes = get_object_vars($attribute);
367+
368+
if (!$reflector instanceof \ReflectionMethod) {
369+
$definition->addTag('kernel.event_listener', $tagAttributes);
370+
371+
return;
372+
}
373+
374+
if (isset($tagAttributes['method'])) {
375+
throw new \LogicException(\sprintf('AsEventListener attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name));
376+
}
377+
378+
$tagAttributes['method'] = $reflector->getName();
379+
380+
if (!$eventArg = $reflector->getParameters()[0] ?? null) {
381+
throw new \LogicException(\sprintf('AsEventListener attribute requires the first argument of "%s::%s()" to be an event object.', $reflector->class, $reflector->name));
382+
}
383+
384+
$types = ($type = $eventArg->getType() instanceof \ReflectionUnionType ? $eventArg->getType()->getTypes() : [$eventArg->getType()]) ?: [];
385+
386+
foreach ($types as $type) {
387+
if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) {
388+
$tagAttributes['event'] = $type->getName();
389+
390+
$definition->addTag('kernel.event_listener', $tagAttributes);
391+
}
392+
}
393+
});
394+
395+
$container->register('foo', TaggedUnionTypeListener::class)->setAutoconfigured(true);
396+
$container->register('event_dispatcher', \stdClass::class);
397+
398+
(new AttributeAutoconfigurationPass())->process($container);
399+
(new ResolveInstanceofConditionalsPass())->process($container);
400+
(new RegisterListenersPass())->process($container);
401+
402+
$definition = $container->getDefinition('event_dispatcher');
403+
$expectedCalls = [
404+
[
405+
'addListener',
406+
[
407+
CustomEvent::class,
408+
[new ServiceClosureArgument(new Reference('foo')), 'onUnionEvent'],
409+
0,
410+
],
411+
],
412+
[
413+
'addListener',
414+
[
415+
DummyEvent::class,
416+
[new ServiceClosureArgument(new Reference('foo')), 'onUnionEvent'],
417+
0,
418+
],
419+
],
420+
];
421+
422+
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
423+
}
424+
359425
public function testAliasedEventListener()
360426
{
361427
$container = new ContainerBuilder();
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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\Tests\Fixtures;
13+
14+
final class DummyEvent
15+
{
16+
}
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\EventDispatcher\Tests\Fixtures;
13+
14+
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
15+
16+
final class TaggedUnionTypeListener
17+
{
18+
#[AsEventListener]
19+
public function onUnionEvent(CustomEvent|DummyEvent $event): void
20+
{
21+
}
22+
}

0 commit comments

Comments
 (0)