Skip to content

Commit bdad285

Browse files
committed
[HttpKernel][WebProfilerBundle] Profile stack
1 parent d3c17f2 commit bdad285

File tree

6 files changed

+111
-9
lines changed

6 files changed

+111
-9
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
<argument>null</argument>
2525
<argument>%profiler_listener.only_exceptions%</argument>
2626
<argument>%profiler_listener.only_master_requests%</argument>
27+
<argument type="service" id="profile_stack" />
2728
</service>
29+
30+
<service id="profile_stack" class="Symfony\Component\HttpKernel\Profiler\ProfileStack" />
2831
</services>
2932
</container>

src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616
use Symfony\Component\HttpFoundation\Request;
1717
use Symfony\Component\HttpFoundation\Response;
1818
use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag;
19+
use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector;
20+
use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector;
1921
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
2022
use Symfony\Component\HttpKernel\KernelEvents;
23+
use Symfony\Component\HttpKernel\Profiler\ProfileStack;
2124
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
2225
use Twig\Environment;
2326

@@ -44,15 +47,17 @@ class WebDebugToolbarListener implements EventSubscriberInterface
4447
protected $mode;
4548
protected $excludedAjaxPaths;
4649
private $cspHandler;
50+
private $profileStack;
4751

48-
public function __construct(Environment $twig, bool $interceptRedirects = false, int $mode = self::ENABLED, UrlGeneratorInterface $urlGenerator = null, string $excludedAjaxPaths = '^/bundles|^/_wdt', ContentSecurityPolicyHandler $cspHandler = null)
52+
public function __construct(Environment $twig, bool $interceptRedirects = false, int $mode = self::ENABLED, UrlGeneratorInterface $urlGenerator = null, string $excludedAjaxPaths = '^/bundles|^/_wdt', ContentSecurityPolicyHandler $cspHandler = null, ProfileStack $profileStack = null)
4953
{
5054
$this->twig = $twig;
5155
$this->urlGenerator = $urlGenerator;
5256
$this->interceptRedirects = $interceptRedirects;
5357
$this->mode = $mode;
5458
$this->excludedAjaxPaths = $excludedAjaxPaths;
5559
$this->cspHandler = $cspHandler;
60+
$this->profileStack = $profileStack;
5661
}
5762

5863
public function isEnabled()
@@ -65,11 +70,34 @@ public function onKernelResponse(FilterResponseEvent $event)
6570
$response = $event->getResponse();
6671
$request = $event->getRequest();
6772

68-
if ($response->headers->has('X-Debug-Token') && null !== $this->urlGenerator) {
73+
$hasProfile = $this->profileStack instanceof ProfileStack ? $this->profileStack->has($request) : $response->headers->has('X-Debug-Token');
74+
75+
if ($hasProfile && null !== $this->urlGenerator) {
76+
if ($this->profileStack instanceof ProfileStack) {
77+
$profile = $this->profileStack->get($request);
78+
79+
$token = $profile->getToken();
80+
81+
foreach ($profile->getCollectors() as $collector) {
82+
if ($collector instanceof ExceptionDataCollector && $collector->hasException()) {
83+
$panel = $collector->getName();
84+
85+
break;
86+
} elseif ($collector instanceof DumpDataCollector && $collector->getDumpsCount() > 0) {
87+
$panel = $collector->getName();
88+
}
89+
}
90+
} else {
91+
$token = $response->headers->has('X-Debug-Token');
92+
}
93+
6994
try {
7095
$response->headers->set(
7196
'X-Debug-Token-Link',
72-
$this->urlGenerator->generate('_profiler', ['token' => $response->headers->get('X-Debug-Token')], UrlGeneratorInterface::ABSOLUTE_URL)
97+
$this->urlGenerator->generate('_profiler', [
98+
'token' => $token,
99+
'panel' => $panel ?? null,
100+
], UrlGeneratorInterface::ABSOLUTE_URL)
73101
);
74102
} catch (\Exception $e) {
75103
$response->headers->set('X-Debug-Error', \get_class($e).': '.preg_replace('/\s+/', ' ', $e->getMessage()));
@@ -87,7 +115,7 @@ public function onKernelResponse(FilterResponseEvent $event)
87115
return;
88116
}
89117

90-
if ($response->headers->has('X-Debug-Token') && $response->isRedirect() && $this->interceptRedirects && 'html' === $request->getRequestFormat()) {
118+
if ($hasProfile && $response->isRedirect() && $this->interceptRedirects && 'html' === $request->getRequestFormat()) {
91119
$session = $request->getSession();
92120
if (null !== $session && $session->isStarted() && $session->getFlashBag() instanceof AutoExpireFlashBag) {
93121
// keep current flashes for one more request if using AutoExpireFlashBag
@@ -100,7 +128,7 @@ public function onKernelResponse(FilterResponseEvent $event)
100128
}
101129

102130
if (self::DISABLED === $this->mode
103-
|| !$response->headers->has('X-Debug-Token')
131+
|| !$hasProfile
104132
|| $response->isRedirection()
105133
|| ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html'))
106134
|| 'html' !== $request->getRequestFormat()

src/Symfony/Bundle/WebProfilerBundle/Resources/config/toolbar.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<argument type="service" id="router" on-invalid="ignore" />
1616
<argument /> <!-- paths that should be excluded from the AJAX requests shown in the toolbar -->
1717
<argument type="service" id="web_profiler.csp.handler" />
18+
<argument type="service" id="profile_stack" />
1819
</service>
1920
</services>
2021
</container>

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@
267267
if (request.profilerUrl) {
268268
profilerCell.textContent = '';
269269
var profilerLink = document.createElement('a');
270-
profilerLink.setAttribute('href', request.statusCode < 400 ? request.profilerUrl : request.profilerUrl + '?panel=exception');
270+
profilerLink.setAttribute('href', request.profilerUrl);
271271
profilerLink.textContent = request.profile;
272272
profilerCell.appendChild(profilerLink);
273273
}

src/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,20 @@
1212
namespace Symfony\Component\HttpKernel\EventListener;
1313

1414
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15+
use Symfony\Component\HttpFoundation\Request;
1516
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
1617
use Symfony\Component\HttpFoundation\RequestStack;
1718
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
1819
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
1920
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
2021
use Symfony\Component\HttpKernel\KernelEvents;
22+
use Symfony\Component\HttpKernel\Profiler\Profile;
2123
use Symfony\Component\HttpKernel\Profiler\Profiler;
24+
use Symfony\Component\HttpKernel\Profiler\ProfileStack;
2225

2326
/**
24-
* ProfilerListener collects data for the current request by listening to the kernel events.
27+
* ProfilerListener collects data for the current request by listening to the
28+
* kernel events.
2529
*
2630
* @author Fabien Potencier <fabien@symfony.com>
2731
*
@@ -37,15 +41,17 @@ class ProfilerListener implements EventSubscriberInterface
3741
protected $profiles;
3842
protected $requestStack;
3943
protected $parents;
44+
private $profileStack;
4045

4146
/**
4247
* @param Profiler $profiler A Profiler instance
4348
* @param RequestStack $requestStack A RequestStack instance
4449
* @param RequestMatcherInterface|null $matcher A RequestMatcher instance
4550
* @param bool $onlyException True if the profiler only collects data when an exception occurs, false otherwise
4651
* @param bool $onlyMasterRequests True if the profiler only collects data when the request is a master request, false otherwise
52+
* @param ProfileStack|null $profileStack A ProfileStack instance
4753
*/
48-
public function __construct(Profiler $profiler, RequestStack $requestStack, RequestMatcherInterface $matcher = null, bool $onlyException = false, bool $onlyMasterRequests = false)
54+
public function __construct(Profiler $profiler, RequestStack $requestStack, RequestMatcherInterface $matcher = null, bool $onlyException = false, bool $onlyMasterRequests = false, ProfileStack $profileStack = null)
4955
{
5056
$this->profiler = $profiler;
5157
$this->matcher = $matcher;
@@ -54,6 +60,7 @@ public function __construct(Profiler $profiler, RequestStack $requestStack, Requ
5460
$this->profiles = new \SplObjectStorage();
5561
$this->parents = new \SplObjectStorage();
5662
$this->requestStack = $requestStack;
63+
$this->profileStack = $profileStack ?: new ProfileStack();
5764
}
5865

5966
/**
@@ -97,9 +104,13 @@ public function onKernelResponse(FilterResponseEvent $event)
97104
$this->profiles[$request] = $profile;
98105

99106
$this->parents[$request] = $this->requestStack->getParentRequest();
107+
108+
if ($this->profileStack instanceof ProfileStack) {
109+
$this->profileStack->set($request, $profile);
110+
}
100111
}
101112

102-
public function onKernelTerminate(PostResponseEvent $event)
113+
public function onKernelTerminate()
103114
{
104115
// attach children to parents
105116
foreach ($this->profiles as $request) {
@@ -117,6 +128,10 @@ public function onKernelTerminate(PostResponseEvent $event)
117128

118129
$this->profiles = new \SplObjectStorage();
119130
$this->parents = new \SplObjectStorage();
131+
132+
if ($this->profileStack instanceof ProfileStack) {
133+
$this->profileStack->reset();
134+
}
120135
}
121136

122137
public static function getSubscribedEvents()
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpKernel\Profiler;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
16+
/**
17+
* @internal
18+
*/
19+
final class ProfileStack
20+
{
21+
/**
22+
* @var \SplObjectStorage
23+
*/
24+
private $profiles;
25+
26+
public function __construct()
27+
{
28+
$this->reset();
29+
}
30+
31+
public function has(Request $request): bool
32+
{
33+
return isset($this->profiles[$request]);
34+
}
35+
36+
public function get(Request $request): Profile
37+
{
38+
return $this->profiles[$request];
39+
}
40+
41+
public function set(Request $request, Profile $profile): void
42+
{
43+
$this->profiles[$request] = $profile;
44+
}
45+
46+
public function all(): \SplObjectStorage
47+
{
48+
return $this->profiles;
49+
}
50+
51+
public function reset(): void
52+
{
53+
$this->profiles = new \SplObjectStorage();
54+
}
55+
}

0 commit comments

Comments
 (0)