8000 bug #25220 [HttpFoundation] Add Session::isEmpty(), fix MockFileSessi… · symfony/symfony@679eebb · GitHub
[go: up one dir, main page]

Skip to content

Commit 679eebb

Browse files
bug #25220 [HttpFoundation] Add Session::isEmpty(), fix MockFileSessionStorage to behave like the native one (nicolas-grekas)
This PR was merged into the 3.4 branch. Discussion ---------- [HttpFoundation] Add Session::isEmpty(), fix MockFileSessionStorage to behave like the native one | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - MockFileSessionStorage should not create any file when the session is empty. Like the native session storage, it should ignore the metadataBag to decide if the session is empty. And to prevent AbstractTestSessionListener from registered a wrong cookie, it must have access to this empty state, which is now possible thanks to a new `Session::isEmpty()` method. Implementing is requires access to the internal storage of bags, which is possible via an internal proxy. Commits ------- 56846ac [HttpFoundation] Add Session::isEmpty(), fix MockFileSessionStorage to behave like the native one
2 parents 7582b1a + 56846ac commit 679eebb

File tree

8 files changed

+164
-9
lines changed

8 files changed

+164
-9
lines changed

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ abstract class AbstractFactory implements SecurityFactoryInterface
2929
protected $options = array(
3030
'check_path' => '/login_check',
3131
'use_forward' => false,
32-
'require_previous_session' => true,
32+
'require_previous_session' => false,
3333
);
3434

3535
protected $defaultSuccessHandlerOptions = array(

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ public function __construct()
2828
$this->addOption('password_path', 'password');
2929
$this->defaultFailureHandlerOptions = array();
3030
$this->defaultSuccessHandlerOptions = array();
31-
$this->options['require_previous_session'] = false;
3231
}
3332

3433
/**

src/Symfony/Component/HttpFoundation/Session/Session.php

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
2828

2929
private $flashName;
3030
private $attributeName;
31+
private $data = array();
3132

3233
/**
3334
* @param SessionStorageInterface $storage A SessionStorageInterface instance
@@ -108,7 +109,7 @@ public function remove($name)
108109
*/
109110
public function clear()
110111
{
111-
$this->storage->getBag($this->attributeName)->clear();
112+
$this->getAttributeBag()->clear();
112113
}
113114

114115
/**
@@ -139,6 +140,22 @@ public function count()
139140
return count($this->getAttributeBag()->all());
140141
}
141142

143+
/**
144+
* @return bool
145+
*
146+
* @internal
147+
*/
148+
public function isEmpty()
149+
{
150+
foreach ($this->data as &$data) {
151+
if (!empty($data)) {
152+
return false;
153+
}
154+
}
155+
156+
return true;
157+
}
158+
142159
/**
143160
* {@inheritdoc}
144161
*/
@@ -210,15 +227,15 @@ public function getMetadataBag()
210227
*/
211228
public function registerBag(SessionBagInterface $bag)
212229
{
213-
$this->storage->registerBag($bag);
230+
$this->storage->registerBag(new SessionBagProxy($bag, $this->data));
214231
}
215232

216233
/**
217234
* {@inheritdoc}
218235
*/
219236
public function getBag($name)
220237
{
221-
return $this->storage->getBag($name);
238+
return $this->storage->getBag($name)->getBag();
222239
}
223240

224241
/**
@@ -240,6 +257,6 @@ public function getFlashBag()
240257
*/
241258
private function getAttributeBag()
242259
{
243-
return $this->storage->getBag($this->attributeName);
260+
return $this->storage->getBag($this->attributeName)->getBag();
244261
}
245262
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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\HttpFoundation\Session;
13+
14+
/**
15+
* @author Nicolas Grekas <p@tchwork.com>
16+
*
17+
* @internal
18+
*/
19+
final class SessionBagProxy implements SessionBagInterface
20+
{
21+
private $bag;
22+
private $data;
23+
24+
public function __construct(SessionBagInterface $bag, array &$data)
25+
{
26+
$this->bag = $bag;
27+
$this->data = &$data;
28+
}
29+
30+
/**
31+
* @return SessionBagInterface
32+
*/
33+
public function getBag()
34+
{
35+
return $this->bag;
36+
}
37+
38+
/**
39+
* @return bool
40+
*/
41+
public function isEmpty()
42+
{
43+
return empty($this->data[$this->bag->getStorageKey()]);
44+
}
45+
46+
/**
47+
* {@inheritdoc}
48+
*/
49+
public function getName()
50+
{
51+
return $this->bag->getName();
52+
}
53+
54+
/**
55+
* {@inheritdoc}
56+
*/
57+
public function initialize(array &$array)
58+
{
59+
$this->data[$this->bag->getStorageKey()] = &$array;
60+
61+
$this->bag->initialize($array);
62+
}
63+
64+
/**
65+
* {@inheritdoc}
66+
*/
67+
public function getStorageKey()
68+
{
69+
return $this->bag->getStorageKey();
70+
}
71+
72+
/**
73+
* {@inheritdoc}
74+
*/
75+
public function clear()
76+
{
77+
return $this->bag->clear();
78+
}
79+
}

src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff F987 line numberDiff line change
@@ -91,7 +91,26 @@ public function save()
9191
throw new \RuntimeException('Trying to save a session that was not started yet or was already closed');
9292
}
9393

94-
file_put_contents($this->getFilePath(), serialize($this->data));
94+
$data = $this->data;
95+
96+
foreach ($this->bags as $bag) {
97+
if (empty($data[$key = $bag->getStorageKey()])) {
98+
unset($data[$key]);
99+
}
100+
}
101+
if (array($key = $this->metadataBag->getStorageKey()) === array_keys($data)) {
102+
unset($data[$key]);
103+
}
104+
105+
try {
106+
if ($data) {
107+
file_put_contents($this->getFilePath(), serialize($data));
108+
} else {
109+
$this->destroy();
110+
}
111+
} finally {
112+
$this->data = $data;
113+
}
95114

96115
// this is needed for Silex, where the session object is re-used across requests
97116
// in functional tests. In Symfony, the container is rebooted, so we don't have

src/Symfony/Component/HttpFoundation/Tests/Session/SessionTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,4 +221,22 @@ public function testGetMeta()
221221
{
222222
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\MetadataBag', $this->session->getMetadataBag());
223223
}
224+
225+
public function testIsEmpty()
226+
{
227+
$this->assertTrue($this->session->isEmpty());
228+
229+
$this->session->set('hello', 'world');
230+
$this->assertFalse($this->session->isEmpty());
231+
232+
$this->session->remove('hello');
233+
$this->assertTrue($this->session->isEmpty());
234+
235+
$flash = $this->session->getFlashBag();
236+
$flash->set('hello', 'world');
237+
$this->assertFalse($this->session->isEmpty());
238+
239+
$flash->get('hello');
240+
$this->assertTrue($this->session->isEmpty());
241+
}
224242
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\HttpKernel\EventListener;
1313

1414
use Symfony\Component\HttpFoundation\Cookie;
15+
use Symfony\Component\HttpFoundation\Session\Session;
1516
use Symfony\Component\HttpFoundation\Session\SessionInterface;
1617
use Symfony\Component\HttpKernel\KernelEvents;
1718
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
@@ -60,8 +61,10 @@ public function onKernelResponse(FilterResponseEvent $event)
6061
$session = $event->getRequest()->getSession();
6162
if ($session && $session->isStarted()) {
6263
$session->save();
63-
$params = session_get_cookie_params();
64-
$event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly']));
64+
if (!$session instanceof Session || !\method_exists($session, 'isEmpty') || !$session->isEmpty()) {
65+
$params = session_get_cookie_params();
66+
$event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly']));
67+
}
6568
}
6669
}
6770

src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,19 @@ public function testDoesNotDeleteCookieIfUsingSessionLifetime()
7373
$this->assertEquals(0, reset($cookies)->getExpiresTime());
7474
}
7575

76+
/**
77+
* @requires function \Symfony\Component\HttpFoundation\Session\Session::isEmpty
78+
*/
79+
public function testEmptySessionDoesNotSendCookie()
80+
{
81+
$this->sessionHasBeenStarted();
82+
$this->sessionIsEmpty();
83+
84+
$response = $this->filterResponse(new Request(), HttpKernelInterface::MASTER_REQUEST);
85+
86+
$this->assertSame(array(), $response->headers->getCookies());
87+
}
88+
7689
public function testUnstartedSessionIsNotSave()
7790
{
7891
$this->sessionHasNotBeenStarted();
@@ -130,6 +143,13 @@ private function sessionHasNotBeenStarted()
130143
->will($this->returnValue(false));
131144
}
132145

146+
private function sessionIsEmpty()
147+
{
148+
$this->session->expects($this->once())
149+
->method('isEmpty')
150+
->will($this->returnValue(true));
151+
}
152+
133153
private function getSession()
134154
{
135155
$mock = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\Session')

0 commit comments

Comments
 (0)
0