3 namespace Drupal\Component\EventDispatcher;
5 use Symfony\Component\DependencyInjection\IntrospectableContainerInterface;
6 use Symfony\Component\EventDispatcher\Event;
7 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
8 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
11 * A performance optimized container aware event dispatcher.
13 * This version of the event dispatcher contains the following optimizations
14 * in comparison to the Symfony event dispatcher component:
17 * <dt>Faster instantiation of the event dispatcher service</dt>
19 * Instead of calling <code>addSubscriberService</code> once for each
20 * subscriber, a precompiled array of listener definitions is passed
21 * directly to the constructor. This is faster by roughly an order of
22 * magnitude. The listeners are collected and prepared using a compiler
25 * <dt>Lazy instantiation of listeners</dt>
27 * Services are only retrieved from the container just before invocation.
28 * Especially when dispatching the KernelEvents::REQUEST event, this leads
29 * to a more timely invocation of the first listener. Overall dispatch
30 * runtime is not affected by this change though.
34 class ContainerAwareEventDispatcher implements EventDispatcherInterface {
37 * The service container.
39 * @var \Symfony\Component\DependencyInjection\IntrospectableContainerInterface;
44 * Listener definitions.
46 * A nested array of listener definitions keyed by event name and priority.
47 * A listener definition is an associative array with one of the following key
49 * - callable: A callable listener
50 * - service: An array of the form [service id, method]
52 * A service entry will be resolved to a callable only just before its
60 * Whether listeners need to be sorted prior to dispatch, keyed by event name.
67 * Constructs a container aware event dispatcher.
69 * @param \Symfony\Component\DependencyInjection\IntrospectableContainerInterface $container
70 * The service container.
71 * @param array $listeners
72 * A nested array of listener definitions keyed by event name and priority.
73 * The array is expected to be ordered by priority. A listener definition is
74 * an associative array with one of the following key value pairs:
75 * - callable: A callable listener
76 * - service: An array of the form [service id, method]
77 * A service entry will be resolved to a callable only just before its
80 public function __construct(IntrospectableContainerInterface $container, array $listeners = []) {
81 $this->container = $container;
82 $this->listeners = $listeners;
89 public function dispatch($event_name, Event $event = NULL) {
90 if ($event === NULL) {
94 $event->setDispatcher($this);
95 $event->setName($event_name);
97 if (isset($this->listeners[$event_name])) {
98 // Sort listeners if necessary.
99 if (isset($this->unsorted[$event_name])) {
100 krsort($this->listeners[$event_name]);
101 unset($this->unsorted[$event_name]);
104 // Invoke listeners and resolve callables if necessary.
105 foreach ($this->listeners[$event_name] as $priority => &$definitions) {
106 foreach ($definitions as $key => &$definition) {
107 if (!isset($definition['callable'])) {
108 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
111 $definition['callable']($event, $event_name, $this);
112 if ($event->isPropagationStopped()) {
125 public function getListeners($event_name = NULL) {
128 if ($event_name === NULL) {
129 // If event name was omitted, collect all listeners of all events.
130 foreach (array_keys($this->listeners) as $event_name) {
131 $listeners = $this->getListeners($event_name);
132 if (!empty($listeners)) {
133 $result[$event_name] = $listeners;
137 elseif (isset($this->listeners[$event_name])) {
138 // Sort listeners if necessary.
139 if (isset($this->unsorted[$event_name])) {
140 krsort($this->listeners[$event_name]);
141 unset($this->unsorted[$event_name]);
144 // Collect listeners and resolve callables if necessary.
145 foreach ($this->listeners[$event_name] as $priority => &$definitions) {
146 foreach ($definitions as $key => &$definition) {
147 if (!isset($definition['callable'])) {
148 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
151 $result[] = $definition['callable'];
162 public function getListenerPriority($eventName, $listener) {
163 // Parts copied from \Symfony\Component\EventDispatcher, that's why you see
164 // a yoda condition here.
165 if (!isset($this->listeners[$eventName])) {
168 foreach ($this->listeners[$eventName] as $priority => $listeners) {
169 if (FALSE !== ($key = array_search(['callable' => $listener], $listeners, TRUE))) {
173 // Resolve service definitions if the listener has not been found so far.
174 foreach ($this->listeners[$eventName] as $priority => &$definitions) {
175 foreach ($definitions as $key => &$definition) {
176 if (!isset($definition['callable'])) {
177 // Once the callable is retrieved we keep it for subsequent method
178 // invocations on this class.
179 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
180 if ($definition['callable'] === $listener) {
191 public function hasListeners($event_name = NULL) {
192 return (bool) count($this->getListeners($event_name));
198 public function addListener($event_name, $listener, $priority = 0) {
199 $this->listeners[$event_name][$priority][] = ['callable' => $listener];
200 $this->unsorted[$event_name] = TRUE;
206 public function removeListener($event_name, $listener) {
207 if (!isset($this->listeners[$event_name])) {
211 foreach ($this->listeners[$event_name] as $priority => $definitions) {
212 foreach ($definitions as $key => $definition) {
213 if (!isset($definition['callable'])) {
214 if (!$this->container->initialized($definition['service'][0])) {
217 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
220 if ($definition['callable'] === $listener) {
221 unset($this->listeners[$event_name][$priority][$key]);
230 public function addSubscriber(EventSubscriberInterface $subscriber) {
231 foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
232 if (is_string($params)) {
233 $this->addListener($event_name, [$subscriber, $params]);
235 elseif (is_string($params[0])) {
236 $this->addListener($event_name, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
239 foreach ($params as $listener) {
240 $this->addListener($event_name, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
249 public function removeSubscriber(EventSubscriberInterface $subscriber) {
250 foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
251 if (is_array($params) && is_array($params[0])) {
252 foreach ($params as $listener) {
253 $this->removeListener($event_name, [$subscriber, $listener[0]]);
257 $this->removeListener($event_name, [$subscriber, is_string($params) ? $params : $params[0]]);