annotate vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /*
Chris@0 4 * This file is part of the Symfony package.
Chris@0 5 *
Chris@0 6 * (c) Fabien Potencier <fabien@symfony.com>
Chris@0 7 *
Chris@0 8 * For the full copyright and license information, please view the LICENSE
Chris@0 9 * file that was distributed with this source code.
Chris@0 10 */
Chris@0 11
Chris@0 12 namespace Symfony\Component\EventDispatcher\Debug;
Chris@0 13
Chris@17 14 use Psr\Log\LoggerInterface;
Chris@17 15 use Symfony\Component\EventDispatcher\Event;
Chris@0 16 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
Chris@0 17 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
Chris@0 18 use Symfony\Component\Stopwatch\Stopwatch;
Chris@0 19
Chris@0 20 /**
Chris@0 21 * Collects some data about event listeners.
Chris@0 22 *
Chris@0 23 * This event dispatcher delegates the dispatching to another one.
Chris@0 24 *
Chris@0 25 * @author Fabien Potencier <fabien@symfony.com>
Chris@0 26 */
Chris@0 27 class TraceableEventDispatcher implements TraceableEventDispatcherInterface
Chris@0 28 {
Chris@0 29 protected $logger;
Chris@0 30 protected $stopwatch;
Chris@0 31
Chris@17 32 private $callStack;
Chris@0 33 private $dispatcher;
Chris@0 34 private $wrappedListeners;
Chris@0 35
Chris@0 36 public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
Chris@0 37 {
Chris@0 38 $this->dispatcher = $dispatcher;
Chris@0 39 $this->stopwatch = $stopwatch;
Chris@0 40 $this->logger = $logger;
Chris@17 41 $this->wrappedListeners = [];
Chris@0 42 }
Chris@0 43
Chris@0 44 /**
Chris@0 45 * {@inheritdoc}
Chris@0 46 */
Chris@0 47 public function addListener($eventName, $listener, $priority = 0)
Chris@0 48 {
Chris@0 49 $this->dispatcher->addListener($eventName, $listener, $priority);
Chris@0 50 }
Chris@0 51
Chris@0 52 /**
Chris@0 53 * {@inheritdoc}
Chris@0 54 */
Chris@0 55 public function addSubscriber(EventSubscriberInterface $subscriber)
Chris@0 56 {
Chris@0 57 $this->dispatcher->addSubscriber($subscriber);
Chris@0 58 }
Chris@0 59
Chris@0 60 /**
Chris@0 61 * {@inheritdoc}
Chris@0 62 */
Chris@0 63 public function removeListener($eventName, $listener)
Chris@0 64 {
Chris@0 65 if (isset($this->wrappedListeners[$eventName])) {
Chris@0 66 foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
Chris@0 67 if ($wrappedListener->getWrappedListener() === $listener) {
Chris@0 68 $listener = $wrappedListener;
Chris@0 69 unset($this->wrappedListeners[$eventName][$index]);
Chris@0 70 break;
Chris@0 71 }
Chris@0 72 }
Chris@0 73 }
Chris@0 74
Chris@0 75 return $this->dispatcher->removeListener($eventName, $listener);
Chris@0 76 }
Chris@0 77
Chris@0 78 /**
Chris@0 79 * {@inheritdoc}
Chris@0 80 */
Chris@0 81 public function removeSubscriber(EventSubscriberInterface $subscriber)
Chris@0 82 {
Chris@0 83 return $this->dispatcher->removeSubscriber($subscriber);
Chris@0 84 }
Chris@0 85
Chris@0 86 /**
Chris@0 87 * {@inheritdoc}
Chris@0 88 */
Chris@0 89 public function getListeners($eventName = null)
Chris@0 90 {
Chris@0 91 return $this->dispatcher->getListeners($eventName);
Chris@0 92 }
Chris@0 93
Chris@0 94 /**
Chris@0 95 * {@inheritdoc}
Chris@0 96 */
Chris@0 97 public function getListenerPriority($eventName, $listener)
Chris@0 98 {
Chris@0 99 // we might have wrapped listeners for the event (if called while dispatching)
Chris@0 100 // in that case get the priority by wrapper
Chris@0 101 if (isset($this->wrappedListeners[$eventName])) {
Chris@0 102 foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
Chris@0 103 if ($wrappedListener->getWrappedListener() === $listener) {
Chris@0 104 return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
Chris@0 105 }
Chris@0 106 }
Chris@0 107 }
Chris@0 108
Chris@0 109 return $this->dispatcher->getListenerPriority($eventName, $listener);
Chris@0 110 }
Chris@0 111
Chris@0 112 /**
Chris@0 113 * {@inheritdoc}
Chris@0 114 */
Chris@0 115 public function hasListeners($eventName = null)
Chris@0 116 {
Chris@0 117 return $this->dispatcher->hasListeners($eventName);
Chris@0 118 }
Chris@0 119
Chris@0 120 /**
Chris@0 121 * {@inheritdoc}
Chris@0 122 */
Chris@0 123 public function dispatch($eventName, Event $event = null)
Chris@0 124 {
Chris@17 125 if (null === $this->callStack) {
Chris@17 126 $this->callStack = new \SplObjectStorage();
Chris@17 127 }
Chris@17 128
Chris@0 129 if (null === $event) {
Chris@0 130 $event = new Event();
Chris@0 131 }
Chris@0 132
Chris@0 133 if (null !== $this->logger && $event->isPropagationStopped()) {
Chris@0 134 $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
Chris@0 135 }
Chris@0 136
Chris@0 137 $this->preProcess($eventName);
Chris@17 138 try {
Chris@17 139 $this->preDispatch($eventName, $event);
Chris@17 140 try {
Chris@17 141 $e = $this->stopwatch->start($eventName, 'section');
Chris@17 142 try {
Chris@17 143 $this->dispatcher->dispatch($eventName, $event);
Chris@17 144 } finally {
Chris@17 145 if ($e->isStarted()) {
Chris@17 146 $e->stop();
Chris@17 147 }
Chris@17 148 }
Chris@17 149 } finally {
Chris@17 150 $this->postDispatch($eventName, $event);
Chris@17 151 }
Chris@17 152 } finally {
Chris@17 153 $this->postProcess($eventName);
Chris@0 154 }
Chris@0 155
Chris@0 156 return $event;
Chris@0 157 }
Chris@0 158
Chris@0 159 /**
Chris@0 160 * {@inheritdoc}
Chris@0 161 */
Chris@0 162 public function getCalledListeners()
Chris@0 163 {
Chris@17 164 if (null === $this->callStack) {
Chris@17 165 return [];
Chris@17 166 }
Chris@17 167
Chris@17 168 $called = [];
Chris@17 169 foreach ($this->callStack as $listener) {
Chris@17 170 list($eventName) = $this->callStack->getInfo();
Chris@17 171
Chris@17 172 $called[] = $listener->getInfo($eventName);
Chris@0 173 }
Chris@0 174
Chris@0 175 return $called;
Chris@0 176 }
Chris@0 177
Chris@0 178 /**
Chris@0 179 * {@inheritdoc}
Chris@0 180 */
Chris@0 181 public function getNotCalledListeners()
Chris@0 182 {
Chris@0 183 try {
Chris@0 184 $allListeners = $this->getListeners();
Chris@0 185 } catch (\Exception $e) {
Chris@0 186 if (null !== $this->logger) {
Chris@17 187 $this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);
Chris@0 188 }
Chris@0 189
Chris@0 190 // unable to retrieve the uncalled listeners
Chris@17 191 return [];
Chris@0 192 }
Chris@0 193
Chris@17 194 $notCalled = [];
Chris@0 195 foreach ($allListeners as $eventName => $listeners) {
Chris@0 196 foreach ($listeners as $listener) {
Chris@0 197 $called = false;
Chris@17 198 if (null !== $this->callStack) {
Chris@17 199 foreach ($this->callStack as $calledListener) {
Chris@17 200 if ($calledListener->getWrappedListener() === $listener) {
Chris@0 201 $called = true;
Chris@0 202
Chris@0 203 break;
Chris@0 204 }
Chris@0 205 }
Chris@0 206 }
Chris@0 207
Chris@0 208 if (!$called) {
Chris@0 209 if (!$listener instanceof WrappedListener) {
Chris@0 210 $listener = new WrappedListener($listener, null, $this->stopwatch, $this);
Chris@0 211 }
Chris@17 212 $notCalled[] = $listener->getInfo($eventName);
Chris@0 213 }
Chris@0 214 }
Chris@0 215 }
Chris@0 216
Chris@17 217 uasort($notCalled, [$this, 'sortNotCalledListeners']);
Chris@0 218
Chris@0 219 return $notCalled;
Chris@0 220 }
Chris@0 221
Chris@14 222 public function reset()
Chris@14 223 {
Chris@17 224 $this->callStack = null;
Chris@14 225 }
Chris@14 226
Chris@0 227 /**
Chris@0 228 * Proxies all method calls to the original event dispatcher.
Chris@0 229 *
Chris@0 230 * @param string $method The method name
Chris@0 231 * @param array $arguments The method arguments
Chris@0 232 *
Chris@0 233 * @return mixed
Chris@0 234 */
Chris@0 235 public function __call($method, $arguments)
Chris@0 236 {
Chris@17 237 return \call_user_func_array([$this->dispatcher, $method], $arguments);
Chris@0 238 }
Chris@0 239
Chris@0 240 /**
Chris@0 241 * Called before dispatching the event.
Chris@0 242 *
Chris@0 243 * @param string $eventName The event name
Chris@0 244 * @param Event $event The event
Chris@0 245 */
Chris@0 246 protected function preDispatch($eventName, Event $event)
Chris@0 247 {
Chris@0 248 }
Chris@0 249
Chris@0 250 /**
Chris@0 251 * Called after dispatching the event.
Chris@0 252 *
Chris@0 253 * @param string $eventName The event name
Chris@0 254 * @param Event $event The event
Chris@0 255 */
Chris@0 256 protected function postDispatch($eventName, Event $event)
Chris@0 257 {
Chris@0 258 }
Chris@0 259
Chris@0 260 private function preProcess($eventName)
Chris@0 261 {
Chris@0 262 foreach ($this->dispatcher->getListeners($eventName) as $listener) {
Chris@0 263 $priority = $this->getListenerPriority($eventName, $listener);
Chris@17 264 $wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this);
Chris@0 265 $this->wrappedListeners[$eventName][] = $wrappedListener;
Chris@0 266 $this->dispatcher->removeListener($eventName, $listener);
Chris@0 267 $this->dispatcher->addListener($eventName, $wrappedListener, $priority);
Chris@17 268 $this->callStack->attach($wrappedListener, [$eventName]);
Chris@0 269 }
Chris@0 270 }
Chris@0 271
Chris@0 272 private function postProcess($eventName)
Chris@0 273 {
Chris@0 274 unset($this->wrappedListeners[$eventName]);
Chris@0 275 $skipped = false;
Chris@0 276 foreach ($this->dispatcher->getListeners($eventName) as $listener) {
Chris@0 277 if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
Chris@0 278 continue;
Chris@0 279 }
Chris@0 280 // Unwrap listener
Chris@0 281 $priority = $this->getListenerPriority($eventName, $listener);
Chris@0 282 $this->dispatcher->removeListener($eventName, $listener);
Chris@0 283 $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
Chris@0 284
Chris@0 285 if (null !== $this->logger) {
Chris@17 286 $context = ['event' => $eventName, 'listener' => $listener->getPretty()];
Chris@0 287 }
Chris@0 288
Chris@0 289 if ($listener->wasCalled()) {
Chris@0 290 if (null !== $this->logger) {
Chris@0 291 $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);
Chris@0 292 }
Chris@17 293 } else {
Chris@17 294 $this->callStack->detach($listener);
Chris@0 295 }
Chris@0 296
Chris@0 297 if (null !== $this->logger && $skipped) {
Chris@0 298 $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
Chris@0 299 }
Chris@0 300
Chris@0 301 if ($listener->stoppedPropagation()) {
Chris@0 302 if (null !== $this->logger) {
Chris@0 303 $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);
Chris@0 304 }
Chris@0 305
Chris@0 306 $skipped = true;
Chris@0 307 }
Chris@0 308 }
Chris@0 309 }
Chris@0 310
Chris@17 311 private function sortNotCalledListeners(array $a, array $b)
Chris@0 312 {
Chris@17 313 if (0 !== $cmp = strcmp($a['event'], $b['event'])) {
Chris@17 314 return $cmp;
Chris@17 315 }
Chris@17 316
Chris@17 317 if (\is_int($a['priority']) && !\is_int($b['priority'])) {
Chris@0 318 return 1;
Chris@0 319 }
Chris@0 320
Chris@17 321 if (!\is_int($a['priority']) && \is_int($b['priority'])) {
Chris@0 322 return -1;
Chris@0 323 }
Chris@0 324
Chris@0 325 if ($a['priority'] === $b['priority']) {
Chris@0 326 return 0;
Chris@0 327 }
Chris@0 328
Chris@0 329 if ($a['priority'] > $b['priority']) {
Chris@0 330 return -1;
Chris@0 331 }
Chris@0 332
Chris@0 333 return 1;
Chris@0 334 }
Chris@0 335 }