Skip to content

Commit 9dddcdd

Browse files
committed
[EventDispatcher][FrameworkBundle] Rework union types on #[AsEventListener]
1 parent 899a660 commit 9dddcdd

File tree

3 files changed

+55
-59
lines changed

3 files changed

+55
-59
lines changed

DependencyInjection/RegisterListenersPass.php

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,27 @@ public function process(ContainerBuilder $container): void
6565
foreach ($container->findTaggedServiceIds('kernel.event_listener', true) as $id => $events) {
6666
$noPreload = 0;
6767

68+
$resolvedEvents = [];
6869
foreach ($events as $event) {
69-
$priority = $event['priority'] ?? 0;
70-
7170
if (!isset($event['event'])) {
7271
if ($container->getDefinition($id)->hasTag('kernel.event_subscriber')) {
7372
continue;
7473
}
7574

7675
$event['method'] ??= '__invoke';
77-
$event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']);
76+
$eventNames = $this->getEventFromTypeDeclaration($container, $id, $event['method']);
77+
} else {
78+
$eventNames = [$event['event']];
79+
}
80+
81+
foreach ($eventNames as $eventName) {
82+
$event['event'] = $aliases[$eventName] ?? $eventName;
83+
$resolvedEvents[] = $event;
7884
}
85+
}
7986

80-
$event['event'] = $aliases[$event['event']] ?? $event['event'];
87+
foreach ($resolvedEvents as $event) {
88+
$priority = $event['priority'] ?? 0;
8189

8290
if (!isset($event['method'])) {
8391
$event['method'] = 'on'.preg_replace_callback([
@@ -167,21 +175,40 @@ public function process(ContainerBuilder $container): void
167175
}
168176
}
169177

170-
private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string
178+
/**
179+
* @return string[]
180+
*/
181+
private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): array
171182
{
172183
if (
173184
null === ($class = $container->getDefinition($id)->getClass())
174185
|| !($r = $container->getReflectionClass($class, false))
175186
|| !$r->hasMethod($method)
176187
|| 1 > ($m = $r->getMethod($method))->getNumberOfParameters()
177-
|| !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType
178-
|| $type->isBuiltin()
179-
|| Event::class === ($name = $type->getName())
188+
|| !(($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType || $type instanceof \ReflectionUnionType)
180189
) {
181190
throw new InvalidArgumentException(\sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id));
182191
}
183192

184-
return $name;
193+
$types = $type instanceof \ReflectionUnionType ? $type->getTypes() : [$type];
194+
195+
$names = [];
196+
foreach ($types as $type) {
197+
if (!$type instanceof \ReflectionNamedType
198+
|| $type->isBuiltin()
199+
|| Event::class === ($name = $type->getName())
200+
) {
201+
continue;
202+
}
203+
204+
$names[] = $name;
205+
}
206+
207+
if (!$names) {
208+
throw new InvalidArgumentException(\sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id));
209+
}
210+
211+
return $names;
185212
}
186213
}
187214

Tests/DependencyInjection/RegisterListenersPassTest.php

Lines changed: 18 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212
namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension;
1516
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
16-
use Symfony\Component\DependencyInjection\ChildDefinition;
1717
use Symfony\Component\DependencyInjection\Compiler\AttributeAutoconfigurationPass;
1818
use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass;
1919
use Symfony\Component\DependencyInjection\ContainerBuilder;
2020
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
2121
use Symfony\Component\DependencyInjection\Reference;
22-
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
2322
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
2423
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
2524
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -275,10 +274,7 @@ public function testItThrowsAnExceptionIfTagIsMissingMethodAndClassHasNoValidMet
275274

276275
public function testTaggedInvokableEventListener()
277276
{
278-
$container = new ContainerBuilder();
279-
$container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute): void {
280-
$definition->addTag('kernel.event_listener', get_object_vars($attribute));
281-
});
277+
$container = $this->createContainerBuilder();
282278
$container->register('foo', TaggedInvokableListener::class)->setAutoconfigured(true);
283279
$container->register('event_dispatcher', \stdClass::class);
284280

@@ -297,21 +293,12 @@ public function testTaggedInvokableEventListener()
297293
],
298294
],
299295
];
300-
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
296+
$this->assertEquals($expectedCalls, \array_slice($definition->getMethodCalls(), 0, \count($expectedCalls)));
301297
}
302298

303299
public function testTaggedMultiEventListener()
304300
{
305-
$container = new ContainerBuilder();
306-
$container->registerAttributeForAutoconfiguration(AsEventListener::class,
307-
static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector): void {
308-
$tagAttributes = get_object_vars($attribute);
309-
if ($reflector instanceof \ReflectionMethod) {
310-
$tagAttributes['method'] = $reflector->getName();
311-
}
312-
$definition->addTag('kernel.event_listener', $tagAttributes);
313-
}
314-
);
301+
$container = $this->createContainerBuilder();
315302

316303
$container->register('foo', TaggedMultiListener::class)->setAutoconfigured(true);
317304
$container->register('event_dispatcher', \stdClass::class);
@@ -355,42 +342,12 @@ static function (ChildDefinition $definition, AsEventListener $attribute, \Refle
355342
],
356343
],
357344
];
358-
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
345+
$this->assertEquals($expectedCalls, \array_slice($definition->getMethodCalls(), 0, \count($expectedCalls)));
359346
}
360347

361348
public function testTaggedMethodUnionTypeEventListener()
362349
{
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-
});
350+
$container = $this->createContainerBuilder();
394351

395352
$container->register('foo', TaggedUnionTypeListener::class)->setAutoconfigured(true);
396353
$container->register('event_dispatcher', \stdClass::class);
@@ -419,7 +376,7 @@ static function (ChildDefinition $definition, AsEventListener $attribute, \Refle
419376
],
420377
];
421378

422-
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
379+
$this->assertEquals($expectedCalls, \array_slice($definition->getMethodCalls(), 0, \count($expectedCalls)));
423380
}
424381

425382
public function testAliasedEventListener()
@@ -569,6 +526,17 @@ public function testOmitEventNameOnSubscriber()
569526
];
570527
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
571528
}
529+
530+
private function createContainerBuilder(): ContainerBuilder
531+
{
532+
$container = new ContainerBuilder();
533+
$container->setParameter('kernel.debug', true);
534+
$container->setParameter('kernel.project_dir', __DIR__);
535+
$container->setParameter('kernel.container_class', 'testContainer');
536+
(new FrameworkExtension())->load([], $container);
537+
538+
return $container;
539+
}
572540
}
573541

574542
class SubscriberService implements EventSubscriberInterface

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"symfony/expression-language": "^6.4|^7.0|^8.0",
2525
"symfony/config": "^6.4|^7.0|^8.0",
2626
"symfony/error-handler": "^6.4|^7.0|^8.0",
27+
"symfony/framework-bundle": "^6.4|^7.0|^8.0",
2728
"symfony/http-foundation": "^6.4|^7.0|^8.0",
2829
"symfony/service-contracts": "^2.5|^3",
2930
"symfony/stopwatch": "^6.4|^7.0|^8.0",

0 commit comments

Comments
 (0)