TraceableEventDispatcher.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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. private $container;
  26. /**
  27. * Constructor.
  28. *
  29. * @param ContainerInterface $container A ContainerInterface instance
  30. * @param LoggerInterface $logger A LoggerInterface instance
  31. */
  32. public function __construct(ContainerInterface $container, LoggerInterface $logger = null)
  33. {
  34. parent::__construct($container);
  35. $this->container = $container;
  36. $this->logger = $logger;
  37. $this->called = array();
  38. }
  39. /**
  40. * {@inheritDoc}
  41. *
  42. * @throws \RuntimeException if the listener method is not callable
  43. */
  44. public function addListener($eventNames, $listener, $priority = 0)
  45. {
  46. if (!$listener instanceof \Closure) {
  47. foreach ((array) $eventNames as $method) {
  48. if (!is_callable(array($listener, $method))) {
  49. $msg = sprintf('The event method "%s()" is not callable on the class "%s".', $method, get_class($listener));
  50. if (null !== $this->logger) {
  51. $this->logger->err($msg);
  52. }
  53. throw new \RuntimeException($msg);
  54. }
  55. }
  56. }
  57. parent::addListener($eventNames, $listener, $priority);
  58. }
  59. /**
  60. * {@inheritDoc}
  61. */
  62. protected function triggerListener($listener, $eventName, Event $event)
  63. {
  64. parent::triggerListener($listener, $eventName, $event);
  65. if (null !== $this->logger) {
  66. $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, get_class($listener)));
  67. }
  68. $this->called[$eventName.'.'.get_class($listener)] = $this->getListenerInfo($listener, $eventName);
  69. if ($event->isPropagationStopped() && null !== $this->logger) {
  70. $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', get_class($listener), $eventName));
  71. $skippedListeners = $this->getListeners($eventName);
  72. $skipped = false;
  73. foreach ($skippedListeners as $skippedListener) {
  74. if ($skipped) {
  75. $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', get_class($skippedListener), $eventName));
  76. }
  77. if ($skippedListener === $listener) {
  78. $skipped = true;
  79. }
  80. }
  81. }
  82. }
  83. /**
  84. * {@inheritDoc}
  85. */
  86. public function getCalledListeners()
  87. {
  88. return $this->called;
  89. }
  90. /**
  91. * {@inheritDoc}
  92. */
  93. public function getNotCalledListeners()
  94. {
  95. $notCalled = array();
  96. foreach (array_keys($this->getListeners()) as $name) {
  97. foreach ($this->getListeners($name) as $listener) {
  98. if (!isset($this->called[$name.'.'.get_class($listener)])) {
  99. $notCalled[$name.'.'.get_class($listener)] = $this->getListenerInfo($listener, $name);
  100. }
  101. }
  102. }
  103. return $notCalled;
  104. }
  105. /**
  106. * Returns information about the listener
  107. *
  108. * @param object $listener The listener
  109. * @param string $eventName The event name
  110. *
  111. * @return array Informations about the listener
  112. */
  113. private function getListenerInfo($listener, $eventName)
  114. {
  115. $info = array('event' => $eventName);
  116. if ($listener instanceof \Closure) {
  117. $info += array('type' => 'Closure');
  118. } else {
  119. $class = get_class($listener);
  120. try {
  121. $r = new \ReflectionMethod($class, $eventName);
  122. $file = $r->getFileName();
  123. $line = $r->getStartLine();
  124. } catch (\ReflectionException $e) {
  125. $file = null;
  126. $line = null;
  127. }
  128. $info += array(
  129. 'type' => 'Method',
  130. 'class' => $class,
  131. 'file' => $file,
  132. 'line' => $line
  133. );
  134. }
  135. return $info;
  136. }
  137. }