1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\HttpKernel\Debug;
13
14use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher;
15use Symfony\Component\HttpKernel\KernelEvents;
16
17/**
18 * Collects some data about event listeners.
19 *
20 * This event dispatcher delegates the dispatching to another one.
21 *
22 * @author Fabien Potencier <fabien@symfony.com>
23 */
24class TraceableEventDispatcher extends BaseTraceableEventDispatcher
25{
26    /**
27     * {@inheritdoc}
28     */
29    protected function beforeDispatch(string $eventName, object $event)
30    {
31        switch ($eventName) {
32            case KernelEvents::REQUEST:
33                $this->stopwatch->openSection();
34                break;
35            case KernelEvents::VIEW:
36            case KernelEvents::RESPONSE:
37                // stop only if a controller has been executed
38                if ($this->stopwatch->isStarted('controller')) {
39                    $this->stopwatch->stop('controller');
40                }
41                break;
42            case KernelEvents::TERMINATE:
43                $token = $event->getResponse()->headers->get('X-Debug-Token');
44                if (null === $token) {
45                    break;
46                }
47                // There is a very special case when using built-in AppCache class as kernel wrapper, in the case
48                // of an ESI request leading to a `stale` response [B]  inside a `fresh` cached response [A].
49                // In this case, `$token` contains the [B] debug token, but the  open `stopwatch` section ID
50                // is equal to the [A] debug token. Trying to reopen section with the [B] token throws an exception
51                // which must be caught.
52                try {
53                    $this->stopwatch->openSection($token);
54                } catch (\LogicException $e) {
55                }
56                break;
57        }
58    }
59
60    /**
61     * {@inheritdoc}
62     */
63    protected function afterDispatch(string $eventName, object $event)
64    {
65        switch ($eventName) {
66            case KernelEvents::CONTROLLER_ARGUMENTS:
67                $this->stopwatch->start('controller', 'section');
68                break;
69            case KernelEvents::RESPONSE:
70                $token = $event->getResponse()->headers->get('X-Debug-Token');
71                if (null === $token) {
72                    break;
73                }
74                $this->stopwatch->stopSection($token);
75                break;
76            case KernelEvents::TERMINATE:
77                // In the special case described in the `preDispatch` method above, the `$token` section
78                // does not exist, then closing it throws an exception which must be caught.
79                $token = $event->getResponse()->headers->get('X-Debug-Token');
80                if (null === $token) {
81                    break;
82                }
83                try {
84                    $this->stopwatch->stopSection($token);
85                } catch (\LogicException $e) {
86                }
87                break;
88        }
89    }
90}
91