Skip to content

Commit

Permalink
[EventDispatcher] swap arguments of dispatch() to allow registering e…
Browse files Browse the repository at this point in the history
…vents by FQCN
  • Loading branch information
nicolas-grekas committed Mar 14, 2019
1 parent 1618e06 commit 1a30b07
Show file tree
Hide file tree
Showing 11 changed files with 287 additions and 52 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

4.3.0
-----

* The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated

4.1.0
-----

Expand Down
24 changes: 19 additions & 5 deletions Debug/TraceableEventDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
use Symfony\Component\Stopwatch\Stopwatch;

/**
Expand All @@ -36,7 +37,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface

public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
{
$this->dispatcher = $dispatcher;
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
$this->stopwatch = $stopwatch;
$this->logger = $logger;
$this->wrappedListeners = [];
Expand Down Expand Up @@ -121,15 +122,28 @@ public function hasListeners($eventName = null)

/**
* {@inheritdoc}
*
* @param string|null $eventName
*/
public function dispatch($eventName, Event $event = null)
public function dispatch($event/*, string $eventName = null*/)
{
if (null === $this->callStack) {
$this->callStack = new \SplObjectStorage();
}

if (null === $event) {
$event = new Event();
$eventName = 1 < \func_num_args() ? \func_get_arg(1) : null;

if ($event instanceof Event) {
$eventName = $eventName ?? \get_class($event);
} else {
@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);
$swap = $event;
$event = $eventName ?? new Event();
$eventName = $swap;

if (!$event instanceof Event) {
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)));
}
}

if (null !== $this->logger && $event->isPropagationStopped()) {
Expand All @@ -142,7 +156,7 @@ public function dispatch($eventName, Event $event = null)
try {
$e = $this->stopwatch->start($eventName, 'section');
try {
$this->dispatcher->dispatch($eventName, $event);
$this->dispatcher->dispatch($event, $eventName);
} finally {
if ($e->isStarted()) {
$e->stop();
Expand Down
22 changes: 19 additions & 3 deletions DependencyInjection/RegisterListenersPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ class RegisterListenersPass implements CompilerPassInterface
protected $dispatcherService;
protected $listenerTag;
protected $subscriberTag;
protected $eventAliasesParameter;

private $hotPathEvents = [];
private $hotPathTagName;

public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber')
public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases')
{
$this->dispatcherService = $dispatcherService;
$this->listenerTag = $listenerTag;
$this->subscriberTag = $subscriberTag;
$this->eventAliasesParameter = $eventAliasesParameter;
}

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

if ($container->hasParameter($this->eventAliasesParameter)) {
$aliases = $container->getParameter($this->eventAliasesParameter);
$container->getParameterBag()->remove($this->eventAliasesParameter);
} else {
$aliases = [];
}
$definition = $container->findDefinition($this->dispatcherService);

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

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

ExtractingEventDispatcher::$aliases = $aliases;
ExtractingEventDispatcher::$subscriber = $class;
$extractingDispatcher->addSubscriber($extractingDispatcher);
foreach ($extractingDispatcher->listeners as $args) {
Expand All @@ -109,6 +119,7 @@ public function process(ContainerBuilder $container)
}
}
$extractingDispatcher->listeners = [];
ExtractingEventDispatcher::$aliases = [];
}
}
}
Expand All @@ -120,6 +131,7 @@ class ExtractingEventDispatcher extends EventDispatcher implements EventSubscrib
{
public $listeners = [];

public static $aliases = [];
public static $subscriber;

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

public static function getSubscribedEvents()
{
$callback = [self::$subscriber, 'getSubscribedEvents'];
$events = [];

foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) {
$events[self::$aliases[$eventName] ?? $eventName] = $params;
}

return $callback();
return $events;
}
}
19 changes: 16 additions & 3 deletions EventDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,24 @@ public function __construct()

/**
* {@inheritdoc}
*
* @param string|null $eventName
*/
public function dispatch($eventName, Event $event = null)
public function dispatch($event/*, string $eventName = null*/)
{
if (null === $event) {
$event = new Event();
$eventName = 1 < \func_num_args() ? \func_get_arg(1) : null;

if ($event instanceof Event) {
$eventName = $eventName ?? \get_class($event);
} else {
@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);
$swap = $event;
$event = $eventName ?? new Event();
$eventName = $swap;

if (!$event instanceof Event) {
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)));
}
}

if (null !== $this->optimized && null !== $eventName) {
Expand Down
10 changes: 4 additions & 6 deletions EventDispatcherInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,13 @@ interface EventDispatcherInterface
/**
* Dispatches an event to all registered listeners.
*
* @param string $eventName The name of the event to dispatch. The name of
* the event is the name of the method that is
* invoked on listeners.
* @param Event|null $event The event to pass to the event handlers/listeners
* If not supplied, an empty Event instance is created
* @param Event $event The event to pass to the event handlers/listeners
* @param string|null $eventName The name of the event to dispatch. If not supplied,
* the class of $event should be used instead.
*
* @return Event
*/
public function dispatch($eventName, Event $event = null);
public function dispatch($event/*, string $eventName = null*/);

/**
* Adds an event listener that listens on the specified events.
Expand Down
17 changes: 14 additions & 3 deletions ImmutableEventDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,26 @@ class ImmutableEventDispatcher implements EventDispatcherInterface

public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
}

/**
* {@inheritdoc}
*
* @param string|null $eventName
*/
public function dispatch($eventName, Event $event = null)
public function dispatch($event/*, string $eventName = null*/)
{
return $this->dispatcher->dispatch($eventName, $event);
$eventName = 1 < \func_num_args() ? \func_get_arg(1) : null;

if (\is_scalar($event)) {
// deprecated
$swap = $event;
$event = $eventName ?? new Event();
$eventName = $swap;
}

return $this->dispatcher->dispatch($event, $eventName);
}

/**
Expand Down
142 changes: 142 additions & 0 deletions LegacyEventDispatcherProxy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\EventDispatcher;

/**
* An helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch().
*
* This class should be deprecated in Symfony 5.1
*
* @author Nicolas Grekas <[email protected]>
*/
final class LegacyEventDispatcherProxy implements EventDispatcherInterface
{
private $dispatcher;

public static function decorate(?EventDispatcherInterface $dispatcher): ?EventDispatcherInterface
{
if (null === $dispatcher) {
return null;
}
$r = new \ReflectionMethod($dispatcher, 'dispatch');
$param2 = $r->getParameters()[1] ?? null;

if (!$param2 || !$param2->hasType() || $param2->getType()->isBuiltin()) {
return $dispatcher;
}

@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);

$self = new self();
$self->dispatcher = $dispatcher;

return $self;
}

/**
* {@inheritdoc}
*
* @param string|null $eventName
*/
public function dispatch($event/*, string $eventName = null*/)
{
$eventName = 1 < \func_num_args() ? \func_get_arg(1) : null;

if ($event instanceof Event) {
$eventName = $eventName ?? \get_class($event);
} else {
@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);
$swap = $event;
$event = $eventName ?? new Event();
$eventName = $swap;

if (!$event instanceof Event) {
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)));
}
}

$listeners = $this->getListeners($eventName);

foreach ($listeners as $listener) {
if ($event->isPropagationStopped()) {
break;
}
$listener($event, $eventName, $this);
}

return $event;
}

/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
{
return $this->dispatcher->addListener($eventName, $listener, $priority);
}

/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
return $this->dispatcher->addSubscriber($subscriber);
}

/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
{
return $this->dispatcher->removeListener($eventName, $listener);
}

/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
return $this->dispatcher->removeSubscriber($subscriber);
}

/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
return $this->dispatcher->getListeners($eventName);
}

/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
{
return $this->dispatcher->getListenerPriority($eventName, $listener);
}

/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
return $this->dispatcher->hasListeners($eventName);
}

/**
* Proxies all method calls to the original event dispatcher.
*/
public function __call($method, $arguments)
{
return $this->dispatcher->{$method}(...$arguments);
}
}
Loading

0 comments on commit 1a30b07

Please sign in to comment.