Verzeichnisstruktur phpBB-3.1.0


Veröffentlicht
27.10.2014

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

TraceableEventDispatcher.php

Zuletzt modifiziert: 09.10.2024, 12:58 - Dateigröße: 15.86 KiB


001  <?php
002   
003  /*
004   * This file is part of the Symfony package.
005   *
006   * (c) Fabien Potencier <fabien@symfony.com>
007   *
008   * For the full copyright and license information, please view the LICENSE
009   * file that was distributed with this source code.
010   */
011   
012  namespace Symfony\Component\HttpKernel\Debug;
013   
014  use Symfony\Component\Stopwatch\Stopwatch;
015  use Symfony\Component\HttpKernel\KernelEvents;
016  use Psr\Log\LoggerInterface;
017  use Symfony\Component\HttpKernel\Profiler\Profile;
018  use Symfony\Component\HttpKernel\Profiler\Profiler;
019  use Symfony\Component\HttpKernel\HttpKernelInterface;
020  use Symfony\Component\EventDispatcher\Event;
021  use Symfony\Component\EventDispatcher\EventDispatcherInterface;
022  use Symfony\Component\EventDispatcher\EventSubscriberInterface;
023  use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface;
024   
025  /**
026   * Collects some data about event listeners.
027   *
028   * This event dispatcher delegates the dispatching to another one.
029   *
030   * @author Fabien Potencier <fabien@symfony.com>
031   */
032  class TraceableEventDispatcher implements EventDispatcherInterface, TraceableEventDispatcherInterface
033  {
034      private $logger;
035      private $called;
036      private $stopwatch;
037      private $profiler;
038      private $dispatcher;
039      private $wrappedListeners;
040      private $firstCalledEvent;
041      private $lastEventId = 0;
042   
043      /**
044       * Constructor.
045       *
046       * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
047       * @param Stopwatch                $stopwatch  A Stopwatch instance
048       * @param LoggerInterface          $logger     A LoggerInterface instance
049       */
050      public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
051      {
052          $this->dispatcher = $dispatcher;
053          $this->stopwatch = $stopwatch;
054          $this->logger = $logger;
055          $this->called = array();
056          $this->wrappedListeners = array();
057          $this->firstCalledEvent = array();
058      }
059   
060      /**
061       * Sets the profiler.
062       *
063       * @param Profiler|null $profiler A Profiler instance
064       */
065      public function setProfiler(Profiler $profiler = null)
066      {
067          $this->profiler = $profiler;
068      }
069   
070      /**
071       * {@inheritdoc}
072       */
073      public function addListener($eventName, $listener, $priority = 0)
074      {
075          $this->dispatcher->addListener($eventName, $listener, $priority);
076      }
077   
078      /**
079       * {@inheritdoc}
080       */
081      public function addSubscriber(EventSubscriberInterface $subscriber)
082      {
083          $this->dispatcher->addSubscriber($subscriber);
084      }
085   
086      /**
087       * {@inheritdoc}
088       */
089      public function removeListener($eventName, $listener)
090      {
091          return $this->dispatcher->removeListener($eventName, $listener);
092      }
093   
094      /**
095       * {@inheritdoc}
096       */
097      public function removeSubscriber(EventSubscriberInterface $subscriber)
098      {
099          return $this->dispatcher->removeSubscriber($subscriber);
100      }
101   
102      /**
103       * {@inheritdoc}
104       */
105      public function getListeners($eventName = null)
106      {
107          return $this->dispatcher->getListeners($eventName);
108      }
109   
110      /**
111       * {@inheritdoc}
112       */
113      public function hasListeners($eventName = null)
114      {
115          return $this->dispatcher->hasListeners($eventName);
116      }
117   
118      /**
119       * {@inheritdoc}
120       */
121      public function dispatch($eventName, Event $event = null)
122      {
123          if (null === $event) {
124              $event = new Event();
125          }
126   
127          $eventId = ++$this->lastEventId;
128   
129          $this->preDispatch($eventName, $eventId, $event);
130   
131          $e = $this->stopwatch->start($eventName, 'section');
132   
133          $this->firstCalledEvent[$eventName] = $this->stopwatch->start($eventName.'.loading', 'event_listener_loading');
134   
135          if (!$this->dispatcher->hasListeners($eventName)) {
136              $this->firstCalledEvent[$eventName]->stop();
137          }
138   
139          $this->dispatcher->dispatch($eventName, $event);
140   
141          unset($this->firstCalledEvent[$eventName]);
142   
143          $e->stop();
144   
145          $this->postDispatch($eventName, $eventId, $event);
146   
147          return $event;
148      }
149   
150      /**
151       * {@inheritdoc}
152       */
153      public function getCalledListeners()
154      {
155          return $this->called;
156      }
157   
158      /**
159       * {@inheritdoc}
160       */
161      public function getNotCalledListeners()
162      {
163          try {
164              $allListeners = $this->getListeners();
165          } catch (\Exception $e) {
166              if (null !== $this->logger) {
167                  $this->logger->info(sprintf('An exception was thrown while getting the uncalled listeners (%s)', $e->getMessage()), array('exception' => $e));
168              }
169   
170              // unable to retrieve the uncalled listeners
171              return array();
172          }
173   
174          $notCalled = array();
175          foreach ($allListeners as $name => $listeners) {
176              foreach ($listeners as $listener) {
177                  $info = $this->getListenerInfo($listener, null, $name);
178                  if (!isset($this->called[$name.'.'.$info['pretty']])) {
179                      $notCalled[$name.'.'.$info['pretty']] = $info;
180                  }
181              }
182          }
183   
184          return $notCalled;
185      }
186   
187      /**
188       * Proxies all method calls to the original event dispatcher.
189       *
190       * @param string $method    The method name
191       * @param array  $arguments The method arguments
192       *
193       * @return mixed
194       */
195      public function __call($method, $arguments)
196      {
197          return call_user_func_array(array($this->dispatcher, $method), $arguments);
198      }
199   
200      /**
201       * This is a private method and must not be used.
202       *
203       * This method is public because it is used in a closure.
204       * Whenever Symfony will require PHP 5.4, this could be changed
205       * to a proper private method.
206       */
207      public function logSkippedListeners($eventName, $eventId, Event $event, $listener)
208      {
209          if (null === $this->logger) {
210              return;
211          }
212   
213          $info = $this->getListenerInfo($listener, $eventId, $eventName);
214   
215          $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName));
216   
217          $skippedListeners = $this->getListeners($eventName);
218          $skipped = false;
219   
220          foreach ($skippedListeners as $skippedListener) {
221              $skippedListener = $this->unwrapListener($skippedListener, $eventId);
222   
223              if ($skipped) {
224                  $info = $this->getListenerInfo($skippedListener, $eventId, $eventName);
225                  $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName));
226              }
227   
228              if ($skippedListener === $listener) {
229                  $skipped = true;
230              }
231          }
232      }
233   
234      /**
235       * This is a private method.
236       *
237       * This method is public because it is used in a closure.
238       * Whenever Symfony will require PHP 5.4, this could be changed
239       * to a proper private method.
240       */
241      public function preListenerCall($eventName, $eventId, $listener)
242      {
243          // is it the first called listener?
244          if (isset($this->firstCalledEvent[$eventName])) {
245              $this->firstCalledEvent[$eventName]->stop();
246   
247              unset($this->firstCalledEvent[$eventName]);
248          }
249   
250          $info = $this->getListenerInfo($listener, $eventId, $eventName);
251   
252          if (null !== $this->logger) {
253              $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty']));
254          }
255   
256          $this->called[$eventName.'.'.$info['pretty']] = $info;
257   
258          return $this->stopwatch->start(isset($info['class']) ? $info['class'] : $info['type'], 'event_listener');
259      }
260   
261      /**
262       * Returns information about the listener
263       *
264       * @param object   $listener  The listener
265       * @param int|null $eventId   The event id
266       * @param string   $eventName The event name
267       *
268       * @return array Information about the listener
269       */
270      private function getListenerInfo($listener, $eventId, $eventName)
271      {
272          $listener = $this->unwrapListener($listener, $eventId);
273   
274          $info = array(
275              'event' => $eventName,
276          );
277          if ($listener instanceof \Closure) {
278              $info += array(
279                  'type' => 'Closure',
280                  'pretty' => 'closure',
281              );
282          } elseif (is_string($listener)) {
283              try {
284                  $r = new \ReflectionFunction($listener);
285                  $file = $r->getFileName();
286                  $line = $r->getStartLine();
287              } catch (\ReflectionException $e) {
288                  $file = null;
289                  $line = null;
290              }
291              $info += array(
292                  'type'  => 'Function',
293                  'function' => $listener,
294                  'file'  => $file,
295                  'line'  => $line,
296                  'pretty' => $listener,
297              );
298          } elseif (is_array($listener) || (is_object($listener) && is_callable($listener))) {
299              if (!is_array($listener)) {
300                  $listener = array($listener, '__invoke');
301              }
302              $class = is_object($listener[0]) ? get_class($listener[0]) : $listener[0];
303              try {
304                  $r = new \ReflectionMethod($class, $listener[1]);
305                  $file = $r->getFileName();
306                  $line = $r->getStartLine();
307              } catch (\ReflectionException $e) {
308                  $file = null;
309                  $line = null;
310              }
311              $info += array(
312                  'type'  => 'Method',
313                  'class' => $class,
314                  'method' => $listener[1],
315                  'file'  => $file,
316                  'line'  => $line,
317                  'pretty' => $class.'::'.$listener[1],
318              );
319          }
320   
321          return $info;
322      }
323   
324      /**
325       * Updates the stopwatch data in the profile hierarchy.
326       *
327       * @param string  $token          Profile token
328       * @param bool    $updateChildren Whether to update the children altogether
329       */
330      private function updateProfiles($token, $updateChildren)
331      {
332          if (!$this->profiler || !$profile = $this->profiler->loadProfile($token)) {
333              return;
334          }
335   
336          $this->saveInfoInProfile($profile, $updateChildren);
337      }
338   
339      /**
340       * Update the profiles with the timing and events information and saves them.
341       *
342       * @param Profile $profile        The root profile
343       * @param bool    $updateChildren Whether to update the children altogether
344       */
345      private function saveInfoInProfile(Profile $profile, $updateChildren)
346      {
347          try {
348              $collector = $profile->getCollector('memory');
349              $collector->updateMemoryUsage();
350          } catch (\InvalidArgumentException $e) {
351          }
352   
353          try {
354              $collector = $profile->getCollector('time');
355              $collector->setEvents($this->stopwatch->getSectionEvents($profile->getToken()));
356          } catch (\InvalidArgumentException $e) {
357          }
358   
359          try {
360              $collector = $profile->getCollector('events');
361              $collector->setCalledListeners($this->getCalledListeners());
362              $collector->setNotCalledListeners($this->getNotCalledListeners());
363          } catch (\InvalidArgumentException $e) {
364          }
365   
366          $this->profiler->saveProfile($profile);
367   
368          if ($updateChildren) {
369              foreach ($profile->getChildren() as $child) {
370                  $this->saveInfoInProfile($child, true);
371              }
372          }
373      }
374   
375      private function preDispatch($eventName, $eventId, Event $event)
376      {
377          // wrap all listeners before they are called
378          $this->wrappedListeners[$eventId] = new \SplObjectStorage();
379   
380          $listeners = $this->dispatcher->getListeners($eventName);
381   
382          foreach ($listeners as $listener) {
383              $this->dispatcher->removeListener($eventName, $listener);
384              $wrapped = $this->wrapListener($eventName, $eventId, $listener);
385              $this->wrappedListeners[$eventId][$wrapped] = $listener;
386              $this->dispatcher->addListener($eventName, $wrapped);
387          }
388   
389          switch ($eventName) {
390              case KernelEvents::REQUEST:
391                  $this->stopwatch->openSection();
392                  break;
393              case KernelEvents::VIEW:
394              case KernelEvents::RESPONSE:
395                  // stop only if a controller has been executed
396                  if ($this->stopwatch->isStarted('controller')) {
397                      $this->stopwatch->stop('controller');
398                  }
399                  break;
400              case KernelEvents::TERMINATE:
401                  $token = $event->getResponse()->headers->get('X-Debug-Token');
402                  // There is a very special case when using builtin AppCache class as kernel wrapper, in the case
403                  // of an ESI request leading to a `stale` response [B]  inside a `fresh` cached response [A].
404                  // In this case, `$token` contains the [B] debug token, but the  open `stopwatch` section ID
405                  // is equal to the [A] debug token. Trying to reopen section with the [B] token throws an exception
406                  // which must be caught.
407                  try {
408                      $this->stopwatch->openSection($token);
409                  } catch (\LogicException $e) {
410                  }
411                  break;
412          }
413      }
414   
415      private function postDispatch($eventName, $eventId, Event $event)
416      {
417          switch ($eventName) {
418              case KernelEvents::CONTROLLER:
419                  $this->stopwatch->start('controller', 'section');
420                  break;
421              case KernelEvents::RESPONSE:
422                  $token = $event->getResponse()->headers->get('X-Debug-Token');
423                  $this->stopwatch->stopSection($token);
424                  if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
425                      // The profiles can only be updated once they have been created
426                      // that is after the 'kernel.response' event of the main request
427                      $this->updateProfiles($token, true);
428                  }
429                  break;
430              case KernelEvents::TERMINATE:
431                  $token = $event->getResponse()->headers->get('X-Debug-Token');
432                  // In the special case described in the `preDispatch` method above, the `$token` section
433                  // does not exist, then closing it throws an exception which must be caught.
434                  try {
435                      $this->stopwatch->stopSection($token);
436                  } catch (\LogicException $e) {
437                  }
438                  // The children profiles have been updated by the previous 'kernel.response'
439                  // event. Only the root profile need to be updated with the 'kernel.terminate'
440                  // timing information.
441                  $this->updateProfiles($token, false);
442                  break;
443          }
444   
445          foreach ($this->wrappedListeners[$eventId] as $wrapped) {
446              $this->dispatcher->removeListener($eventName, $wrapped);
447              $this->dispatcher->addListener($eventName, $this->wrappedListeners[$eventId][$wrapped]);
448          }
449   
450          unset($this->wrappedListeners[$eventId]);
451      }
452   
453      private function wrapListener($eventName, $eventId, $listener)
454      {
455          $self = $this;
456   
457          return function (Event $event) use ($self, $eventName, $eventId, $listener) {
458              $e = $self->preListenerCall($eventName, $eventId, $listener);
459   
460              call_user_func($listener, $event);
461   
462              $e->stop();
463   
464              if ($event->isPropagationStopped()) {
465                  $self->logSkippedListeners($eventName, $eventId, $event, $listener);
466              }
467          };
468      }
469   
470      private function unwrapListener($listener, $eventId)
471      {
472          // get the original listener
473          if (is_object($listener)) {
474              if (null === $eventId) {
475                  foreach (array_keys($this->wrappedListeners) as $eventId) {
476                      if (isset($this->wrappedListeners[$eventId][$listener])) {
477                          return $this->wrappedListeners[$eventId][$listener];
478                      }
479                  }
480              } elseif (isset($this->wrappedListeners[$eventId][$listener])) {
481                  return $this->wrappedListeners[$eventId][$listener];
482              }
483          }
484   
485          return $listener;
486      }
487  }
488