3 namespace Drupal\Component\EventDispatcher;
5 use Symfony\Component\DependencyInjection\ContainerInterface;
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\ContainerInterface;
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\ContainerInterface $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(ContainerInterface $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 if (isset($this->listeners[$event_name])) {
95 // Sort listeners if necessary.
96 if (isset($this->unsorted[$event_name])) {
97 krsort($this->listeners[$event_name]);
98 unset($this->unsorted[$event_name]);
101 // Invoke listeners and resolve callables if necessary.
102 foreach ($this->listeners[$event_name] as $priority => &$definitions) {
103 foreach ($definitions as $key => &$definition) {
104 if (!isset($definition['callable'])) {
105 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
108 $definition['callable']($event, $event_name, $this);
109 if ($event->isPropagationStopped()) {
122 public function getListeners($event_name = NULL) {
125 if ($event_name === NULL) {
126 // If event name was omitted, collect all listeners of all events.
127 foreach (array_keys($this->listeners) as $event_name) {
128 $listeners = $this->getListeners($event_name);
129 if (!empty($listeners)) {
130 $result[$event_name] = $listeners;
134 elseif (isset($this->listeners[$event_name])) {
135 // Sort listeners if necessary.
136 if (isset($this->unsorted[$event_name])) {
137 krsort($this->listeners[$event_name]);
138 unset($this->unsorted[$event_name]);
141 // Collect listeners and resolve callables if necessary.
142 foreach ($this->listeners[$event_name] as $priority => &$definitions) {
143 foreach ($definitions as $key => &$definition) {
144 if (!isset($definition['callable'])) {
145 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
148 $result[] = $definition['callable'];
159 public function getListenerPriority($eventName, $listener) {
160 // Parts copied from \Symfony\Component\EventDispatcher, that's why you see
161 // a yoda condition here.
162 if (!isset($this->listeners[$eventName])) {
165 foreach ($this->listeners[$eventName] as $priority => $listeners) {
166 if (FALSE !== ($key = array_search(['callable' => $listener], $listeners, TRUE))) {
170 // Resolve service definitions if the listener has not been found so far.
171 foreach ($this->listeners[$eventName] as $priority => &$definitions) {
172 foreach ($definitions as $key => &$definition) {
173 if (!isset($definition['callable'])) {
174 // Once the callable is retrieved we keep it for subsequent method
175 // invocations on this class.
176 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
177 if ($definition['callable'] === $listener) {
188 public function hasListeners($event_name = NULL) {
189 return (bool) count($this->getListeners($event_name));
195 public function addListener($event_name, $listener, $priority = 0) {
196 $this->listeners[$event_name][$priority][] = ['callable' => $listener];
197 $this->unsorted[$event_name] = TRUE;
203 public function removeListener($event_name, $listener) {
204 if (!isset($this->listeners[$event_name])) {
208 foreach ($this->listeners[$event_name] as $priority => $definitions) {
209 foreach ($definitions as $key => $definition) {
210 if (!isset($definition['callable'])) {
211 if (!$this->container->initialized($definition['service'][0])) {
214 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
217 if ($definition['callable'] === $listener) {
218 unset($this->listeners[$event_name][$priority][$key]);
227 public function addSubscriber(EventSubscriberInterface $subscriber) {
228 foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
229 if (is_string($params)) {
230 $this->addListener($event_name, [$subscriber, $params]);
232 elseif (is_string($params[0])) {
233 $this->addListener($event_name, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
236 foreach ($params as $listener) {
237 $this->addListener($event_name, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
246 public function removeSubscriber(EventSubscriberInterface $subscriber) {
247 foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
248 if (is_array($params) && is_array($params[0])) {
249 foreach ($params as $listener) {
250 $this->removeListener($event_name, [$subscriber, $listener[0]]);
254 $this->removeListener($event_name, [$subscriber, is_string($params) ? $params : $params[0]]);