1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Symfony\Component\EventDispatcher\Debug;
13:
14: use Symfony\Component\EventDispatcher\EventDispatcherInterface;
15: use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16: use Symfony\Component\EventDispatcher\Event;
17: use Symfony\Component\Stopwatch\Stopwatch;
18: use Psr\Log\LoggerInterface;
19:
20: 21: 22: 23: 24: 25: 26:
27: class TraceableEventDispatcher implements TraceableEventDispatcherInterface
28: {
29: protected $logger;
30: protected $stopwatch;
31:
32: private $called;
33: private $dispatcher;
34:
35: 36: 37: 38: 39: 40: 41:
42: public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
43: {
44: $this->dispatcher = $dispatcher;
45: $this->stopwatch = $stopwatch;
46: $this->logger = $logger;
47: $this->called = array();
48: }
49:
50: 51: 52:
53: public function addListener($eventName, $listener, $priority = 0)
54: {
55: $this->dispatcher->addListener($eventName, $listener, $priority);
56: }
57:
58: 59: 60:
61: public function addSubscriber(EventSubscriberInterface $subscriber)
62: {
63: $this->dispatcher->addSubscriber($subscriber);
64: }
65:
66: 67: 68:
69: public function removeListener($eventName, $listener)
70: {
71: return $this->dispatcher->removeListener($eventName, $listener);
72: }
73:
74: 75: 76:
77: public function removeSubscriber(EventSubscriberInterface $subscriber)
78: {
79: return $this->dispatcher->removeSubscriber($subscriber);
80: }
81:
82: 83: 84:
85: public function getListeners($eventName = null)
86: {
87: return $this->dispatcher->getListeners($eventName);
88: }
89:
90: 91: 92:
93: public function hasListeners($eventName = null)
94: {
95: return $this->dispatcher->hasListeners($eventName);
96: }
97:
98: 99: 100:
101: public function dispatch($eventName, Event $event = null)
102: {
103: if (null === $event) {
104: $event = new Event();
105: }
106:
107: $this->preProcess($eventName);
108: $this->preDispatch($eventName, $event);
109:
110: $e = $this->stopwatch->start($eventName, 'section');
111:
112: $this->dispatcher->dispatch($eventName, $event);
113:
114: if ($e->isStarted()) {
115: $e->stop();
116: }
117:
118: $this->postDispatch($eventName, $event);
119: $this->postProcess($eventName);
120:
121: return $event;
122: }
123:
124: 125: 126:
127: public function getCalledListeners()
128: {
129: $called = array();
130: foreach ($this->called as $eventName => $listeners) {
131: foreach ($listeners as $listener) {
132: $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName);
133: $called[$eventName.'.'.$info['pretty']] = $info;
134: }
135: }
136:
137: return $called;
138: }
139:
140: 141: 142:
143: public function getNotCalledListeners()
144: {
145: try {
146: $allListeners = $this->getListeners();
147: } catch (\Exception $e) {
148: if (null !== $this->logger) {
149: $this->logger->info(sprintf('An exception was thrown while getting the uncalled listeners (%s)', $e->getMessage()), array('exception' => $e));
150: }
151:
152:
153: return array();
154: }
155:
156: $notCalled = array();
157: foreach ($allListeners as $eventName => $listeners) {
158: foreach ($listeners as $listener) {
159: $called = false;
160: if (isset($this->called[$eventName])) {
161: foreach ($this->called[$eventName] as $l) {
162: if ($l->getWrappedListener() === $listener) {
163: $called = true;
164:
165: break;
166: }
167: }
168: }
169:
170: if (!$called) {
171: $info = $this->getListenerInfo($listener, $eventName);
172: $notCalled[$eventName.'.'.$info['pretty']] = $info;
173: }
174: }
175: }
176:
177: return $notCalled;
178: }
179:
180: 181: 182: 183: 184: 185: 186: 187:
188: public function __call($method, $arguments)
189: {
190: return call_user_func_array(array($this->dispatcher, $method), $arguments);
191: }
192:
193: 194: 195: 196: 197: 198:
199: protected function preDispatch($eventName, Event $event)
200: {
201: }
202:
203: 204: 205: 206: 207: 208:
209: protected function postDispatch($eventName, Event $event)
210: {
211: }
212:
213: private function preProcess($eventName)
214: {
215: foreach ($this->dispatcher->getListeners($eventName) as $listener) {
216: $this->dispatcher->removeListener($eventName, $listener);
217: $info = $this->getListenerInfo($listener, $eventName);
218: $name = isset($info['class']) ? $info['class'] : $info['type'];
219: $this->dispatcher->addListener($eventName, new WrappedListener($listener, $name, $this->stopwatch, $this));
220: }
221: }
222:
223: private function postProcess($eventName)
224: {
225: $skipped = false;
226: foreach ($this->dispatcher->getListeners($eventName) as $listener) {
227: if (!$listener instanceof WrappedListener) {
228: continue;
229: }
230:
231: $this->dispatcher->removeListener($eventName, $listener);
232: $this->dispatcher->addListener($eventName, $listener->getWrappedListener());
233:
234: $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName);
235: if ($listener->wasCalled()) {
236: if (null !== $this->logger) {
237: $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty']));
238: }
239:
240: if (!isset($this->called[$eventName])) {
241: $this->called[$eventName] = new \SplObjectStorage();
242: }
243:
244: $this->called[$eventName]->attach($listener);
245: }
246:
247: if (null !== $this->logger && $skipped) {
248: $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName));
249: }
250:
251: if ($listener->stoppedPropagation()) {
252: if (null !== $this->logger) {
253: $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName));
254: }
255:
256: $skipped = true;
257: }
258: }
259: }
260:
261: 262: 263: 264: 265: 266: 267: 268:
269: private function getListenerInfo($listener, $eventName)
270: {
271: $info = array(
272: 'event' => $eventName,
273: );
274: if ($listener instanceof \Closure) {
275: $info += array(
276: 'type' => 'Closure',
277: 'pretty' => 'closure',
278: );
279: } elseif (is_string($listener)) {
280: try {
281: $r = new \ReflectionFunction($listener);
282: $file = $r->getFileName();
283: $line = $r->getStartLine();
284: } catch (\ReflectionException $e) {
285: $file = null;
286: $line = null;
287: }
288: $info += array(
289: 'type' => 'Function',
290: 'function' => $listener,
291: 'file' => $file,
292: 'line' => $line,
293: 'pretty' => $listener,
294: );
295: } elseif (is_array($listener) || (is_object($listener) && is_callable($listener))) {
296: if (!is_array($listener)) {
297: $listener = array($listener, '__invoke');
298: }
299: $class = is_object($listener[0]) ? get_class($listener[0]) : $listener[0];
300: try {
301: $r = new \ReflectionMethod($class, $listener[1]);
302: $file = $r->getFileName();
303: $line = $r->getStartLine();
304: } catch (\ReflectionException $e) {
305: $file = null;
306: $line = null;
307: }
308: $info += array(
309: 'type' => 'Method',
310: 'class' => $class,
311: 'method' => $listener[1],
312: 'file' => $file,
313: 'line' => $line,
314: 'pretty' => $class.'::'.$listener[1],
315: );
316: }
317:
318: return $info;
319: }
320: }
321: