annotate core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Component\EventDispatcher;
Chris@0 4
Chris@0 5 use Symfony\Component\DependencyInjection\ContainerInterface;
Chris@0 6 use Symfony\Component\EventDispatcher\Event;
Chris@0 7 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
Chris@0 8 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
Chris@0 9
Chris@0 10 /**
Chris@0 11 * A performance optimized container aware event dispatcher.
Chris@0 12 *
Chris@0 13 * This version of the event dispatcher contains the following optimizations
Chris@0 14 * in comparison to the Symfony event dispatcher component:
Chris@0 15 *
Chris@0 16 * <dl>
Chris@0 17 * <dt>Faster instantiation of the event dispatcher service</dt>
Chris@0 18 * <dd>
Chris@0 19 * Instead of calling <code>addSubscriberService</code> once for each
Chris@0 20 * subscriber, a precompiled array of listener definitions is passed
Chris@0 21 * directly to the constructor. This is faster by roughly an order of
Chris@0 22 * magnitude. The listeners are collected and prepared using a compiler
Chris@0 23 * pass.
Chris@0 24 * </dd>
Chris@0 25 * <dt>Lazy instantiation of listeners</dt>
Chris@0 26 * <dd>
Chris@0 27 * Services are only retrieved from the container just before invocation.
Chris@0 28 * Especially when dispatching the KernelEvents::REQUEST event, this leads
Chris@0 29 * to a more timely invocation of the first listener. Overall dispatch
Chris@0 30 * runtime is not affected by this change though.
Chris@0 31 * </dd>
Chris@0 32 * </dl>
Chris@0 33 */
Chris@0 34 class ContainerAwareEventDispatcher implements EventDispatcherInterface {
Chris@0 35
Chris@0 36 /**
Chris@0 37 * The service container.
Chris@0 38 *
Chris@17 39 * @var \Symfony\Component\DependencyInjection\ContainerInterface
Chris@0 40 */
Chris@0 41 protected $container;
Chris@0 42
Chris@0 43 /**
Chris@0 44 * Listener definitions.
Chris@0 45 *
Chris@0 46 * A nested array of listener definitions keyed by event name and priority.
Chris@0 47 * A listener definition is an associative array with one of the following key
Chris@0 48 * value pairs:
Chris@0 49 * - callable: A callable listener
Chris@0 50 * - service: An array of the form [service id, method]
Chris@0 51 *
Chris@0 52 * A service entry will be resolved to a callable only just before its
Chris@0 53 * invocation.
Chris@0 54 *
Chris@0 55 * @var array
Chris@0 56 */
Chris@0 57 protected $listeners;
Chris@0 58
Chris@0 59 /**
Chris@0 60 * Whether listeners need to be sorted prior to dispatch, keyed by event name.
Chris@0 61 *
Chris@0 62 * @var TRUE[]
Chris@0 63 */
Chris@0 64 protected $unsorted;
Chris@0 65
Chris@0 66 /**
Chris@0 67 * Constructs a container aware event dispatcher.
Chris@0 68 *
Chris@0 69 * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
Chris@0 70 * The service container.
Chris@0 71 * @param array $listeners
Chris@0 72 * A nested array of listener definitions keyed by event name and priority.
Chris@0 73 * The array is expected to be ordered by priority. A listener definition is
Chris@0 74 * an associative array with one of the following key value pairs:
Chris@0 75 * - callable: A callable listener
Chris@0 76 * - service: An array of the form [service id, method]
Chris@0 77 * A service entry will be resolved to a callable only just before its
Chris@0 78 * invocation.
Chris@0 79 */
Chris@0 80 public function __construct(ContainerInterface $container, array $listeners = []) {
Chris@0 81 $this->container = $container;
Chris@0 82 $this->listeners = $listeners;
Chris@0 83 $this->unsorted = [];
Chris@0 84 }
Chris@0 85
Chris@0 86 /**
Chris@0 87 * {@inheritdoc}
Chris@0 88 */
Chris@0 89 public function dispatch($event_name, Event $event = NULL) {
Chris@0 90 if ($event === NULL) {
Chris@0 91 $event = new Event();
Chris@0 92 }
Chris@0 93
Chris@0 94 if (isset($this->listeners[$event_name])) {
Chris@0 95 // Sort listeners if necessary.
Chris@0 96 if (isset($this->unsorted[$event_name])) {
Chris@0 97 krsort($this->listeners[$event_name]);
Chris@0 98 unset($this->unsorted[$event_name]);
Chris@0 99 }
Chris@0 100
Chris@0 101 // Invoke listeners and resolve callables if necessary.
Chris@0 102 foreach ($this->listeners[$event_name] as $priority => &$definitions) {
Chris@0 103 foreach ($definitions as $key => &$definition) {
Chris@0 104 if (!isset($definition['callable'])) {
Chris@0 105 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
Chris@0 106 }
Chris@14 107 if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
Chris@14 108 $definition['callable'][0] = $definition['callable'][0]();
Chris@14 109 }
Chris@0 110
Chris@14 111 call_user_func($definition['callable'], $event, $event_name, $this);
Chris@0 112 if ($event->isPropagationStopped()) {
Chris@0 113 return $event;
Chris@0 114 }
Chris@0 115 }
Chris@0 116 }
Chris@0 117 }
Chris@0 118
Chris@0 119 return $event;
Chris@0 120 }
Chris@0 121
Chris@0 122 /**
Chris@0 123 * {@inheritdoc}
Chris@0 124 */
Chris@0 125 public function getListeners($event_name = NULL) {
Chris@0 126 $result = [];
Chris@0 127
Chris@0 128 if ($event_name === NULL) {
Chris@0 129 // If event name was omitted, collect all listeners of all events.
Chris@0 130 foreach (array_keys($this->listeners) as $event_name) {
Chris@0 131 $listeners = $this->getListeners($event_name);
Chris@0 132 if (!empty($listeners)) {
Chris@0 133 $result[$event_name] = $listeners;
Chris@0 134 }
Chris@0 135 }
Chris@0 136 }
Chris@0 137 elseif (isset($this->listeners[$event_name])) {
Chris@0 138 // Sort listeners if necessary.
Chris@0 139 if (isset($this->unsorted[$event_name])) {
Chris@0 140 krsort($this->listeners[$event_name]);
Chris@0 141 unset($this->unsorted[$event_name]);
Chris@0 142 }
Chris@0 143
Chris@0 144 // Collect listeners and resolve callables if necessary.
Chris@0 145 foreach ($this->listeners[$event_name] as $priority => &$definitions) {
Chris@0 146 foreach ($definitions as $key => &$definition) {
Chris@0 147 if (!isset($definition['callable'])) {
Chris@0 148 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
Chris@0 149 }
Chris@14 150 if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
Chris@14 151 $definition['callable'][0] = $definition['callable'][0]();
Chris@14 152 }
Chris@0 153
Chris@0 154 $result[] = $definition['callable'];
Chris@0 155 }
Chris@0 156 }
Chris@0 157 }
Chris@0 158
Chris@0 159 return $result;
Chris@0 160 }
Chris@0 161
Chris@0 162 /**
Chris@0 163 * {@inheritdoc}
Chris@0 164 */
Chris@14 165 public function getListenerPriority($event_name, $listener) {
Chris@14 166 if (!isset($this->listeners[$event_name])) {
Chris@0 167 return;
Chris@0 168 }
Chris@14 169 if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
Chris@14 170 $listener[0] = $listener[0]();
Chris@0 171 }
Chris@0 172 // Resolve service definitions if the listener has not been found so far.
Chris@14 173 foreach ($this->listeners[$event_name] as $priority => &$definitions) {
Chris@0 174 foreach ($definitions as $key => &$definition) {
Chris@0 175 if (!isset($definition['callable'])) {
Chris@0 176 // Once the callable is retrieved we keep it for subsequent method
Chris@0 177 // invocations on this class.
Chris@14 178 $definition['callable'] = [
Chris@14 179 $this->container->get($definition['service'][0]),
Chris@14 180 $definition['service'][1],
Chris@14 181 ];
Chris@14 182 }
Chris@14 183 if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure) {
Chris@14 184 $definition['callable'][0] = $definition['callable'][0]();
Chris@14 185 }
Chris@14 186 if ($definition['callable'] === $listener) {
Chris@14 187 return $priority;
Chris@0 188 }
Chris@0 189 }
Chris@0 190 }
Chris@0 191 }
Chris@0 192
Chris@0 193 /**
Chris@0 194 * {@inheritdoc}
Chris@0 195 */
Chris@0 196 public function hasListeners($event_name = NULL) {
Chris@14 197 if ($event_name !== NULL) {
Chris@14 198 return !empty($this->listeners[$event_name]);
Chris@14 199 }
Chris@14 200
Chris@14 201 foreach ($this->listeners as $event_listeners) {
Chris@14 202 if ($event_listeners) {
Chris@14 203 return TRUE;
Chris@14 204 }
Chris@14 205 }
Chris@14 206
Chris@14 207 return FALSE;
Chris@0 208 }
Chris@0 209
Chris@0 210 /**
Chris@0 211 * {@inheritdoc}
Chris@0 212 */
Chris@0 213 public function addListener($event_name, $listener, $priority = 0) {
Chris@0 214 $this->listeners[$event_name][$priority][] = ['callable' => $listener];
Chris@0 215 $this->unsorted[$event_name] = TRUE;
Chris@0 216 }
Chris@0 217
Chris@0 218 /**
Chris@0 219 * {@inheritdoc}
Chris@0 220 */
Chris@0 221 public function removeListener($event_name, $listener) {
Chris@0 222 if (!isset($this->listeners[$event_name])) {
Chris@0 223 return;
Chris@0 224 }
Chris@0 225
Chris@0 226 foreach ($this->listeners[$event_name] as $priority => $definitions) {
Chris@0 227 foreach ($definitions as $key => $definition) {
Chris@0 228 if (!isset($definition['callable'])) {
Chris@0 229 if (!$this->container->initialized($definition['service'][0])) {
Chris@0 230 continue;
Chris@0 231 }
Chris@0 232 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
Chris@0 233 }
Chris@0 234
Chris@14 235 if (is_array($definition['callable']) && isset($definition['callable'][0]) && $definition['callable'][0] instanceof \Closure && !$listener instanceof \Closure) {
Chris@14 236 $definition['callable'][0] = $definition['callable'][0]();
Chris@14 237 }
Chris@14 238
Chris@14 239 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 240 $listener[0] = $listener[0]();
Chris@14 241 }
Chris@0 242 if ($definition['callable'] === $listener) {
Chris@14 243 unset($definitions[$key]);
Chris@0 244 }
Chris@0 245 }
Chris@14 246 if ($definitions) {
Chris@14 247 $this->listeners[$event_name][$priority] = $definitions;
Chris@14 248 }
Chris@14 249 else {
Chris@14 250 unset($this->listeners[$event_name][$priority]);
Chris@14 251 }
Chris@0 252 }
Chris@0 253 }
Chris@0 254
Chris@0 255 /**
Chris@0 256 * {@inheritdoc}
Chris@0 257 */
Chris@0 258 public function addSubscriber(EventSubscriberInterface $subscriber) {
Chris@0 259 foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
Chris@0 260 if (is_string($params)) {
Chris@0 261 $this->addListener($event_name, [$subscriber, $params]);
Chris@0 262 }
Chris@0 263 elseif (is_string($params[0])) {
Chris@0 264 $this->addListener($event_name, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
Chris@0 265 }
Chris@0 266 else {
Chris@0 267 foreach ($params as $listener) {
Chris@0 268 $this->addListener($event_name, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
Chris@0 269 }
Chris@0 270 }
Chris@0 271 }
Chris@0 272 }
Chris@0 273
Chris@0 274 /**
Chris@0 275 * {@inheritdoc}
Chris@0 276 */
Chris@0 277 public function removeSubscriber(EventSubscriberInterface $subscriber) {
Chris@0 278 foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
Chris@0 279 if (is_array($params) && is_array($params[0])) {
Chris@0 280 foreach ($params as $listener) {
Chris@0 281 $this->removeListener($event_name, [$subscriber, $listener[0]]);
Chris@0 282 }
Chris@0 283 }
Chris@0 284 else {
Chris@0 285 $this->removeListener($event_name, [$subscriber, is_string($params) ? $params : $params[0]]);
Chris@0 286 }
Chris@0 287 }
Chris@0 288 }
Chris@0 289
Chris@0 290 }