8000 Autoconfigure event listeners · symfony/symfony@788b744 · GitHub
[go: up one dir, main page]

Skip to content

Commit 788b744

Browse files
committed
Autoconfigure event listeners
1 parent 162d5a8 commit 788b744

File tree

4 files changed

+116
-4
lines changed

4 files changed

+116
-4
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
use Symfony\Component\DependencyInjection\Parameter;
5454
use Symfony\Component\DependencyInjection\Reference;
5555
use Symfony\Component\DependencyInjection\ServiceLocator;
56+
use Symfony\Component\EventDispatcher\EventListenerInterface;
5657
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
5758
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
5859
use Symfony\Component\Finder\Finder;
@@ -364,6 +365,8 @@ public function load(array $configs, ContainerBuilder $container)
364365
->addTag('kernel.cache_clearer');
365366
$container->registerForAutoconfiguration(CacheWarmerInterface::class)
366367
->addTag('kernel.cache_warmer');
368+
$container->registerForAutoconfiguration(EventListenerInterface::class)
369+
->addTag('kernel.event_listener');
367370
$container->registerForAutoconfiguration(EventSubscriberInterface::class)
368371
->addTag('kernel.event_subscriber');
369372
$container->registerForAutoconfiguration(ResetInterface::class)

src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
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;
1920
use Symfony\Component\EventDispatcher\EventDispatcher;
2021
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
2122

@@ -63,12 +64,15 @@ public function process(ContainerBuilder $container)
6364
$definition = $container->findDefinition($this->dispatcherService);
6465

6566
foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) {
67+
$isAutoregisterEnabled = false;
68+
$registeredMethods = [];
6669
foreach ($events as $event) {
67-
$priority = isset($event['priority']) ? $event['priority'] : 0;
68-
6970
if (!isset($event['event'])) {
70-
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
71+
$isAutoregisterEnabled = true;
72+
continue;
7173
}
74+
75+
$priority = isset($event['priority']) ? $event['priority'] : 0;
7276
$event['event'] = $aliases[$event['event']] ?? $event['event'];
7377

7478
if (!isset($event['method'])) {
@@ -82,13 +86,38 @@ public function process(ContainerBuilder $container)
8286
$event['method'] = '__invoke';
8387
}
8488
}
89+
$registeredMethods[$event['method']] = true;
8590

8691
$definition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]);
87-
8892
if (isset($this->hotPathEvents[$event['event']])) {
8993
$container->getDefinition($id)->addTag($this->hotPathTagName);
9094
}
9195
}
96+
97+
if ($isAutoregisterEnabled) {
98+
if (null === ($class = $container->getDefinition($id)->getClass()) || null === ($r = $container->getReflectionClass($class, false))) {
99+
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
100+
}
101+
$hasAutoregisteredMethod = false;
102+
foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $m) {
103+
if ($m->isConstructor() || $m->isDestructor() || $m->isStatic() || $m->isAbstract() || $m->isGenerator() || isset($registeredMethods[$m->getName()])) {
104+
continue;
105+
}
106+
$parameters = $m->getParameters();
107+
if ($m->getNumberOfRequiredParameters() < 1 || !$parameters[0]->hasType() || !\is_a(($eventName = $parameters[0]->getType()->getName()), Event::class, true)) {
108+
continue;
109+
}
110+
$hasAutoregisteredMethod = true;
111+
112+
$definition->addMethodCall('addListener', [$eventName, [new ServiceClosureArgument(new Reference($id)), $m->getName()], 0]);
113+
if (isset($this->hotPathEvents[$eventName])) {
114+
$container->getDefinition($id)->addTag($this->hotPathTagName);
115+
}
116+
}
117+
if (!$hasAutoregisteredMethod) {
118+
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
119+
}
120+
}
92121
}
93122

94123
$extractingDispatcher = new ExtractingEventDispatcher();
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 <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;
13+
14+
/**
15+
* @author Jérémy Derussé <jeremy@derusse.com>
16+
*/
17+
interface EventListenerInterface
18+
{
19+
}

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
1717
use Symfony\Component\DependencyInjection\Reference;
1818
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
19+
use Symfony\Component\EventDispatcher\Event;
1920

2021
class RegisterListenersPassTest extends TestCase
2122
{
@@ -148,6 +149,8 @@ public function testInvokableEventListener()
148149
$container->register('foo', \stdClass::class)->addTag('kernel.event_listener', ['event' => 'foo.bar']);
149150
$container->register('bar', InvokableListenerService::class)->addTag('kernel.event_listener', ['event' => 'foo.bar']);
150151
$container->register('baz', InvokableListenerService::class)->addTag('kernel.event_listener', ['event' => 'event']);
152+
$container->register('qux', AutoconfigurationListenerService::class)->addTag('kernel.event_listener');
153+
$container->register('qum', HybridListenerService::class)->addTag('kernel.event_listener', ['event' => 'foo'])->addTag('kernel.event_listener');
151154
$container->register('event_dispatcher', \stdClass::class);
152155

153156
$registerListenersPass = new RegisterListenersPass();
@@ -179,6 +182,38 @@ public function testInvokableEventListener()
179182
0,
180183
],
181184
],
185+
[
186+
'addListener',
187+
[
188+
'Symfony\Component\EventDispatcher\Event',
189+
[new ServiceClosureArgument(new Reference('qux')), 'onFoo'],
190+
0,
191+
],
192+
],
193+
[
194+
'addListener',
195+
[
196+
'Symfony\Component\EventDispatcher\Tests\DependencyInjection\FooEvent',
197+
[new ServiceClosureArgument(new Reference('qux')), 'onBar'],
198+
0,
199+
],
200+
],
201+
[
202+
'addListener',
203+
[
204+
'foo',
205+
[new ServiceClosureArgument(new Reference('qum')), 'onFoo'],
206+
0,
207+
],
208+
],
209+
[
210+
'addListener',
211+
[
212+
'Symfony\Component\EventDispatcher\Tests\DependencyInjection\FooEvent',
213+
[new ServiceClosureArgument(new Reference('qum')), 'onBar'],
214+
0,
215+
],
216+
],
182217
];
183218
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
184219
}
@@ -204,3 +239,29 @@ public function onEvent()
204239
{
205240
}
206241
}
242+
243+
class AutoconfigurationListenerService
244+
{
245+
public function onFoo(Event $e)
246+
{
247+
}
248+
249+
public function onBar(FooEvent $e)
250+
{
251+
}
252+
}
253+
254+
class HybridListenerService
255+
{
256+
public function onFoo(Event $e)
257+
{
258+
}
259+
260+
public function onBar(FooEvent $e)
261+
{
262+
}
263+
}
264+
265+
class FooEvent extends Event
266+
{
267+
}

0 commit comments

Comments
 (0)
0