annotate core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 1fec387a4317
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@0 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@0 107
Chris@0 108 $definition['callable']($event, $event_name, $this);
Chris@0 109 if ($event->isPropagationStopped()) {
Chris@0 110 return $event;
Chris@0 111 }
Chris@0 112 }
Chris@0 113 }
Chris@0 114 }
Chris@0 115
Chris@0 116 return $event;
Chris@0 117 }
Chris@0 118
Chris@0 119 /**
Chris@0 120 * {@inheritdoc}
Chris@0 121 */
Chris@0 122 public function getListeners($event_name = NULL) {
Chris@0 123 $result = [];
Chris@0 124
Chris@0 125 if ($event_name === NULL) {
Chris@0 126 // If event name was omitted, collect all listeners of all events.
Chris@0 127 foreach (array_keys($this->listeners) as $event_name) {
Chris@0 128 $listeners = $this->getListeners($event_name);
Chris@0 129 if (!empty($listeners)) {
Chris@0 130 $result[$event_name] = $listeners;
Chris@0 131 }
Chris@0 132 }
Chris@0 133 }
Chris@0 134 elseif (isset($this->listeners[$event_name])) {
Chris@0 135 // Sort listeners if necessary.
Chris@0 136 if (isset($this->unsorted[$event_name])) {
Chris@0 137 krsort($this->listeners[$event_name]);
Chris@0 138 unset($this->unsorted[$event_name]);
Chris@0 139 }
Chris@0 140
Chris@0 141 // Collect listeners and resolve callables if necessary.
Chris@0 142 foreach ($this->listeners[$event_name] as $priority => &$definitions) {
Chris@0 143 foreach ($definitions as $key => &$definition) {
Chris@0 144 if (!isset($definition['callable'])) {
Chris@0 145 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
Chris@0 146 }
Chris@0 147
Chris@0 148 $result[] = $definition['callable'];
Chris@0 149 }
Chris@0 150 }
Chris@0 151 }
Chris@0 152
Chris@0 153 return $result;
Chris@0 154 }
Chris@0 155
Chris@0 156 /**
Chris@0 157 * {@inheritdoc}
Chris@0 158 */
Chris@0 159 public function getListenerPriority($eventName, $listener) {
Chris@0 160 // Parts copied from \Symfony\Component\EventDispatcher, that's why you see
Chris@0 161 // a yoda condition here.
Chris@0 162 if (!isset($this->listeners[$eventName])) {
Chris@0 163 return;
Chris@0 164 }
Chris@0 165 foreach ($this->listeners[$eventName] as $priority => $listeners) {
Chris@0 166 if (FALSE !== ($key = array_search(['callable' => $listener], $listeners, TRUE))) {
Chris@0 167 return $priority;
Chris@0 168 }
Chris@0 169 }
Chris@0 170 // Resolve service definitions if the listener has not been found so far.
Chris@0 171 foreach ($this->listeners[$eventName] as $priority => &$definitions) {
Chris@0 172 foreach ($definitions as $key => &$definition) {
Chris@0 173 if (!isset($definition['callable'])) {
Chris@0 174 // Once the callable is retrieved we keep it for subsequent method
Chris@0 175 // invocations on this class.
Chris@0 176 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
Chris@0 177 if ($definition['callable'] === $listener) {
Chris@0 178 return $priority;
Chris@0 179 }
Chris@0 180 }
Chris@0 181 }
Chris@0 182 }
Chris@0 183 }
Chris@0 184
Chris@0 185 /**
Chris@0 186 * {@inheritdoc}
Chris@0 187 */
Chris@0 188 public function hasListeners($event_name = NULL) {
Chris@0 189 return (bool) count($this->getListeners($event_name));
Chris@0 190 }
Chris@0 191
Chris@0 192 /**
Chris@0 193 * {@inheritdoc}
Chris@0 194 */
Chris@0 195 public function addListener($event_name, $listener, $priority = 0) {
Chris@0 196 $this->listeners[$event_name][$priority][] = ['callable' => $listener];
Chris@0 197 $this->unsorted[$event_name] = TRUE;
Chris@0 198 }
Chris@0 199
Chris@0 200 /**
Chris@0 201 * {@inheritdoc}
Chris@0 202 */
Chris@0 203 public function removeListener($event_name, $listener) {
Chris@0 204 if (!isset($this->listeners[$event_name])) {
Chris@0 205 return;
Chris@0 206 }
Chris@0 207
Chris@0 208 foreach ($this->listeners[$event_name] as $priority => $definitions) {
Chris@0 209 foreach ($definitions as $key => $definition) {
Chris@0 210 if (!isset($definition['callable'])) {
Chris@0 211 if (!$this->container->initialized($definition['service'][0])) {
Chris@0 212 continue;
Chris@0 213 }
Chris@0 214 $definition['callable'] = [$this->container->get($definition['service'][0]), $definition['service'][1]];
Chris@0 215 }
Chris@0 216
Chris@0 217 if ($definition['callable'] === $listener) {
Chris@0 218 unset($this->listeners[$event_name][$priority][$key]);
Chris@0 219 }
Chris@0 220 }
Chris@0 221 }
Chris@0 222 }
Chris@0 223
Chris@0 224 /**
Chris@0 225 * {@inheritdoc}
Chris@0 226 */
Chris@0 227 public function addSubscriber(EventSubscriberInterface $subscriber) {
Chris@0 228 foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
Chris@0 229 if (is_string($params)) {
Chris@0 230 $this->addListener($event_name, [$subscriber, $params]);
Chris@0 231 }
Chris@0 232 elseif (is_string($params[0])) {
Chris@0 233 $this->addListener($event_name, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
Chris@0 234 }
Chris@0 235 else {
Chris@0 236 foreach ($params as $listener) {
Chris@0 237 $this->addListener($event_name, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
Chris@0 238 }
Chris@0 239 }
Chris@0 240 }
Chris@0 241 }
Chris@0 242
Chris@0 243 /**
Chris@0 244 * {@inheritdoc}
Chris@0 245 */
Chris@0 246 public function removeSubscriber(EventSubscriberInterface $subscriber) {
Chris@0 247 foreach ($subscriber->getSubscribedEvents() as $event_name => $params) {
Chris@0 248 if (is_array($params) && is_array($params[0])) {
Chris@0 249 foreach ($params as $listener) {
Chris@0 250 $this->removeListener($event_name, [$subscriber, $listener[0]]);
Chris@0 251 }
Chris@0 252 }
Chris@0 253 else {
Chris@0 254 $this->removeListener($event_name, [$subscriber, is_string($params) ? $params : $params[0]]);
Chris@0 255 }
Chris@0 256 }
Chris@0 257 }
Chris@0 258
Chris@0 259 }