TraceableEventDispatcher.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <?php
  2. namespace Drupal\webprofiler\EventDispatcher;
  3. use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
  4. use Drupal\webprofiler\Stopwatch;
  5. use Symfony\Component\DependencyInjection\ContainerInterface;
  6. use Symfony\Component\EventDispatcher\Event;
  7. use Symfony\Component\HttpKernel\KernelEvents;
  8. /**
  9. * Class TraceableEventDispatcher
  10. */
  11. class TraceableEventDispatcher extends ContainerAwareEventDispatcher implements EventDispatcherTraceableInterface {
  12. /**
  13. * @var \Drupal\webprofiler\Stopwatch
  14. * The stopwatch service.
  15. */
  16. protected $stopwatch;
  17. /**
  18. * @var array
  19. */
  20. protected $calledListeners;
  21. /**
  22. * @var array
  23. */
  24. protected $notCalledListeners;
  25. /**
  26. * {@inheritdoc}
  27. */
  28. public function __construct(ContainerInterface $container, array $listeners = []) {
  29. parent::__construct($container, $listeners);
  30. $this->notCalledListeners = $listeners;
  31. }
  32. /**
  33. * {@inheritdoc}
  34. */
  35. public function addListener($event_name, $listener, $priority = 0) {
  36. parent::addListener($event_name, $listener, $priority);
  37. $this->notCalledListeners[$event_name][$priority][] = ['callable' => $listener];
  38. }
  39. /**
  40. * {@inheritdoc}
  41. */
  42. public function dispatch($event_name, Event $event = NULL) {
  43. if ($event === NULL) {
  44. $event = new Event();
  45. }
  46. $this->preDispatch($event_name, $event);
  47. $e = $this->stopwatch->start($event_name, 'section');
  48. if (isset($this->listeners[$event_name])) {
  49. // Sort listeners if necessary.
  50. if (isset($this->unsorted[$event_name])) {
  51. krsort($this->listeners[$event_name]);
  52. unset($this->unsorted[$event_name]);
  53. }
  54. // Invoke listeners and resolve callables if necessary.
  55. foreach ($this->listeners[$event_name] as $priority => &$definitions) {
  56. foreach ($definitions as $key => &$definition) {
  57. if (!isset($definition['callable'])) {
  58. $definition['callable'] = [
  59. $this->container->get($definition['service'][0]),
  60. $definition['service'][1],
  61. ];
  62. }
  63. $definition['callable']($event, $event_name, $this);
  64. $this->addCalledListener($definition, $event_name, $priority);
  65. if ($event->isPropagationStopped()) {
  66. return $event;
  67. }
  68. }
  69. }
  70. }
  71. if ($e->isStarted()) {
  72. $e->stop();
  73. }
  74. $this->postDispatch($event_name, $event);
  75. return $event;
  76. }
  77. /**
  78. * {@inheritdoc}
  79. */
  80. public function getCalledListeners() {
  81. return $this->calledListeners;
  82. }
  83. /**
  84. * {@inheritdoc}
  85. */
  86. public function getNotCalledListeners() {
  87. return $this->notCalledListeners;
  88. }
  89. /**
  90. * @param \Drupal\webprofiler\Stopwatch $stopwatch
  91. */
  92. public function setStopwatch(Stopwatch $stopwatch) {
  93. $this->stopwatch = $stopwatch;
  94. }
  95. /**
  96. * Called before dispatching the event.
  97. *
  98. * @param string $eventName The event name
  99. * @param Event $event The event
  100. */
  101. protected function preDispatch($eventName, Event $event) {
  102. switch ($eventName) {
  103. case KernelEvents::VIEW:
  104. case KernelEvents::RESPONSE:
  105. // stop only if a controller has been executed
  106. if ($this->stopwatch->isStarted('controller')) {
  107. $this->stopwatch->stop('controller');
  108. }
  109. break;
  110. }
  111. }
  112. /**
  113. * Called after dispatching the event.
  114. *
  115. * @param string $eventName The event name
  116. * @param Event $event The event
  117. */
  118. protected function postDispatch($eventName, Event $event) {
  119. switch ($eventName) {
  120. case KernelEvents::CONTROLLER:
  121. $this->stopwatch->start('controller', 'section');
  122. break;
  123. case KernelEvents::RESPONSE:
  124. $token = $event->getResponse()->headers->get('X-Debug-Token');
  125. try {
  126. $this->stopwatch->stopSection($token);
  127. }
  128. catch (\LogicException $e) {
  129. }
  130. break;
  131. case KernelEvents::TERMINATE:
  132. // In the special case described in the `preDispatch` method above, the `$token` section
  133. // does not exist, then closing it throws an exception which must be caught.
  134. $token = $event->getResponse()->headers->get('X-Debug-Token');
  135. try {
  136. $this->stopwatch->stopSection($token);
  137. }
  138. catch (\LogicException $e) {
  139. }
  140. break;
  141. }
  142. }
  143. /**
  144. * @param $definition
  145. * @param $event_name
  146. * @param $priority
  147. */
  148. private function addCalledListener($definition, $event_name, $priority) {
  149. if ($this->isClosure($definition['callable'])) {
  150. $this->calledListeners[$event_name][$priority][] = [
  151. 'class' => 'Closure',
  152. 'method' => '',
  153. ];
  154. }
  155. else {
  156. $this->calledListeners[$event_name][$priority][] = [
  157. 'class' => get_class($definition['callable'][0]),
  158. 'method' => $definition['callable'][1],
  159. ];
  160. }
  161. foreach ($this->notCalledListeners[$event_name][$priority] as $key => $listener) {
  162. if (isset($listener['service'])) {
  163. if ($listener['service'][0] == $definition['service'][0] && $listener['service'][1] == $definition['service'][1]) {
  164. unset($this->notCalledListeners[$event_name][$priority][$key]);
  165. }
  166. }
  167. else {
  168. if ($this->isClosure($listener['callable'])) {
  169. if (is_callable($listener['callable'], TRUE, $listenerCallableName) && is_callable($definition['callable'], TRUE, $definitionCallableName)) {
  170. if ($listenerCallableName == $definitionCallableName) {
  171. unset($this->notCalledListeners[$event_name][$priority][$key]);
  172. }
  173. }
  174. }
  175. else {
  176. if (get_class($listener['callable'][0]) == get_class($definition['callable'][0]) && $listener['callable'][1] == $definition['callable'][1]) {
  177. unset($this->notCalledListeners[$event_name][$priority][$key]);
  178. }
  179. }
  180. }
  181. }
  182. }
  183. private function isClosure($t) {
  184. return is_object($t) && ($t instanceof \Closure);
  185. }
  186. }