[EventDispatcher] Split events across requests

This commit is contained in:
Roland Franssen 2018-11-25 08:41:16 +01:00 committed by Fabien Potencier
parent aa5b6f95b9
commit c3477badbc
6 changed files with 57 additions and 21 deletions

View File

@ -28,6 +28,7 @@
<service id="data_collector.events" class="Symfony\Component\HttpKernel\DataCollector\EventDataCollector"> <service id="data_collector.events" class="Symfony\Component\HttpKernel\DataCollector\EventDataCollector">
<tag name="data_collector" template="@WebProfiler/Collector/events.html.twig" id="events" priority="290" /> <tag name="data_collector" template="@WebProfiler/Collector/events.html.twig" id="events" priority="290" />
<argument type="service" id="debug.event_dispatcher" on-invalid="ignore" /> <argument type="service" id="debug.event_dispatcher" on-invalid="ignore" />
<argument type="service" id="request_stack" on-invalid="ignore" />
</service> </service>
<service id="data_collector.logger" class="Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector"> <service id="data_collector.logger" class="Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector">

View File

@ -12,6 +12,7 @@
<argument type="service" id="debug.event_dispatcher.inner" /> <argument type="service" id="debug.event_dispatcher.inner" />
<argument type="service" id="debug.stopwatch" /> <argument type="service" id="debug.stopwatch" />
<argument type="service" id="logger" on-invalid="null" /> <argument type="service" id="logger" on-invalid="null" />
<argument type="service" id="request_stack" on-invalid="null" />
</service> </service>
<service id="debug.controller_resolver" decorates="controller_resolver" class="Symfony\Component\HttpKernel\Controller\TraceableControllerResolver"> <service id="debug.controller_resolver" decorates="controller_resolver" class="Symfony\Component\HttpKernel\Controller\TraceableControllerResolver">

View File

@ -13,10 +13,12 @@ namespace Symfony\Component\EventDispatcher\Debug;
use Psr\EventDispatcher\StoppableEventInterface; use Psr\EventDispatcher\StoppableEventInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\BrowserKit\Request;
use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy; use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Contracts\EventDispatcher\Event as ContractsEvent; use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
@ -36,14 +38,17 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
private $dispatcher; private $dispatcher;
private $wrappedListeners; private $wrappedListeners;
private $orphanedEvents; private $orphanedEvents;
private $requestStack;
private $currentRequestHash = '';
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null) public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null, RequestStack $requestStack = null)
{ {
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
$this->stopwatch = $stopwatch; $this->stopwatch = $stopwatch;
$this->logger = $logger; $this->logger = $logger;
$this->wrappedListeners = []; $this->wrappedListeners = [];
$this->orphanedEvents = []; $this->orphanedEvents = [];
$this->requestStack = $requestStack;
} }
/** /**
@ -133,6 +138,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
$this->callStack = new \SplObjectStorage(); $this->callStack = new \SplObjectStorage();
} }
$currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : '';
$eventName = 1 < \func_num_args() ? \func_get_arg(1) : null; $eventName = 1 < \func_num_args() ? \func_get_arg(1) : null;
if (\is_object($event)) { if (\is_object($event)) {
@ -168,6 +174,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
$this->afterDispatch($eventName, $event); $this->afterDispatch($eventName, $event);
} }
} finally { } finally {
$this->currentRequestHash = $currentRequestHash;
$this->postProcess($eventName); $this->postProcess($eventName);
} }
@ -176,18 +183,22 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*
* @param Request|null $request The request to get listeners for
*/ */
public function getCalledListeners() public function getCalledListeners(/* Request $request = null */)
{ {
if (null === $this->callStack) { if (null === $this->callStack) {
return []; return [];
} }
$hash = 1 <= \func_num_args() && null !== ($request = \func_get_arg(0)) ? spl_object_hash($request) : null;
$called = []; $called = [];
foreach ($this->callStack as $listener) { foreach ($this->callStack as $listener) {
list($eventName) = $this->callStack->getInfo(); list($eventName, $requestHash) = $this->callStack->getInfo();
if (null === $hash || $hash === $requestHash) {
$called[] = $listener->getInfo($eventName); $called[] = $listener->getInfo($eventName);
}
} }
return $called; return $called;
@ -195,8 +206,10 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*
* @param Request|null $request The request to get listeners for
*/ */
public function getNotCalledListeners() public function getNotCalledListeners(/* Request $request = null */)
{ {
try { try {
$allListeners = $this->getListeners(); $allListeners = $this->getListeners();
@ -209,13 +222,15 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
return []; return [];
} }
$hash = 1 <= \func_num_args() && null !== ($request = \func_get_arg(0)) ? spl_object_hash($request) : null;
$notCalled = []; $notCalled = [];
foreach ($allListeners as $eventName => $listeners) { foreach ($allListeners as $eventName => $listeners) {
foreach ($listeners as $listener) { foreach ($listeners as $listener) {
$called = false; $called = false;
if (null !== $this->callStack) { if (null !== $this->callStack) {
foreach ($this->callStack as $calledListener) { foreach ($this->callStack as $calledListener) {
if ($calledListener->getWrappedListener() === $listener) { list(, $requestHash) = $this->callStack->getInfo();
if ((null === $hash || $hash === $requestHash) && $calledListener->getWrappedListener() === $listener) {
$called = true; $called = true;
break; break;
@ -237,15 +252,27 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
return $notCalled; return $notCalled;
} }
public function getOrphanedEvents(): array /**
* @param Request|null $request The request to get orphaned events for
*/
public function getOrphanedEvents(/* Request $request = null */): array
{ {
return $this->orphanedEvents; if (1 <= \func_num_args() && null !== $request = \func_get_arg(0)) {
return $this->orphanedEvents[spl_object_hash($request)] ?? [];
}
if (!$this->orphanedEvents) {
return [];
}
return array_merge(...array_values($this->orphanedEvents));
} }
public function reset() public function reset()
{ {
$this->callStack = null; $this->callStack = null;
$this->orphanedEvents = []; $this->orphanedEvents = [];
$this->currentRequestHash = '';
} }
/** /**
@ -298,7 +325,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
private function preProcess($eventName) private function preProcess($eventName)
{ {
if (!$this->dispatcher->hasListeners($eventName)) { if (!$this->dispatcher->hasListeners($eventName)) {
$this->orphanedEvents[] = $eventName; $this->orphanedEvents[$this->currentRequestHash][] = $eventName;
return; return;
} }
@ -309,7 +336,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
$this->wrappedListeners[$eventName][] = $wrappedListener; $this->wrappedListeners[$eventName][] = $wrappedListener;
$this->dispatcher->removeListener($eventName, $listener); $this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $wrappedListener, $priority); $this->dispatcher->addListener($eventName, $wrappedListener, $priority);
$this->callStack->attach($wrappedListener, [$eventName]); $this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]);
} }
} }
@ -334,10 +361,6 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
if (null !== $this->logger) { if (null !== $this->logger) {
$this->logger->debug('Notified event "{event}" to listener "{listener}".', $context); $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);
} }
if (!isset($this->called[$eventName])) {
$this->called[$eventName] = new \SplObjectStorage();
}
} else { } else {
$this->callStack->detach($listener); $this->callStack->detach($listener);
} }

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\EventDispatcher\Debug; namespace Symfony\Component\EventDispatcher\Debug;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Contracts\Service\ResetInterface; use Symfony\Contracts\Service\ResetInterface;
/** /**
@ -24,14 +25,18 @@ interface TraceableEventDispatcherInterface extends EventDispatcherInterface, Re
/** /**
* Gets the called listeners. * Gets the called listeners.
* *
* @param Request|null $request The request to get listeners for
*
* @return array An array of called listeners * @return array An array of called listeners
*/ */
public function getCalledListeners(); public function getCalledListeners(/* Request $request = null */);
/** /**
* Gets the not called listeners. * Gets the not called listeners.
* *
* @param Request|null $request The request to get listeners for
*
* @return array An array of not called listeners * @return array An array of not called listeners
*/ */
public function getNotCalledListeners(); public function getNotCalledListeners(/* Request $request = null */);
} }

View File

@ -23,6 +23,7 @@
"symfony/dependency-injection": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0",
"symfony/expression-language": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0",
"symfony/config": "~3.4|~4.0", "symfony/config": "~3.4|~4.0",
"symfony/http-foundation": "^3.4|^4.0",
"symfony/stopwatch": "~3.4|~4.0", "symfony/stopwatch": "~3.4|~4.0",
"psr/log": "~1.0" "psr/log": "~1.0"
}, },

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface; use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Service\ResetInterface; use Symfony\Contracts\Service\ResetInterface;
@ -26,10 +27,13 @@ use Symfony\Contracts\Service\ResetInterface;
class EventDataCollector extends DataCollector implements LateDataCollectorInterface class EventDataCollector extends DataCollector implements LateDataCollectorInterface
{ {
protected $dispatcher; protected $dispatcher;
private $requestStack;
private $currentRequest;
public function __construct(EventDispatcherInterface $dispatcher = null) public function __construct(EventDispatcherInterface $dispatcher = null, RequestStack $requestStack = null)
{ {
$this->dispatcher = $dispatcher; $this->dispatcher = $dispatcher;
$this->requestStack = $requestStack;
} }
/** /**
@ -37,6 +41,7 @@ class EventDataCollector extends DataCollector implements LateDataCollectorInter
*/ */
public function collect(Request $request, Response $response, \Exception $exception = null) public function collect(Request $request, Response $response, \Exception $exception = null)
{ {
$this->currentRequest = $this->requestStack && $this->requestStack->getMasterRequest() !== $request ? $request : null;
$this->data = [ $this->data = [
'called_listeners' => [], 'called_listeners' => [],
'not_called_listeners' => [], 'not_called_listeners' => [],
@ -56,12 +61,12 @@ class EventDataCollector extends DataCollector implements LateDataCollectorInter
public function lateCollect() public function lateCollect()
{ {
if ($this->dispatcher instanceof TraceableEventDispatcherInterface) { if ($this->dispatcher instanceof TraceableEventDispatcherInterface) {
$this->setCalledListeners($this->dispatcher->getCalledListeners()); $this->setCalledListeners($this->dispatcher->getCalledListeners($this->currentRequest));
$this->setNotCalledListeners($this->dispatcher->getNotCalledListeners()); $this->setNotCalledListeners($this->dispatcher->getNotCalledListeners($this->currentRequest));
} }
if ($this->dispatcher instanceof TraceableEventDispatcher) { if ($this->dispatcher instanceof TraceableEventDispatcher) {
$this->setOrphanedEvents($this->dispatcher->getOrphanedEvents()); $this->setOrphanedEvents($this->dispatcher->getOrphanedEvents($this->currentRequest));
} }
$this->data = $this->cloneVar($this->data); $this->data = $this->cloneVar($this->data);