Skip to content

Commit

Permalink
feature #29312 [EventDispatcher] Split events across requests (ro0NL)
Browse files Browse the repository at this point in the history
This PR was squashed before being merged into the 4.3-dev branch (closes #29312).

Discussion
----------

[EventDispatcher] Split events across requests

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | no
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | #24275
| License       | MIT
| Doc PR        | symfony/symfony-docs#... <!-- required for new features -->

Split events per request, as currently a profiled sub-request includes all events. Follows same approach how logs are split in #23659.

Commits
-------

c3477ba [EventDispatcher] Split events across requests
  • Loading branch information
fabpot committed Apr 2, 2019
2 parents aa5b6f9 + c3477ba commit 50a5dfd
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 21 deletions.
Expand Up @@ -28,6 +28,7 @@
<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" />
<argument type="service" id="debug.event_dispatcher" on-invalid="ignore" />
<argument type="service" id="request_stack" on-invalid="ignore" />
</service>

<service id="data_collector.logger" class="Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector">
Expand Down
Expand Up @@ -12,6 +12,7 @@
<argument type="service" id="debug.event_dispatcher.inner" />
<argument type="service" id="debug.stopwatch" />
<argument type="service" id="logger" on-invalid="null" />
<argument type="service" id="request_stack" on-invalid="null" />
</service>

<service id="debug.controller_resolver" decorates="controller_resolver" class="Symfony\Component\HttpKernel\Controller\TraceableControllerResolver">
Expand Down
Expand Up @@ -13,10 +13,12 @@

use Psr\EventDispatcher\StoppableEventInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\BrowserKit\Request;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;

Expand All @@ -36,14 +38,17 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface
private $dispatcher;
private $wrappedListeners;
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->stopwatch = $stopwatch;
$this->logger = $logger;
$this->wrappedListeners = [];
$this->orphanedEvents = [];
$this->requestStack = $requestStack;
}

/**
Expand Down Expand Up @@ -133,6 +138,7 @@ public function dispatch($event/*, string $eventName = null*/)
$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;

if (\is_object($event)) {
Expand Down Expand Up @@ -168,6 +174,7 @@ public function dispatch($event/*, string $eventName = null*/)
$this->afterDispatch($eventName, $event);
}
} finally {
$this->currentRequestHash = $currentRequestHash;
$this->postProcess($eventName);
}

Expand All @@ -176,27 +183,33 @@ public function dispatch($event/*, string $eventName = null*/)

/**
* {@inheritdoc}
*
* @param Request|null $request The request to get listeners for
*/
public function getCalledListeners()
public function getCalledListeners(/* Request $request = null */)
{
if (null === $this->callStack) {
return [];
}

$hash = 1 <= \func_num_args() && null !== ($request = \func_get_arg(0)) ? spl_object_hash($request) : null;
$called = [];
foreach ($this->callStack as $listener) {
list($eventName) = $this->callStack->getInfo();

$called[] = $listener->getInfo($eventName);
list($eventName, $requestHash) = $this->callStack->getInfo();
if (null === $hash || $hash === $requestHash) {
$called[] = $listener->getInfo($eventName);
}
}

return $called;
}

/**
* {@inheritdoc}
*
* @param Request|null $request The request to get listeners for
*/
public function getNotCalledListeners()
public function getNotCalledListeners(/* Request $request = null */)
{
try {
$allListeners = $this->getListeners();
Expand All @@ -209,13 +222,15 @@ public function getNotCalledListeners()
return [];
}

$hash = 1 <= \func_num_args() && null !== ($request = \func_get_arg(0)) ? spl_object_hash($request) : null;
$notCalled = [];
foreach ($allListeners as $eventName => $listeners) {
foreach ($listeners as $listener) {
$called = false;
if (null !== $this->callStack) {
foreach ($this->callStack as $calledListener) {
if ($calledListener->getWrappedListener() === $listener) {
list(, $requestHash) = $this->callStack->getInfo();
if ((null === $hash || $hash === $requestHash) && $calledListener->getWrappedListener() === $listener) {
$called = true;

break;
Expand All @@ -237,15 +252,27 @@ public function getNotCalledListeners()
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()
{
$this->callStack = null;
$this->orphanedEvents = [];
$this->currentRequestHash = '';
}

/**
Expand Down Expand Up @@ -298,7 +325,7 @@ protected function postDispatch($eventName, Event $event)
private function preProcess($eventName)
{
if (!$this->dispatcher->hasListeners($eventName)) {
$this->orphanedEvents[] = $eventName;
$this->orphanedEvents[$this->currentRequestHash][] = $eventName;

return;
}
Expand All @@ -309,7 +336,7 @@ private function preProcess($eventName)
$this->wrappedListeners[$eventName][] = $wrappedListener;
$this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $wrappedListener, $priority);
$this->callStack->attach($wrappedListener, [$eventName]);
$this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]);
}
}

Expand All @@ -334,10 +361,6 @@ private function postProcess($eventName)
if (null !== $this->logger) {
$this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);
}

if (!isset($this->called[$eventName])) {
$this->called[$eventName] = new \SplObjectStorage();
}
} else {
$this->callStack->detach($listener);
}
Expand Down
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\EventDispatcher\Debug;

use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Contracts\Service\ResetInterface;

/**
Expand All @@ -24,14 +25,18 @@ interface TraceableEventDispatcherInterface extends EventDispatcherInterface, Re
/**
* Gets the called listeners.
*
* @param Request|null $request The request to get listeners for
*
* @return array An array of called listeners
*/
public function getCalledListeners();
public function getCalledListeners(/* Request $request = null */);

/**
* Gets the not called listeners.
*
* @param Request|null $request The request to get listeners for
*
* @return array An array of not called listeners
*/
public function getNotCalledListeners();
public function getNotCalledListeners(/* Request $request = null */);
}
1 change: 1 addition & 0 deletions src/Symfony/Component/EventDispatcher/composer.json
Expand Up @@ -23,6 +23,7 @@
"symfony/dependency-injection": "~3.4|~4.0",
"symfony/expression-language": "~3.4|~4.0",
"symfony/config": "~3.4|~4.0",
"symfony/http-foundation": "^3.4|^4.0",
"symfony/stopwatch": "~3.4|~4.0",
"psr/log": "~1.0"
},
Expand Down
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Service\ResetInterface;
Expand All @@ -26,17 +27,21 @@
class EventDataCollector extends DataCollector implements LateDataCollectorInterface
{
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->requestStack = $requestStack;
}

/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->currentRequest = $this->requestStack && $this->requestStack->getMasterRequest() !== $request ? $request : null;
$this->data = [
'called_listeners' => [],
'not_called_listeners' => [],
Expand All @@ -56,12 +61,12 @@ public function reset()
public function lateCollect()
{
if ($this->dispatcher instanceof TraceableEventDispatcherInterface) {
$this->setCalledListeners($this->dispatcher->getCalledListeners());
$this->setNotCalledListeners($this->dispatcher->getNotCalledListeners());
$this->setCalledListeners($this->dispatcher->getCalledListeners($this->currentRequest));
$this->setNotCalledListeners($this->dispatcher->getNotCalledListeners($this->currentRequest));
}

if ($this->dispatcher instanceof TraceableEventDispatcher) {
$this->setOrphanedEvents($this->dispatcher->getOrphanedEvents());
$this->setOrphanedEvents($this->dispatcher->getOrphanedEvents($this->currentRequest));
}

$this->data = $this->cloneVar($this->data);
Expand Down

0 comments on commit 50a5dfd

Please sign in to comment.