diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.php index af7e4c1d819a..010b5bf8fccc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.php @@ -29,9 +29,16 @@ ->tag('data_collector', ['template' => '@WebProfiler/Collector/config.html.twig', 'id' => 'config', 'priority' => -255]) ->set('data_collector.request', RequestDataCollector::class) + ->args([ + service('request_stack')->ignoreOnInvalid(), + ]) ->tag('kernel.event_subscriber') ->tag('data_collector', ['template' => '@WebProfiler/Collector/request.html.twig', 'id' => 'request', 'priority' => 335]) + ->set('data_collector.request.session_collector', \Closure::class) + ->factory([\Closure::class, 'fromCallable']) + ->args([[service('data_collector.request'), 'collectSessionUsage']]) + ->set('data_collector.ajax', AjaxDataCollector::class) ->tag('data_collector', ['template' => '@WebProfiler/Collector/ajax.html.twig', 'id' => 'ajax', 'priority' => 315]) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.php index 0f5e5de07100..812ee50e7ce8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.php @@ -97,6 +97,7 @@ 'session' => service('session')->ignoreOnInvalid(), 'initialized_session' => service('session')->ignoreOnUninitialized(), 'logger' => service('logger')->ignoreOnInvalid(), + 'session_collector' => service('data_collector.request.session_collector')->ignoreOnInvalid(), ]), param('kernel.debug'), ]) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 13912658d286..5480ec7ecf13 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -504,7 +504,7 @@ public function testNullSessionHandler() $this->assertNull($container->getDefinition('session.storage.native')->getArgument(1)); $this->assertNull($container->getDefinition('session.storage.php_bridge')->getArgument(0)); - $expected = ['session', 'initialized_session', 'logger']; + $expected = ['session', 'initialized_session', 'logger', 'session_collector']; $this->assertEquals($expected, array_keys($container->getDefinition('session_listener')->getArgument(0)->getValues())); } @@ -1312,7 +1312,7 @@ public function testSessionCookieSecureAuto() { $container = $this->createContainerFromFile('session_cookie_secure_auto'); - $expected = ['session', 'initialized_session', 'logger', 'session_storage', 'request_stack']; + $expected = ['session', 'initialized_session', 'logger', 'session_collector', 'session_storage', 'request_stack']; $this->assertEquals($expected, array_keys($container->getDefinition('session_listener')->getArgument(0)->getValues())); } diff --git a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md index 5418767fc9e0..028537ead68c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.2.0 +----- + + * added session usage + 5.0.0 ----- diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig index eb5c5595c4cd..18311c169fec 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig @@ -59,6 +59,11 @@ Has session {% if collector.sessionmetadata|length %}yes{% else %}no{% endif %} + +
+ Stateless Check + {% if collector.statelesscheck %}yes{% else %}no{% endif %} +
{% if redirect_handler is defined -%} @@ -228,7 +233,7 @@
-

Session

+

Session{% if collector.sessionusages is not empty %} {{ collector.sessionusages|length }}{% endif %}

Session Metadata

@@ -250,6 +255,54 @@ {% else %} {{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.sessionattributes, labels: ['Attribute', 'Value'] }, with_context = false) }} {% endif %} + +

Session Usage

+ +
+
+ {{ collector.sessionusages|length }} + Usages +
+ +
+ {{ include('@WebProfiler/Icon/' ~ (collector.statelesscheck ? 'yes' : 'no') ~ '.svg') }} + Stateless check enabled +
+
+ + {% if collector.sessionusages is empty %} +
+

Session not used.

+
+ {% else %} + + + + + + + + + {% for key, usage in collector.sessionusages %} + + + + {% endfor %} + +
Usage
+ {%- set link = usage.file|file_link(usage.line) %} + {%- if link %}{% else %}{% endif %} + {{ usage.name }} + {%- if link %}{% else %}{% endif %} +
+ {% set usage_id = 'session-usage-trace-' ~ key %} + Show trace +
+
+ {{ profiler_dump(usage.trace, maxDepth=2) }} +
+
+ {% endif %}
diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 19f8d9f3bde3..b5024cf0f0be 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.2.0 ----- + * added session usage * made the public `http_cache` service handle requests when available * allowed enabling trusted hosts and proxies using new `kernel.trusted_hosts`, `kernel.trusted_proxies` and `kernel.trusted_headers` parameters diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php index b92518d8062b..3b4063b4a9d9 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php @@ -15,7 +15,10 @@ use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Event\ControllerEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -28,10 +31,13 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInterface, LateDataCollectorInterface { protected $controllers; + private $sessionUsages = []; + private $requestStack; - public function __construct() + public function __construct(?RequestStack $requestStack = null) { $this->controllers = new \SplObjectStorage(); + $this->requestStack = $requestStack; } /** @@ -105,6 +111,8 @@ public function collect(Request $request, Response $response, \Throwable $except 'response_cookies' => $responseCookies, 'session_metadata' => $sessionMetadata, 'session_attributes' => $sessionAttributes, + 'session_usages' => array_values($this->sessionUsages), + 'stateless_check' => $this->requestStack && $this->requestStack->getMasterRequest()->attributes->get('_stateless', false), 'flashes' => $flashes, 'path_info' => $request->getPathInfo(), 'controller' => 'n/a', @@ -175,6 +183,7 @@ public function reset() { $this->data = []; $this->controllers = new \SplObjectStorage(); + $this->sessionUsages = []; } public function getMethod() @@ -242,6 +251,16 @@ public function getSessionAttributes() return $this->data['session_attributes']->getValue(); } + public function getStatelessCheck() + { + return $this->data['stateless_check']; + } + + public function getSessionUsages() + { + return $this->data['session_usages']; + } + public function getFlashes() { return $this->data['flashes']->getValue(); @@ -382,6 +401,37 @@ public function getName() return 'request'; } + public function collectSessionUsage(): void + { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + + $traceEndIndex = \count($trace) - 1; + for ($i = $traceEndIndex; $i > 0; --$i) { + if (null !== ($class = $trace[$i]['class'] ?? null) && (is_subclass_of($class, SessionInterface::class) || is_subclass_of($class, SessionBagInterface::class))) { + $traceEndIndex = $i; + break; + } + } + + if ((\count($trace) - 1) === $traceEndIndex) { + return; + } + + // Remove part of the backtrace that belongs to session only + array_splice($trace, 0, $traceEndIndex); + + // Merge identical backtraces generated by internal call reports + $name = sprintf('%s:%s', $trace[1]['class'] ?? $trace[0]['file'], $trace[0]['line']); + if (!\array_key_exists($name, $this->sessionUsages)) { + $this->sessionUsages[$name] = [ + 'name' => $name, + 'file' => $trace[0]['file'], + 'line' => $trace[0]['line'], + 'trace' => $trace, + ]; + } + } + /** * Parse a controller. * diff --git a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php index 1fe3264f7d30..0208e8dec537 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php @@ -152,6 +152,10 @@ public function onSessionUsage(): void return; } + if ($this->container && $this->container->has('session_collector')) { + $this->container->get('session_collector')(); + } + if (!$requestStack = $this->container && $this->container->has('request_stack') ? $this->container->get('request_stack') : null) { return; } diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php index 5753dc88da7b..b62f765068dc 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php @@ -17,8 +17,11 @@ use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; @@ -248,6 +251,65 @@ public function testItCollectsTheRedirectionAndClearTheCookie() $this->assertNull($cookie->getValue()); } + public function testItCollectsTheSessionTraceProperly() + { + $collector = new RequestDataCollector(); + $request = $this->createRequest(); + + // RequestDataCollectorTest doesn't implement SessionInterface or SessionBagInterface, therefore should do nothing. + $collector->collectSessionUsage(); + + $collector->collect($request, $this->createResponse()); + $this->assertSame([], $collector->getSessionUsages()); + + $collector->reset(); + + $session = $this->createMock(SessionInterface::class); + $session->method('getMetadataBag')->willReturnCallback(static function () use ($collector) { + $collector->collectSessionUsage(); + }); + $session->getMetadataBag(); + + $collector->collect($request, $this->createResponse()); + $collector->lateCollect(); + + $usages = $collector->getSessionUsages(); + + $this->assertCount(1, $usages); + $this->assertSame(__FILE__, $usages[0]['file']); + $this->assertSame(__LINE__ - 9, $line = $usages[0]['line']); + + $trace = $usages[0]['trace']; + $this->assertSame('getMetadataBag', $trace[0]['function']); + $this->assertSame(self::class, $class = $trace[1]['class']); + + $this->assertSame(sprintf('%s:%s', $class, $line), $usages[0]['name']); + } + + public function testStatelessCheck() + { + $requestStack = new RequestStack(); + $request = $this->createRequest(); + $requestStack->push($request); + + $collector = new RequestDataCollector($requestStack); + $collector->collect($request, $response = $this->createResponse()); + $collector->lateCollect(); + + $this->assertFalse($collector->getStatelessCheck()); + + $requestStack = new RequestStack(); + $request = $this->createRequest(); + $request->attributes->set('_stateless', true); + $requestStack->push($request); + + $collector = new RequestDataCollector($requestStack); + $collector->collect($request, $response = $this->createResponse()); + $collector->lateCollect(); + + $this->assertTrue($collector->getStatelessCheck()); + } + protected function createRequest($routeParams = ['name' => 'foo']) { $request = Request::create('http://test.com/foo?bar=baz'); diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php index 8df2ce51698e..36183d3c138b 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php @@ -20,6 +20,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; @@ -260,9 +261,13 @@ public function testSessionUsageCallbackWhenDebugAndStateless() $requestStack->push($request); $requestStack->push(new Request()); + $collector = $this->createMock(RequestDataCollector::class); + $collector->expects($this->once())->method('collectSessionUsage'); + $container = new Container(); $container->set('initialized_session', $session); $container->set('request_stack', $requestStack); + $container->set('session_collector', \Closure::fromCallable([$collector, 'collectSessionUsage'])); $this->expectException(UnexpectedSessionUsageException::class); (new SessionListener($container, true))->onSessionUsage(); @@ -277,12 +282,16 @@ public function testSessionUsageCallbackWhenNoDebug() $request = new Request(); $request->attributes->set('_stateless', true); - $requestStack = $this->getMockBuilder(RequestStack::class)->getMock(); - $requestStack->expects($this->never())->method('getMasterRequest')->willReturn($request); + $requestStack = new RequestStack(); + $requestStack->push($request); + + $collector = $this->createMock(RequestDataCollector::class); + $collector->expects($this->never())->method('collectSessionUsage'); $container = new Container(); $container->set('initialized_session', $session); $container->set('request_stack', $requestStack); + $container->set('session_collector', $collector); (new SessionListener($container))->onSessionUsage(); } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy