Skip to content

Commit 1e4499c

Browse files
Merge branch '4.4'
* 4.4: (26 commits) cs fix [Validator] sync NO and NB translations [Cache] improve perf of pruning for fs-based adapters [Cache] cs fix [Cache] clean tags folder on invalidation [Cache] remove implicit dependency on symfony/filesystem Allow to set cookie_samesite to 'none' [Dotenv] support setting default env var values [VarDumper] fix array key error for class SymfonyCaster [Cache] Improve RedisTagAwareAdapter invalidation logic & requirements Adds missing translations for no nb [HttpKernel] fix $dotenvVars in data collector Add the missing translations for the Swedish ("sv") locale Prevent ProgressBar redraw when message is same [DI] enable improved syntax for defining method calls in Yaml bumped Symfony version to 4.3.6 updated VERSION for 4.3.5 updated CHANGELOG for 4.3.5 bumped Symfony version to 3.4.33 updated VERSION for 3.4.32 ...
2 parents 40b0e33 + c73bdef commit 1e4499c

File tree

3 files changed

+179
-1
lines changed

3 files changed

+179
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ CHANGELOG
1313
-----
1414

1515
* `AddEventAliasesPass` has been added, allowing applications and bundles to extend the event alias mapping used by `RegisterListenersPass`.
16+
* Made the `event` attribute of the `kernel.event_listener` tag optional for FQCN events.
1617

1718
4.3.0
1819
-----

DependencyInjection/RegisterListenersPass.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
1717
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1818
use Symfony\Component\DependencyInjection\Reference;
19+
use Symfony\Component\EventDispatcher\Event as LegacyEvent;
1920
use Symfony\Component\EventDispatcher\EventDispatcher;
2021
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
22+
use Symfony\Contracts\EventDispatcher\Event;
2123

2224
/**
2325
* Compiler pass to register tagged services for an event dispatcher.
@@ -67,8 +69,14 @@ public function process(ContainerBuilder $container)
6769
$priority = isset($event['priority']) ? $event['priority'] : 0;
6870

6971
if (!isset($event['event'])) {
70-
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
72+
if ($container->getDefinition($id)->hasTag($this->subscriberTag)) {
73+
continue;
74+
}
75+
76+
$event['method'] = $event['method'] ?? '__invoke';
77+
$event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']);
7178
}
79+
7280
$event['event'] = $aliases[$event['event']] ?? $event['event'];
7381

7482
if (!isset($event['method'])) {
@@ -122,6 +130,24 @@ public function process(ContainerBuilder $container)
122130
ExtractingEventDispatcher::$aliases = [];
123131
}
124132
}
133+
134+
private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string
135+
{
136+
if (
137+
null === ($class = $container->getDefinition($id)->getClass())
138+
|| !($r = $container->getReflectionClass($class, false))
139+
|| !$r->hasMethod($method)
140+
|| 1 > ($m = $r->getMethod($method))->getNumberOfParameters()
141+
|| !($type = $m->getParameters()[0]->getType())
142+
|| $type->isBuiltin()
143+
|| Event::class === ($name = $type->getName())
144+
|| LegacyEvent::class === $name
145+
) {
146+
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
147+
}
148+
149+
return $name;
150+
}
125151
}
126152

127153
/**

Tests/DependencyInjection/RegisterListenersPassTest.php

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1718
use Symfony\Component\DependencyInjection\Reference;
1819
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
1920
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
@@ -244,6 +245,116 @@ public function testAliasedEventListener(): void
244245
];
245246
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
246247
}
248+
249+
public function testOmitEventNameOnTypedListener(): void
250+
{
251+
$container = new ContainerBuilder();
252+
$container->setParameter('event_dispatcher.event_aliases', [AliasedEvent::class => 'aliased_event']);
253+
$container->register('foo', TypedListener::class)->addTag('kernel.event_listener', ['method' => 'onEvent']);
254+
$container->register('bar', TypedListener::class)->addTag('kernel.event_listener');
255+
$container->register('event_dispatcher');
256+
257+
$registerListenersPass = new RegisterListenersPass();
258+
$registerListenersPass->process($container);
259+
260+
$definition = $container->getDefinition('event_dispatcher');
261+
$expectedCalls = [
262+
[
263+
'addListener',
264+
[
265+
CustomEvent::class,
266+
[new ServiceClosureArgument(new Reference('foo')), 'onEvent'],
267+
0,
268+
],
269+
],
270+
[
271+
'addListener',
272+
[
273+
'aliased_event',
274+
[new ServiceClosureArgument(new Reference('bar')), '__invoke'],
275+
0,
276+
],
277+
],
278+
];
279+
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
280+
}
281+
282+
public function testOmitEventNameOnUntypedListener(): void
283+
{
284+
$container = new ContainerBuilder();
285+
$container->register('foo', InvokableListenerService::class)->addTag('kernel.event_listener', ['method' => 'onEvent']);
286+
$container->register('event_dispatcher');
287+
288+
$this->expectException(InvalidArgumentException::class);
289+
$this->expectExceptionMessage('Service "foo" must define the "event" attribute on "kernel.event_listener" tags.');
290+
291+
$registerListenersPass = new RegisterListenersPass();
292+
$registerListenersPass->process($container);
293+
}
294+
295+
public function testOmitEventNameAndMethodOnUntypedListener(): void
296+
{
297+
$container = new ContainerBuilder();
298+
$container->register('foo', InvokableListenerService::class)->addTag('kernel.event_listener');
299+
$container->register('event_dispatcher');
300+
301+
$this->expectException(InvalidArgumentException::class);
302+
$this->expectExceptionMessage('Service "foo" must define the "event" attribute on "kernel.event_listener" tags.');
303+
304+
$registerListenersPass = new RegisterListenersPass();
305+
$registerListenersPass->process($container);
306+
}
307+
308+
/**
309+
* @requires PHP 7.2
310+
*/
311+
public function testOmitEventNameAndMethodOnGenericListener(): void
312+
{
313+
$container = new ContainerBuilder();
314+
$container->register('foo', GenericListener::class)->addTag('kernel.event_listener');
315+
$container->register('event_dispatcher');
316+
317+
$this->expectException(InvalidArgumentException::class);
318+
$this->expectExceptionMessage('Service "foo" must define the "event" attribute on "kernel.event_listener" tags.');
319+
320+
$registerListenersPass = new RegisterListenersPass();
321+
$registerListenersPass->process($container);
322+
}
323+
324+
public function testOmitEventNameOnSubscriber(): void
325+
{
326+
$container = new ContainerBuilder();
327+
$container->register('subscriber', IncompleteSubscriber::class)
328+
->addTag('kernel.event_subscriber')
329+
->addTag('kernel.event_listener')
330+
->addTag('kernel.event_listener', ['event' => 'bar', 'method' => 'onBar'])
331+
;
332+
$container->register('event_dispatcher');
333+
334+
$registerListenersPass = new RegisterListenersPass();
335+
$registerListenersPass->process($container);
336+
337+
$definition = $container->getDefinition('event_dispatcher');
338+
$expectedCalls = [
339+
[
340+
'addListener',
341+
[
342+
'bar',
343+
[new ServiceClosureArgument(new Reference('subscriber')), 'onBar'],
344+
0,
345+
],
346+
],
347+
[
348+
'addListener',
349+
[
350+
'foo',
351+
[new ServiceClosureArgument(new Reference('subscriber')), 'onFoo'],
352+
0,
353+
],
354+
],
355+
];
356+
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
357+
}
247358
}
248359

249360
class SubscriberService implements EventSubscriberInterface
@@ -285,3 +396,43 @@ final class AliasedEvent
285396
final class CustomEvent
286397
{
287398
}
399+
400+
final class TypedListener
401+
{
402+
public function __invoke(AliasedEvent $event): void
403+
{
404+
}
405+
406+
public function onEvent(CustomEvent $event): void
407+
{
408+
}
409+
}
410+
411+
final class GenericListener
412+
{
413+
public function __invoke(object $event): void
414+
{
415+
}
416+
}
417+
418+
final class IncompleteSubscriber implements EventSubscriberInterface
419+
{
420+
public static function getSubscribedEvents(): array
421+
{
422+
return [
423+
'foo' => 'onFoo',
424+
];
425+
}
426+
427+
public function onFoo(): void
428+
{
429+
}
430+
431+
public function onBar(): void
432+
{
433+
}
434+
435+
public function __invoke(CustomEvent $event): void
436+
{
437+
}
438+
}

0 commit comments

Comments
 (0)