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. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
TraceableEventDispatcher.php
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