Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f62a6d7

Browse filesBrowse files
committed
feature #26681 Allow to easily ask Symfony not to set a response to private automatically (Toflar)
This PR was squashed before being merged into the 4.1-dev branch (closes #26681). Discussion ---------- Allow to easily ask Symfony not to set a response to private automatically | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | | License | MIT | Doc PR | This PR is related to the many discussions going on about the latest (Symfony 3.4+) changes regarding the way Symfony handles the session. I think we're almost there now, Symfony 4.1 automatically turns responses into `private` responses once the session is started and it's all done in the `AbstractSessionListener` where the session is also saved. In other issues/PRs (#25583, #25699, #24988) it was agreed that setting the response to `private` if the session is started is a good default for Symfony. It was also agreed that setting it to `private` does not always make sense because you **can share a response** across sessions, it just requires a more complex caching setup with shared user context etc. So there must be an easy way to disable this behaviour. Right now it's very hard to do so because what you end up doing is basically decorating the `session_listener` which is very hard because you have to keep track on that over different Symfony versions as the base listener might get additional features etc. The [FOSCacheBundle](FriendsOfSymfony/FOSHttpCacheBundle#438) is already having this problem, [Contao](contao/core-bundle#1388) has the same issue and there will be probably more. Basically everyone that wants to share a response cache across the session will have to decorate the default listener. That's just too hard, so I came up with this solution. The header is easy. Every project can add that easily. It does not require any extension, configuration or adjustment of any service. It's clean, transparent and has absolutely no impact on "default" Symfony setups. Would be happy to have some feedback before 4.1 freeze. Commits ------- 0f36710 Allow to easily ask Symfony not to set a response to private automatically
2 parents 6f15386 + 0f36710 commit f62a6d7
Copy full SHA for f62a6d7

File tree

2 files changed

+47
-6
lines changed
Filter options

2 files changed

+47
-6
lines changed

‎src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php
+21-5Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,22 @@
2020
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
2121

2222
/**
23-
* Sets the session in the request.
23+
* Sets the session onto the request on the "kernel.request" event and saves
24+
* it on the "kernel.response" event.
25+
*
26+
* In addition, if the session has been started it overrides the Cache-Control
27+
* header in such a way that all caching is disabled in that case.
28+
* If you have a scenario where caching responses with session information in
29+
* them makes sense, you can disable this behaviour by setting the header
30+
* AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER on the response.
2431
*
2532
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
2633
* @author Tobias Schultze <http://tobion.de>
2734
*/
2835
abstract class AbstractSessionListener implements EventSubscriberInterface
2936
{
37+
const NO_AUTO_CACHE_CONTROL_HEADER = 'Symfony-Session-NoAutoCacheControl';
38+
3039
protected $container;
3140

3241
public function __construct(ContainerInterface $container = null)
@@ -60,13 +69,20 @@ public function onKernelResponse(FilterResponseEvent $event)
6069
return;
6170
}
6271

72+
$response = $event->getResponse();
73+
6374
if ($session->isStarted() || ($session instanceof Session && $session->hasBeenStarted())) {
64-
$event->getResponse()
65-
->setPrivate()
66-
->setMaxAge(0)
67-
->headers->addCacheControlDirective('must-revalidate');
75+
if (!$response->headers->has(self::NO_AUTO_CACHE_CONTROL_HEADER)) {
76+
$response
77+
->setPrivate()
78+
->setMaxAge(0)
79+
->headers->addCacheControlDirective('must-revalidate');
80+
}
6881
}
6982

83+
// Always remove the internal header if present
84+
$response->headers->remove(self::NO_AUTO_CACHE_CONTROL_HEADER);
85+
7086
if ($session->isStarted()) {
7187
/*
7288
* Saves the session, in case it is still open, before sending the response/headers.

‎src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php
+26-1Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public function testSessionIsSet()
5656
$this->assertSame($session, $request->getSession());
5757
}
5858

59-
public function testResponseIsPrivate()
59+
public function testResponseIsPrivateIfSessionStarted()
6060
{
6161
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
6262
$session->expects($this->exactly(2))->method('isStarted')->willReturn(false);
@@ -74,6 +74,31 @@ public function testResponseIsPrivate()
7474
$this->assertTrue($response->headers->hasCacheControlDirective('private'));
7575
$this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate'));
7676
$this->assertSame('0', $response->headers->getCacheControlDirective('max-age'));
77+
$this->assertFalse($response->headers->has(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER));
78+
}
79+
80+
public function testResponseIsStillPublicIfSessionStartedAndHeaderPresent()
81+
{
82+
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
83+
$session->expects($this->exactly(2))->method('isStarted')->willReturn(false);
84+
$session->expects($this->once())->method('hasBeenStarted')->willReturn(true);
85+
86+
$container = new Container();
87+
$container->set('initialized_session', $session);
88+
89+
$listener = new SessionListener($container);
90+
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->disableOriginalConstructor()->getMock();
91+
92+
$response = new Response();
93+
$response->setSharedMaxAge(60);
94+
$response->headers->set(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER, 'true');
95+
$listener->onKernelResponse(new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response));
96+
97+
$this->assertTrue($response->headers->hasCacheControlDirective('public'));
98+
$this->assertFalse($response->headers->hasCacheControlDirective('private'));
99+
$this->assertFalse($response->headers->hasCacheControlDirective('must-revalidate'));
100+
$this->assertSame('60', $response->headers->getCacheControlDirective('s-maxage'));
101+
$this->assertFalse($response->headers->has(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER));
77102
}
78103

79104
public function testUninitilizedSession()

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.