Skip to content

Commit 1a30b07

Browse files
[EventDispatcher] swap arguments of dispatch() to allow registering events by FQCN
1 parent 1618e06 commit 1a30b07

11 files changed

+287
-52
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
4.3.0
5+
-----
6+
7+
* The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated
8+
49
4.1.0
510
-----
611

Debug/TraceableEventDispatcher.php

+19-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\EventDispatcher\Event;
1616
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
1717
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
18+
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
1819
use Symfony\Component\Stopwatch\Stopwatch;
1920

2021
/**
@@ -36,7 +37,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
3637

3738
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
3839
{
39-
$this->dispatcher = $dispatcher;
40+
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
4041
$this->stopwatch = $stopwatch;
4142
$this->logger = $logger;
4243
$this->wrappedListeners = [];
@@ -121,15 +122,28 @@ public function hasListeners($eventName = null)
121122

122123
/**
123124
* {@inheritdoc}
125+
*
126+
* @param string|null $eventName
124127
*/
125-
public function dispatch($eventName, Event $event = null)
128+
public function dispatch($event/*, string $eventName = null*/)
126129
{
127130
if (null === $this->callStack) {
128131
$this->callStack = new \SplObjectStorage();
129132
}
130133

131-
if (null === $event) {
132-
$event = new Event();
134+
$eventName = 1 < \func_num_args() ? \func_get_arg(1) : null;
135+
136+
if ($event instanceof Event) {
137+
$eventName = $eventName ?? \get_class($event);
138+
} else {
139+
@trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as first argument is deprecated since Symfony 4.3, pass it second and provide the event object first instead.', EventDispatcherInterface::class), E_USER_DEPRECATED);
140+
$swap = $event;
141+
$event = $eventName ?? new Event();
142+
$eventName = $swap;
143+
144+
if (!$event instanceof Event) {
145+
throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an instance of %s, %s given.', EventDispatcherInterface::class, Event::class, \is_object($event) ? \get_class($event) : \gettype($event)));
146+
}
133147
}
134148

135149
if (null !== $this->logger && $event->isPropagationStopped()) {
@@ -142,7 +156,7 @@ public function dispatch($eventName, Event $event = null)
142156
try {
143157
$e = $this->stopwatch->start($eventName, 'section');
144158
try {
145-
$this->dispatcher->dispatch($eventName, $event);
159+
$this->dispatcher->dispatch($event, $eventName);
146160
} finally {
147161
if ($e->isStarted()) {
148162
$e->stop();

DependencyInjection/RegisterListenersPass.php

+19-3
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,17 @@ class RegisterListenersPass implements CompilerPassInterface
2727
protected $dispatcherService;
2828
protected $listenerTag;
2929
protected $subscriberTag;
30+
protected $eventAliasesParameter;
3031

3132
private $hotPathEvents = [];
3233
private $hotPathTagName;
3334

34-
public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber')
35+
public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases')
3536
{
3637
$this->dispatcherService = $dispatcherService;
3738
$this->listenerTag = $listenerTag;
3839
$this->subscriberTag = $subscriberTag;
40+
$this->eventAliasesParameter = $eventAliasesParameter;
3941
}
4042

4143
public function setHotPathEvents(array $hotPathEvents, $tagName = 'container.hot_path')
@@ -52,6 +54,12 @@ public function process(ContainerBuilder $container)
5254
return;
5355
}
5456

57+
if ($container->hasParameter($this->eventAliasesParameter)) {
58+
$aliases = $container->getParameter($this->eventAliasesParameter);
59+
$container->getParameterBag()->remove($this->eventAliasesParameter);
60+
} else {
61+
$aliases = [];
62+
}
5563
$definition = $container->findDefinition($this->dispatcherService);
5664

5765
foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) {
@@ -61,6 +69,7 @@ public function process(ContainerBuilder $container)
6169
if (!isset($event['event'])) {
6270
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
6371
}
72+
$event['event'] = $aliases[$event['event']] ?? $event['event'];
6473

6574
if (!isset($event['method'])) {
6675
$event['method'] = 'on'.preg_replace_callback([
@@ -98,6 +107,7 @@ public function process(ContainerBuilder $container)
98107
}
99108
$class = $r->name;
100109

110+
ExtractingEventDispatcher::$aliases = $aliases;
101111
ExtractingEventDispatcher::$subscriber = $class;
102112
$extractingDispatcher->addSubscriber($extractingDispatcher);
103113
foreach ($extractingDispatcher->listeners as $args) {
@@ -109,6 +119,7 @@ public function process(ContainerBuilder $container)
109119
}
110120
}
111121
$extractingDispatcher->listeners = [];
122+
ExtractingEventDispatcher::$aliases = [];
112123
}
113124
}
114125
}
@@ -120,6 +131,7 @@ class ExtractingEventDispatcher extends EventDispatcher implements EventSubscrib
120131
{
121132
public $listeners = [];
122133

134+
public static $aliases = [];
123135
public static $subscriber;
124136

125137
public function addListener($eventName, $listener, $priority = 0)
@@ -129,8 +141,12 @@ public function addListener($eventName, $listener, $priority = 0)
129141

130142
public static function getSubscribedEvents()
131143
{
132-
$callback = [self::$subscriber, 'getSubscribedEvents'];
144+
$events = [];
145+
146+
foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) {
147+
$events[self::$aliases[$eventName] ?? $eventName] = $params;
148+
}
133149

134-
return $callback();
150+
return $events;
135151
}
136152
}

EventDispatcher.php

+16-3
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,24 @@ public function __construct()
4141

4242
/**
4343
* {@inheritdoc}
44+
*
45+
* @param string|null $eventName
4446
*/
45-
public function dispatch($eventName, Event $event = null)
47+
public function dispatch($event/*, string $eventName = null*/)
4648
{
47-
if (null === $event) {
48-
$event = new Event();
49+
$eventName = 1 < \func_num_args() ? \func_get_arg(1) : null;
50+
51+
if ($event instanceof Event) {
52+
$eventName = $eventName ?? \get_class($event);
53+
} else {
54+
@trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as first argument is deprecated since Symfony 4.3, pass it second and provide the event object first instead.', EventDispatcherInterface::class), E_USER_DEPRECATED);
55+
$swap = $event;
56+
$event = $eventName ?? new Event();
57+
$eventName = $swap;
58+
59+
if (!$event instanceof Event) {
60+
throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an instance of %s, %s given.', EventDispatcherInterface::class, Event::class, \is_object($event) ? \get_class($event) : \gettype($event)));
61+
}
4962
}
5063

5164
if (null !== $this->optimized && null !== $eventName) {

EventDispatcherInterface.php

+4-6
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,13 @@ interface EventDispatcherInterface
2323
/**
2424
* Dispatches an event to all registered listeners.
2525
*
26-
* @param string $eventName The name of the event to dispatch. The name of
27-
* the event is the name of the method that is
28-
* invoked on listeners.
29-
* @param Event|null $event The event to pass to the event handlers/listeners
30-
* If not supplied, an empty Event instance is created
26+
* @param Event $event The event to pass to the event handlers/listeners
27+
* @param string|null $eventName The name of the event to dispatch. If not supplied,
28+
* the class of $event should be used instead.
3129
*
3230
* @return Event
3331
*/
34-
public function dispatch($eventName, Event $event = null);
32+
public function dispatch($event/*, string $eventName = null*/);
3533

3634
/**
3735
* Adds an event listener that listens on the specified events.

ImmutableEventDispatcher.php

+14-3
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,26 @@ class ImmutableEventDispatcher implements EventDispatcherInterface
2222

2323
public function __construct(EventDispatcherInterface $dispatcher)
2424
{
25-
$this->dispatcher = $dispatcher;
25+
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
2626
}
2727

2828
/**
2929
* {@inheritdoc}
30+
*
31+
* @param string|null $eventName
3032
*/
31-
public function dispatch($eventName, Event $event = null)
33+
public function dispatch($event/*, string $eventName = null*/)
3234
{
33-
return $this->dispatcher->dispatch($eventName, $event);
35+
$eventName = 1 < \func_num_args() ? \func_get_arg(1) : null;
36+
37+
if (\is_scalar($event)) {
38+
// deprecated
39+
$swap = $event;
40+
$event = $eventName ?? new Event();
41+
$eventName = $swap;
42+
}
43+
44+
return $this->dispatcher->dispatch($event, $eventName);
3445
}
3546

3647
/**

LegacyEventDispatcherProxy.php

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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+
* An helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch().
16+
*
17+
* This class should be deprecated in Symfony 5.1
18+
*
19+
* @author Nicolas Grekas <p@tchwork.com>
20+
*/
21+
final class LegacyEventDispatcherProxy implements EventDispatcherInterface
22+
{
23+
private $dispatcher;
24+
25+
public static function decorate(?EventDispatcherInterface $dispatcher): ?EventDispatcherInterface
26+
{
27+
if (null === $dispatcher) {
28+
return null;
29+
}
30+
$r = new \ReflectionMethod($dispatcher, 'dispatch');
31+
$param2 = $r->getParameters()[1] ?? null;
32+
33+
if (!$param2 || !$param2->hasType() || $param2->getType()->isBuiltin()) {
34+
return $dispatcher;
35+
}
36+
37+
@trigger_error(sprintf('The signature of the "%s::dispatch()" method should be updated to "dispatch($event, string $eventName = null)", not doing so is deprecated since Symfony 4.3.', $r->class), E_USER_DEPRECATED);
38+
39+
$self = new self();
40+
$self->dispatcher = $dispatcher;
41+
42+
return $self;
43+
}
44+
45+
/**
46+
* {@inheritdoc}
47+
*
48+
* @param string|null $eventName
49+
*/
50+
public function dispatch($event/*, string $eventName = null*/)
51+
{
52+
$eventName = 1 < \func_num_args() ? \func_get_arg(1) : null;
53+
54+
if ($event instanceof Event) {
55+
$eventName = $eventName ?? \get_class($event);
56+
} else {
57+
@trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as first argument is deprecated since Symfony 4.3, pass it second and provide the event object first instead.', EventDispatcherInterface::class), E_USER_DEPRECATED);
58+
$swap = $event;
59+
$event = $eventName ?? new Event();
60+
$eventName = $swap;
61+
62+
if (!$event instanceof Event) {
63+
throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an instance of %s, %s given.', EventDispatcherInterface::class, Event::class, \is_object($event) ? \get_class($event) : \gettype($event)));
64+
}
65+
}
66+
67+
$listeners = $this->getListeners($eventName);
68+
69+
foreach ($listeners as $listener) {
70+
if ($event->isPropagationStopped()) {
71+
break;
72+
}
73+
$listener($event, $eventName, $this);
74+
}
75+
76+
return $event;
77+
}
78+
79+
/**
80+
* {@inheritdoc}
81+
*/
82+
public function addListener($eventName, $listener, $priority = 0)
83+
{
84+
return $this->dispatcher->addListener($eventName, $listener, $priority);
85+
}
86+
87+
/**
88+
* {@inheritdoc}
89+
*/
90+
public function addSubscriber(EventSubscriberInterface $subscriber)
91+
{
92+
return $this->dispatcher->addSubscriber($subscriber);
93+
}
94+
95+
/**
96+
* {@inheritdoc}
97+
*/
98+
public function removeListener($eventName, $listener)
99+
{
100+
return $this->dispatcher->removeListener($eventName, $listener);
101+
}
102+
103+
/**
104+
* {@inheritdoc}
105+
*/
106+
public function removeSubscriber(EventSubscriberInterface $subscriber)
107+
{
108+
return $this->dispatcher->removeSubscriber($subscriber);
109+
}
110+
111+
/**
112+
* {@inheritdoc}
113+
*/
114+
public function getListeners($eventName = null)
115+
{
116+
return $this->dispatcher->getListeners($eventName);
117+
}
118+
119+
/**
120+
* {@inheritdoc}
121+
*/
122+
public function getListenerPriority($eventName, $listener)
123+
{
124+
return $this->dispatcher->getListenerPriority($eventName, $listener);
125+
}
126+
127+
/**
128+
* {@inheritdoc}
129+
*/
130+
public function hasListeners($eventName = null)
131+
{
132+
return $this->dispatcher->hasListeners($eventName);
133+
}
134+
135+
/**
136+
* Proxies all method calls to the original event dispatcher.
137+
*/
138+
public function __call($method, $arguments)
139+
{
140+
return $this->dispatcher->{$method}(...$arguments);
141+
}
142+
}

0 commit comments

Comments
 (0)