8000 feature #26681 Allow to easily ask Symfony not to set a response to p… · symfony/http-kernel@0d35d0f · GitHub
[go: up one dir, main page]

Skip to content

Commit 0d35d0f

Browse 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 (symfony/symfony#25583, symfony/symfony#25699, symfony/symfony#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 8eece57 + 0b90a7b commit 0d35d0f

File tree

2 files changed

+47
-6
lines changed

2 files changed

+47
-6
lines changed

EventListener/AbstractSessionListener.php

Lines 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.

Tests/EventListener/SessionListenerTest.php

Lines 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