Chris@0: Chris@0: *
Faster instantiation of the event dispatcher service
Chris@0: *
Chris@0: * Instead of calling addSubscriberService once for each Chris@0: * subscriber, a precompiled array of listener definitions is passed Chris@0: * directly to the constructor. This is faster by roughly an order of Chris@0: * magnitude. The listeners are collected and prepared using a compiler Chris@0: * pass. Chris@0: *
Chris@0: *
Lazy instantiation of listeners
Chris@0: *
Chris@0: * Services are only retrieved from the container just before invocation. Chris@0: * Especially when dispatching the KernelEvents::REQUEST event, this leads Chris@0: * to a more timely invocation of the first listener. Overall dispatch Chris@0: * runtime is not affected by this change though. Chris@0: *
Chris@0: * Chris@0: */ Chris@0: class ContainerAwareEventDispatcher implements EventDispatcherInterface { Chris@0: Chris@0: /** Chris@0: * The service container. Chris@0: * Chris@17: * @var \Symfony\Component\DependencyInjection\ContainerInterface Chris@0: */ Chris@0: protected $container; Chris@0: Chris@0: /** Chris@0: * Listener definitions. Chris@0: * Chris@0: * A nested array of listener definitions keyed by event name and priority. Chris@0: * A listener definition is an associative array with one of the following key Chris@0: * value pairs: Chris@0: * - callable: A callable listener Chris@0: * - service: An array of the form [service id, method] Chris@0: * Chris@0: * A service entry will be resolved to a callable only just before its Chris@0: * invocation. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: protected $listeners; Chris@0: Chris@0: /** Chris@0: * Whether listeners need to be sorted prior to dispatch, keyed by event name. Chris@0: * Chris@0: * @var TRUE[] Chris@0: */ Chris@0: protected $unsorted; Chris@0: Chris@0: /** Chris@0: * Constructs a container aware event dispatcher. Chris@0: * Chris@0: * @param \Symfony\Component\DependencyInjection\ContainerInterface $container Chris@0: * The service container. Chris@0: * @param array $listeners Chris@0: * A nested array of listener definitions keyed by event name and priority. Chris@0: * The array is expected to be ordered by priority. A listener definition is Chris@0: * an associative array with one of the following key value pairs: Chris@0: * - callable: A callable listener Chris@0: * - service: An array of the form [service id, method] Chris@0: * A service entry will be resolved to a callable only just before its Chris@0: * invocation. Chris@0: */ Chris@0: public function __construct(ContainerInterface $container, array $listeners = []) { Chris@0: $this->container = $container; Chris@0: $this->listeners = $listeners; Chris@0: $this->unsorted = []; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function dispatch($event_name, Event $event = NULL) { Chris@0: if ($event === NULL) { Chris@0: $event = new Event(); Chris@0: } Chris@0: Chris@0: if (isset($this->listeners[$event_name])) { Chris@0: // Sort listeners if necessary. Chris@0: if (isset($this->unsorted[$event_name])) { Chris@0: krsort($this->listeners[$event_name]); Chris@0: unset($this->unsorted[$event_name]); Chris@0: } Chris@0: Chris@0: // Invoke listeners and resolve callables if necessary. Chris@0: foreach ($this->listeners[$event_name] as $priority => &$definitions) { Chris@0: foreach ($definitions as $key => &$definition) { Chris@0: if (!isset($definition['callable'])) { Chris@0: $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]]; Chris@0: } Chris@14: if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) { Chris@14: $definition['callable'][0] = $definition['callable'][0](); Chris@14: } Chris@0: Chris@14: call_user_func($definition['callable'], $event, $event_name, $this); Chris@0: if ($event->isPropagationStopped()) { Chris@0: return $event; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: return $event; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getListeners($event_name = NULL) { Chris@0: $result = []; Chris@0: Chris@0: if ($event_name === NULL) { Chris@0: // If event name was omitted, collect all listeners of all events. Chris@0: foreach (array_keys($this->listeners) as $event_name) { Chris@0: $listeners = $this->getListeners($event_name); Chris@0: if (!empty($listeners)) { Chris@0: $result[$event_name] = $listeners; Chris@0: } Chris@0: } Chris@0: } Chris@0: elseif (isset($this->listeners[$event_name])) { Chris@0: // Sort listeners if necessary. Chris@0: if (isset($this->unsorted[$event_name])) { Chris@0: krsort($this->listeners[$event_name]); Chris@0: unset($this->unsorted[$event_name]); Chris@0: } Chris@0: Chris@0: // Collect listeners and resolve callables if necessary. Chris@0: foreach ($this->listeners[$event_name] as $priority => &$definitions) { Chris@0: foreach ($definitions as $key => &$definition) { Chris@0: if (!isset($definition['callable'])) { Chris@0: $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]]; Chris@0: } Chris@14: if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) { Chris@14: $definition['callable'][0] = $definition['callable'][0](); Chris@14: } Chris@0: Chris@0: $result[] = $definition['callable']; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: return $result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@14: public function getListenerPriority($event_name, $listener) { Chris@14: if (!isset($this->listeners[$event_name])) { Chris@0: return; Chris@0: } Chris@14: if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { Chris@14: $listener[0] = $listener[0](); Chris@0: } Chris@0: // Resolve service definitions if the listener has not been found so far. Chris@14: foreach ($this->listeners[$event_name] as $priority => &$definitions) { Chris@0: foreach ($definitions as $key => &$definition) { Chris@0: if (!isset($definition['callable'])) { Chris@0: // Once the callable is retrieved we keep it for subsequent method Chris@0: // invocations on this class. Chris@14: $definition['callable'] = [ Chris@14: $this->container->get($definition['service'][0]), Chris@14: $definition['service'][1], Chris@14: ]; Chris@14: } Chris@14: if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) { Chris@14: $definition['callable'][0] = $definition['callable'][0](); Chris@14: } Chris@14: if ($definition['callable'] === $listener) { Chris@14: return $priority; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function hasListeners($event_name = NULL) { Chris@14: if ($event_name !== NULL) { Chris@14: return !empty($this->listeners[$event_name]); Chris@14: } Chris@14: Chris@14: foreach ($this->listeners as $event_listeners) { Chris@14: if ($event_listeners) { Chris@14: return TRUE; Chris@14: } Chris@14: } Chris@14: Chris@14: return FALSE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function addListener($event_name, $listener, $priority = 0) { Chris@0: $this->listeners[$event_name][$priority][] = ['callable' => $listener]; Chris@0: $this->unsorted[$event_name] = TRUE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function removeListener($event_name, $listener) { Chris@0: if (!isset($this->listeners[$event_name])) { Chris@0: return; Chris@0: } Chris@0: Chris@0: foreach ($this->listeners[$event_name] as $priority => $definitions) { Chris@0: foreach ($definitions as $key => $definition) { Chris@0: if (!isset($definition['callable'])) { Chris@0: if (!$this->container->initialized($definition['service'][0])) { Chris@0: continue; Chris@0: } Chris@0: $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]]; Chris@0: } Chris@0: Chris@14: if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure && !$listener instanceof \Closure) { Chris@14: $definition['callable'][0] = $definition['callable'][0](); Chris@14: } Chris@14: Chris@14: if (is_array($definition['callable']) && isset($definition['callable'][0]) && !$definition['callable'][0] instanceof \Closure && is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { Chris@14: $listener[0] = $listener[0](); Chris@14: } Chris@0: if ($definition['callable'] === $listener) { Chris@14: unset($definitions[$key]); Chris@0: } Chris@0: } Chris@14: if ($definitions) { Chris@14: $this->listeners[$event_name][$priority] = $definitions; Chris@14: } Chris@14: else { Chris@14: unset($this->listeners[$event_name][$priority]); Chris@14: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function addSubscriber(EventSubscriberInterface $subscriber) { Chris@0: foreach ($subscriber->getSubscribedEvents() as $event_name => $params) { Chris@0: if (is_string($params)) { Chris@0: $this->addListener($event_name, [$subscriber, $params]); Chris@0: } Chris@0: elseif (is_string($params[0])) { Chris@0: $this->addListener($event_name, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0); Chris@0: } Chris@0: else { Chris@0: foreach ($params as $listener) { Chris@0: $this->addListener($event_name, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0); Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function removeSubscriber(EventSubscriberInterface $subscriber) { Chris@0: foreach ($subscriber->getSubscribedEvents() as $event_name => $params) { Chris@0: if (is_array($params) && is_array($params[0])) { Chris@0: foreach ($params as $listener) { Chris@0: $this->removeListener($event_name, [$subscriber, $listener[0]]); Chris@0: } Chris@0: } Chris@0: else { Chris@0: $this->removeListener($event_name, [$subscriber, is_string($params) ? $params : $params[0]]); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: }