/src/Symfony/Bundle/FrameworkBundle/Debug/TraceableEventDispatcher.php

https://github.com/Faianca/symfony · PHP · 202 lines · 137 code · 24 blank · 41 comment · 17 complexity · 170ae7635b42faf4e37d716202b57f25 MD5 · raw file

  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bundle\FrameworkBundle\Debug;
  11. use Symfony\Bundle\FrameworkBundle\ContainerAwareEventDispatcher;
  12. use Symfony\Component\HttpKernel\Log\LoggerInterface;
  13. use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcherInterface;
  14. use Symfony\Component\DependencyInjection\ContainerInterface;
  15. use Symfony\Component\EventDispatcher\Event;
  16. /**
  17. * Extends the ContainerAwareEventDispatcher to add some debugging tools.
  18. *
  19. * @author Fabien Potencier <fabien@symfony.com>
  20. */
  21. class TraceableEventDispatcher extends ContainerAwareEventDispatcher implements TraceableEventDispatcherInterface
  22. {
  23. private $logger;
  24. private $called;
  25. /**
  26. * Constructor.
  27. *
  28. * @param ContainerInterface $container A ContainerInterface instance
  29. * @param LoggerInterface $logger A LoggerInterface instance
  30. */
  31. public function __construct(ContainerInterface $container, LoggerInterface $logger = null)
  32. {
  33. parent::__construct($container);
  34. $this->logger = $logger;
  35. $this->called = array();
  36. }
  37. /**
  38. * {@inheritDoc}
  39. *
  40. * @throws \RuntimeException if the listener method is not callable
  41. */
  42. public function addListener($eventName, $listener, $priority = 0)
  43. {
  44. if (!is_callable($listener)) {
  45. if (is_string($listener)) {
  46. $typeDefinition = '[string] '.$listener;
  47. } elseif (is_array($listener)) {
  48. $typeDefinition = '[array] '.(is_object($listener[0]) ? get_class($listener[0]) : $listener[0]).'::'.$listener[1];
  49. } elseif (is_object($listener)) {
  50. $typeDefinition = '[object] '.get_class($listener);
  51. } else {
  52. $typeDefinition = '[?] '.var_export($listener, true);
  53. }
  54. throw new \RuntimeException(sprintf('The given callback (%s) for event "%s" is not callable.', $typeDefinition, $eventName));
  55. }
  56. parent::addListener($eventName, $listener, $priority);
  57. }
  58. /**
  59. * {@inheritDoc}
  60. */
  61. protected function doDispatch($listeners, $eventName, Event $event)
  62. {
  63. foreach ($listeners as $listener) {
  64. $info = $this->getListenerInfo($listener, $eventName);
  65. if (null !== $this->logger) {
  66. $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty']));
  67. }
  68. $this->called[$eventName.'.'.$info['pretty']] = $info;
  69. call_user_func($listener, $event);
  70. if ($event->isPropagationStopped()) {
  71. if (null !== $this->logger) {
  72. $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName));
  73. $skippedListeners = $this->getListeners($eventName);
  74. $skipped = false;
  75. foreach ($skippedListeners as $skippedListener) {
  76. if ($skipped) {
  77. if (is_object($skippedListener)) {
  78. $typeDefinition = get_class($skippedListener);
  79. } elseif (is_array($skippedListener)) {
  80. if (is_object($skippedListener[0])) {
  81. $typeDefinition = get_class($skippedListener[0]);
  82. } else {
  83. $typeDefinition = implode('::', $skippedListener);
  84. }
  85. } else {
  86. $typeDefinition = $skippedListener;
  87. }
  88. $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $typeDefinition, $eventName));
  89. }
  90. if ($skippedListener === $listener) {
  91. $skipped = true;
  92. }
  93. }
  94. }
  95. break;
  96. }
  97. }
  98. }
  99. /**
  100. * {@inheritDoc}
  101. */
  102. public function getCalledListeners()
  103. {
  104. return $this->called;
  105. }
  106. /**
  107. * {@inheritDoc}
  108. */
  109. public function getNotCalledListeners()
  110. {
  111. $notCalled = array();
  112. foreach ($this->getListeners() as $name => $listeners) {
  113. foreach ($listeners as $listener) {
  114. $info = $this->getListenerInfo($listener, $name);
  115. if (!isset($this->called[$name.'.'.$info['pretty']])) {
  116. $notCalled[$name.'.'.$info['pretty']] = $info;
  117. }
  118. }
  119. }
  120. return $notCalled;
  121. }
  122. /**
  123. * Returns information about the listener
  124. *
  125. * @param object $listener The listener
  126. * @param string $eventName The event name
  127. *
  128. * @return array Informations about the listener
  129. */
  130. private function getListenerInfo($listener, $eventName)
  131. {
  132. $info = array('event' => $eventName);
  133. if ($listener instanceof \Closure) {
  134. $info += array(
  135. 'type' => 'Closure',
  136. 'pretty' => 'closure'
  137. );
  138. } elseif (is_string($listener)) {
  139. try {
  140. $r = new \ReflectionFunction($listener);
  141. $file = $r->getFileName();
  142. $line = $r->getStartLine();
  143. } catch (\ReflectionException $e) {
  144. $file = null;
  145. $line = null;
  146. }
  147. $info += array(
  148. 'type' => 'Function',
  149. 'function' => $listener,
  150. 'file' => $file,
  151. 'line' => $line,
  152. 'pretty' => $listener,
  153. );
  154. } elseif (is_array($listener) || (is_object($listener) && is_callable($listener))) {
  155. if (!is_array($listener)) {
  156. $listener = array($listener, '__invoke');
  157. }
  158. $class = get_class($listener[0]);
  159. try {
  160. $r = new \ReflectionMethod($class, $listener[1]);
  161. $file = $r->getFileName();
  162. $line = $r->getStartLine();
  163. } catch (\ReflectionException $e) {
  164. $file = null;
  165. $line = null;
  166. }
  167. $info += array(
  168. 'type' => 'Method',
  169. 'class' => $class,
  170. 'method' => $listener[1],
  171. 'file' => $file,
  172. 'line' => $line,
  173. 'pretty' => $class.'::'.$listener[1],
  174. );
  175. }
  176. return $info;
  177. }
  178. }