| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 | <?php/* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */namespace Symfony\Component\EventDispatcher\Debug;use Psr\Log\LoggerInterface;use Symfony\Component\EventDispatcher\Event;use Symfony\Component\EventDispatcher\EventDispatcherInterface;use Symfony\Component\EventDispatcher\EventSubscriberInterface;use Symfony\Component\Stopwatch\Stopwatch;/** * Collects some data about event listeners. * * This event dispatcher delegates the dispatching to another one. * * @author Fabien Potencier <fabien@symfony.com> */class TraceableEventDispatcher implements TraceableEventDispatcherInterface{    protected $logger;    protected $stopwatch;    private $callStack;    private $dispatcher;    private $wrappedListeners;    public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)    {        $this->dispatcher = $dispatcher;        $this->stopwatch = $stopwatch;        $this->logger = $logger;        $this->wrappedListeners = [];    }    /**     * {@inheritdoc}     */    public function addListener($eventName, $listener, $priority = 0)    {        $this->dispatcher->addListener($eventName, $listener, $priority);    }    /**     * {@inheritdoc}     */    public function addSubscriber(EventSubscriberInterface $subscriber)    {        $this->dispatcher->addSubscriber($subscriber);    }    /**     * {@inheritdoc}     */    public function removeListener($eventName, $listener)    {        if (isset($this->wrappedListeners[$eventName])) {            foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {                if ($wrappedListener->getWrappedListener() === $listener) {                    $listener = $wrappedListener;                    unset($this->wrappedListeners[$eventName][$index]);                    break;                }            }        }        return $this->dispatcher->removeListener($eventName, $listener);    }    /**     * {@inheritdoc}     */    public function removeSubscriber(EventSubscriberInterface $subscriber)    {        return $this->dispatcher->removeSubscriber($subscriber);    }    /**     * {@inheritdoc}     */    public function getListeners($eventName = null)    {        return $this->dispatcher->getListeners($eventName);    }    /**     * {@inheritdoc}     */    public function getListenerPriority($eventName, $listener)    {        // we might have wrapped listeners for the event (if called while dispatching)        // in that case get the priority by wrapper        if (isset($this->wrappedListeners[$eventName])) {            foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {                if ($wrappedListener->getWrappedListener() === $listener) {                    return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);                }            }        }        return $this->dispatcher->getListenerPriority($eventName, $listener);    }    /**     * {@inheritdoc}     */    public function hasListeners($eventName = null)    {        return $this->dispatcher->hasListeners($eventName);    }    /**     * {@inheritdoc}     */    public function dispatch($eventName, Event $event = null)    {        if (null === $this->callStack) {            $this->callStack = new \SplObjectStorage();        }        if (null === $event) {            $event = new Event();        }        if (null !== $this->logger && $event->isPropagationStopped()) {            $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));        }        $this->preProcess($eventName);        try {            $this->preDispatch($eventName, $event);            try {                $e = $this->stopwatch->start($eventName, 'section');                try {                    $this->dispatcher->dispatch($eventName, $event);                } finally {                    if ($e->isStarted()) {                        $e->stop();                    }                }            } finally {                $this->postDispatch($eventName, $event);            }        } finally {            $this->postProcess($eventName);        }        return $event;    }    /**     * {@inheritdoc}     */    public function getCalledListeners()    {        if (null === $this->callStack) {            return [];        }        $called = [];        foreach ($this->callStack as $listener) {            list($eventName) = $this->callStack->getInfo();            $called[] = $listener->getInfo($eventName);        }        return $called;    }    /**     * {@inheritdoc}     */    public function getNotCalledListeners()    {        try {            $allListeners = $this->getListeners();        } catch (\Exception $e) {            if (null !== $this->logger) {                $this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);            }            // unable to retrieve the uncalled listeners            return [];        }        $calledListeners = [];        if (null !== $this->callStack) {            foreach ($this->callStack as $calledListener) {                $calledListeners[] = $calledListener->getWrappedListener();            }        }        $notCalled = [];        foreach ($allListeners as $eventName => $listeners) {            foreach ($listeners as $listener) {                if (!\in_array($listener, $calledListeners, true)) {                    if (!$listener instanceof WrappedListener) {                        $listener = new WrappedListener($listener, null, $this->stopwatch, $this);                    }                    $notCalled[] = $listener->getInfo($eventName);                }            }        }        uasort($notCalled, [$this, 'sortNotCalledListeners']);        return $notCalled;    }    public function reset()    {        $this->callStack = null;    }    /**     * Proxies all method calls to the original event dispatcher.     *     * @param string $method    The method name     * @param array  $arguments The method arguments     *     * @return mixed     */    public function __call($method, $arguments)    {        return \call_user_func_array([$this->dispatcher, $method], $arguments);    }    /**     * Called before dispatching the event.     *     * @param string $eventName The event name     * @param Event  $event     The event     */    protected function preDispatch($eventName, Event $event)    {    }    /**     * Called after dispatching the event.     *     * @param string $eventName The event name     * @param Event  $event     The event     */    protected function postDispatch($eventName, Event $event)    {    }    private function preProcess($eventName)    {        foreach ($this->dispatcher->getListeners($eventName) as $listener) {            $priority = $this->getListenerPriority($eventName, $listener);            $wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this);            $this->wrappedListeners[$eventName][] = $wrappedListener;            $this->dispatcher->removeListener($eventName, $listener);            $this->dispatcher->addListener($eventName, $wrappedListener, $priority);            $this->callStack->attach($wrappedListener, [$eventName]);        }    }    private function postProcess($eventName)    {        unset($this->wrappedListeners[$eventName]);        $skipped = false;        foreach ($this->dispatcher->getListeners($eventName) as $listener) {            if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.                continue;            }            // Unwrap listener            $priority = $this->getListenerPriority($eventName, $listener);            $this->dispatcher->removeListener($eventName, $listener);            $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);            if (null !== $this->logger) {                $context = ['event' => $eventName, 'listener' => $listener->getPretty()];            }            if ($listener->wasCalled()) {                if (null !== $this->logger) {                    $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);                }            } else {                $this->callStack->detach($listener);            }            if (null !== $this->logger && $skipped) {                $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);            }            if ($listener->stoppedPropagation()) {                if (null !== $this->logger) {                    $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);                }                $skipped = true;            }        }    }    private function sortNotCalledListeners(array $a, array $b)    {        if (0 !== $cmp = strcmp($a['event'], $b['event'])) {            return $cmp;        }        if (\is_int($a['priority']) && !\is_int($b['priority'])) {            return 1;        }        if (!\is_int($a['priority']) && \is_int($b['priority'])) {            return -1;        }        if ($a['priority'] === $b['priority']) {            return 0;        }        if ($a['priority'] > $b['priority']) {            return -1;        }        return 1;    }}
 |