10000 [HttpFoundation] Add a way to avoid the session be written at each re… · symfony/symfony@38f02ea · GitHub
[go: up one dir, main page]

Skip to content

Commit 38f02ea

Browse files
committed
[HttpFoundation] Add a way to avoid the session be written at each request
1 parent c886612 commit 38f02ea

File tree

7 files changed

+259
-4
lines changed

7 files changed

+259
-4
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,10 @@ private function addSessionSection(ArrayNodeDefinition $rootNode)
222222
->scalarNode('gc_probability')->end()
223223
->scalarNode('gc_maxlifetime')->end()
224224
->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end()
225+
->integerNode('metadata_update_threshold')
226+
->defaultValue('0')
227+
->info('seconds to wait between 2 session metadata updates, it will also prevent the session handler to write if the session has not changed')
228+
->end()
225229
->end()
226230
->end()
227231
->end()

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,14 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c
342342
$container->getDefinition('session.storage.native')->replaceArgument(1, null);
343343
$container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null);
344344
} else {
345-
$container->setAlias('session.handler', $config['handler_id']);
345+
$handlerId = $config['handler_id'];
346+
347+
if ($config['metadata_update_threshold'] > 0) {
348+
$container->getDefinition('session.handler.write_check')->addArgument(new Reference($handlerId));
349+
$handlerId = 'session.handler.write_check';
350+
}
351+
352+
$container->setAlias('session.handler', $handlerId);
346353
}
347354

348355
$container->setParameter('session.save_path', $config['save_path']);
@@ -362,6 +369,8 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c
362369
$container->findDefinition('session.storage')->getClass(),
363370
));
364371
}
372+
373+
$container->setParameter('session.metadata.update_threshold', $config['metadata_update_threshold']);
365374
}
366375

367376
/**

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88
<parameter key="session.class">Symfony\Component\HttpFoundation\Session\Session</parameter>
99
<parameter key="session.flashbag.class">Symfony\Component\HttpFoundation\Session\Flash\FlashBag</parameter>
1010
<parameter key="session.attribute_bag.class">Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag</parameter>
11+
<parameter key="session.storage.metadata_bag.class">Symfony\Component\HttpFoundation\Session\Storage\MetadataBag</parameter>
12+
<parameter key="session.metadata.storage_key">_sf2_meta</parameter>
1113
<parameter key="session.storage.native.class">Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage</parameter>
1214
<parameter key="session.storage.php_bridge.class">Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage</parameter>
1315
<parameter key="session.storage.mock_file.class">Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage</parameter>
1416
<parameter key="session.handler.native_file.class">Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler</parameter>
17+
<parameter key="session.handler.write_check.class">Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler</parameter>
1518
<parameter key="session_listener.class">Symfony\Bundle\FrameworkBundle\EventListener\SessionListener</parameter>
1619
</parameters>
1720

@@ -22,13 +25,20 @@
2225
<argument type="service" id="session.flash_bag" />
2326
</service>
2427

28+
<service id="session.storage.metadata_bag" class="%session.storage.metadata_bag.class%" public="false">
29+
<argument>%session.metadata.storage_key%</argument>
30+
<argument>%session.metadata.update_threshold%</argument>
31+
</service>
32+
2533
<service id="session.storage.native" class="%session.storage.native.class%">
2634
<argument>%session.storage.options%</argument>
2735
<argument typ F438 e="service" id="session.handler" />
36+
<argument type="service" id="session.storage.metadata_bag" />
2837
</service>
2938

3039
<service id="session.storage.php_bridge" class="%session.storage.php_bridge.class%">
3140
<argument type="service" id="session.handler" />
41+
<argument type="service" id="session.storage.metadata_bag" />
3242
</service>
3343

3444
<service id="session.flash_bag" class="%session.flashbag.class%" public="false" />
@@ -37,12 +47,16 @@
3747

3848
<service id="session.storage.mock_file" class="%session.storage.mock_file.class%" public="false">
3949
<argument>%kernel.cache_dir%/sessions</argument>
50+
<argument>MOCKSESSID</argument>
51+
<argument type="service" id="session.storage.metadata_bag" />
4052
</service>
4153

4254
<service id="session.handler.native_file" class="%session.handler.native_file.class%" public="false">
4355
<argument>%session.save_path%</argument>
4456
</service>
4557

58+
<service id="session.handler.write_check" class="%session.handler.write_check.class%" public="false" />
59+
4660
<service id="session_listener" class="%session_listener.class%">
4761
<tag name="kernel.event_subscriber" />
4862
<argument type="service" id="service_container" />
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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\Storage\Handler;
13+
14+
/**
15+
* Wraps another SessionHandlerInterface to only write the session when it has been modified.
16+
*
17+
* @author Adrien Brault <adrien.brault@gmail.com>
18+
*/
19+
class WriteCheckSessionHandler implements \SessionHandlerInterface
20+
{
21+
/**
22+
* @var \SessionHandlerInterface
23+
*/
24+
private $wrappedSessionHandler;
25+
26+
/**
27+
* @var array sessionId => session
28+
*/
29+
private $readSessions;
30+
31+
public function __construct(\SessionHandlerInterface $wrappedSessionHandler)
32+
{
33+
$this->wrappedSessionHandler = $wrappedSessionHandler;
34+
}
35+
36+
/**
37+
* {@inheritdoc}
38+
*/
39+
public function close()
40+
{
41+
return $this->wrappedSessionHandler->close();
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public function destroy($sessionId)
48+
{
49+
return $this->wrappedSessionHandler->destroy($sessionId);
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
public function gc($maxLifetime)
56+
{
57+
return $this->wrappedSessionHandler->gc($maxLifetime);
58+
}
59+
60+
/**
61+
* {@inheritdoc}
62+
*/
63+
public function open($savePath, $sessionId)
64+
{
65+
return $this->wrappedSessionHandler->open($savePath, $sessionId);
66+
}
67+
68+
/**
69+
* {@inheritdoc}
70+
*/
71+
public function read($sessionId)
72+
{
73+
$session = $this->wrappedSessionHandler->read($sessionId);
74+
75+
$this->readSessions[$sessionId] = $session;
76+
77+
return $session;
78+
}
79+
80+
/**
81+
* {@inheritdoc}
82+
*/
83+
public function write($sessionId, $sessionData)
84+
{
85+
if (isset($this->readSessions[$sessionId]) && $sessionData === $this->readSessions[$sessionId]) {
86+
return true;
87+
}
88+
89+
return $this->wrappedSessionHandler->write($sessionId, $sessionData);
90+
}
91+
}

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,21 @@ class MetadataBag implements SessionBagInterface
4848
*/
4949
private $lastUsed;
5050

51+
/**
52+
* @var integer
53+
*/
54+
private $updateThreshold;
55+
5156
/**
5257
* Constructor.
5358
*
54-
* @param string $storageKey The key used to store bag in the session.
59+
* @param string $storageKey The key used to store bag in the session.
60+
* @param integer $updateThreshold The time to wait between two UPDATED updates
5561
*/
56-
public function __construct($storageKey = '_sf2_meta')
62+
public function __construct($storageKey = '_sf2_meta', $updateThreshold = 0)
5763
{
5864
$this->storageKey = $storageKey;
65+
$this->updateThreshold = $updateThreshold;
5966
$this->meta = array(self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0);
6067
}
6168

@@ -68,7 +75,11 @@ public function initialize(array &$array)
6875

6976
if (isset($array[self::CREATED])) {
7077
$this->lastUsed = $this->meta[self::UPDATED];
71-
$this->meta[self::UPDATED] = time();
78+
79+
$timeStamp = time();
80+
if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) {
81+
$this->meta[self::UPDATED] = $timeStamp;
82+
}
7283
} else {
7384
$this->stampCreated();
7485
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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\Tests\Session\Storage\Handler;
13+
14+
use Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler;
15+
16+
/**
17+
* @author Adrien Brault <adrien.brault@gmail.com>
18+
*/
19+
class WriteCheckSessionHandlerTest extends \PHPUnit_Framework_TestCase
20+
{
21+
public function test()
22+
{
23+
$wrappedSessionHandlerMock = $this->getMock('SessionHandlerInterface');
24+
$writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
25+
26+
$wrappedSessionHandlerMock
27+
->expects($this->once())
28+
->method('close')
29+
->with()
30+
->will($this->returnValue(true))
31+
;
32+
33+
$this->assertEquals(true, $writeCheckSessionHandler->close());
34+
}
35+
36+
public function testWrite()
37+
{
38+
$wrappedSessionHandlerMock = $this->getMock('SessionHandlerInterface');
39+
$writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
40+
41+
$wrappedSessionHandlerMock
42+
->expects($this->once())
43+
->method('write')
44+
->with('foo', 'bar')
45+
->will($this->returnValue(true))
46+
;
47+
48+
$this->assertEquals(true, $writeCheckSessionHandler->write('foo', 'bar'));
49+
}
50+
51+
public function testSkippedWrite()
52+
{
53+
$wrappedSessionHandlerMock = $this->getMock('SessionHandlerInterface');
54+
$writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
55+
56+
$wrappedSessionHandlerMock
57+
->expects($this->once())
58+
->method('read')
59+
->with('foo')
60+
->will($this->returnValue('bar'))
61+
;
62+
63+
$wrappedSessionHandlerMock
64+
->expects($this->never())
65+
->method('write')
66+
;
67+
68+
$this->assertEquals('bar', $writeCheckSessionHandler->read('foo'));
69+
$this->assertEquals(true, $writeCheckSessionHandler->write('foo', 'bar'));
70+
}
71+
72+
public function testNonSkippedWrite()
73+
{
74+
$wrappedSessionHandlerMock = $this->getMock('SessionHandlerInterface');
75+
$writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
76+
77+
$wrappedSessionHandlerMock
78+
->expects($this->once())
79+
->method('read')
80+
->with('foo')
81+
->will($this->returnValue('bar'))
82+
;
83+
84+
$wrappedSessionHandlerMock
85+
->expects($this->once())
86+
->method('write')
87+
->with('foo', 'baZZZ')
88+
->will($this->returnValue(true))
89+
;
90+
91+
$this->assertEquals('bar', $writeCheckSessionHandler->read('foo'));
92+
$this->assertEquals(true, $writeCheckSessionHandler->write('foo', 'baZZZ'));
93+
}
94+
}

src/Symfony/Component/HttpFoundation/Tests/Session/Storage/MetadataBagTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,36 @@ public function testClear()
100100
{
101101
$this->bag->clear();
102102
}
103+
104+
public function testSkipLastUsedUpdate()
105+
{
106+
$bag = new MetadataBag('', 30);
107+
$timeStamp = time();
108+
109+
$created = $timeStamp - 15;
110+
$sessionMetadata = array(
111+
MetadataBag::CREATED => $created,
112+
MetadataBag::UPDATED => $created,
113+
MetadataBag::LIFETIME => 1000
114+
);
115+
$bag->initialize($sessionMetadata);
116+
117+
$this->assertEquals($created, $sessionMetadata[MetadataBag::UPDATED]);
118+
}
119+
120+
public function testDoesNotSkipLastUsedUpdate()
121+
{
122+
$bag = new MetadataBag('', 30);
123+
$timeStamp = time();
124+
125+
$created = $timeStamp - 45;
126+
$sessionMetadata = array(
127+
MetadataBag::CREATED => $created,
128+
MetadataBag::UPDATED => $created,
129+
MetadataBag::LIFETIME => 1000
130+
);
131+
$bag->initialize($sessionMetadata);
132+
133+
$this->assertEquals($timeStamp, $sessionMetadata[MetadataBag::UPDATED]);
134+
}
103135
}

0 commit comments

Comments
 (0)
0