Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

[HttpFoundation] Add a way to avoid the session be written at each re…

…quest
  • Loading branch information...
commit 191418d24dd9c3dbe37eb5b566eced1b3bac1212 1 parent 5ebaad3
@adrienbrault adrienbrault authored
View
4 src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
@@ -222,6 +222,10 @@ private function addSessionSection(ArrayNodeDefinition $rootNode)
->scalarNode('gc_probability')->end()
->scalarNode('gc_maxlifetime')->end()
->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end()
+ ->integerNode('metadata_update_threshold')
+ ->defaultValue('0')
+ ->info('time to wait between 2 session metadata updates, it will also prevent the session handler to write if the session has not changed')
+ ->end()
->end()
->end()
->end()
View
11 src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -333,7 +333,14 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c
$container->getDefinition('session.storage.native')->replaceArgument(1, null);
$container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null);
} else {
- $container->setAlias('session.handler', $config['handler_id']);
+ $handlerId = $config['handler_id'];
+
+ if ($config['metadata_update_threshold'] > 0) {
+ $container->getDefinition('session.handler.write_check')->addArgument(new Reference($handlerId));
+ $handlerId = 'session.handler.write_check';
+ }
+
+ $container->setAlias('session.handler', $handlerId);
}
$container->setParameter('session.save_path', $config['save_path']);
@@ -353,6 +360,8 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c
$container->findDefinition('session.storage')->getClass(),
));
}
+
+ $container->setParameter('session.metadata.update_threshold', $config['metadata_update_threshold']);
}
/**
View
14 src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml
@@ -8,10 +8,13 @@
<parameter key="session.class">Symfony\Component\HttpFoundation\Session\Session</parameter>
<parameter key="session.flashbag.class">Symfony\Component\HttpFoundation\Session\Flash\FlashBag</parameter>
<parameter key="session.attribute_bag.class">Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag</parameter>
+ <parameter key="session.storage.metadata_bag.class">Symfony\Component\HttpFoundation\Session\Storage\MetadataBag</parameter>
+ <parameter key="session.metadata.storage_key">_sf2_meta</parameter>
<parameter key="session.storage.native.class">Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage</parameter>
<parameter key="session.storage.php_bridge.class">Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage</parameter>
<parameter key="session.storage.mock_file.class">Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage</parameter>
<parameter key="session.handler.native_file.class">Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler</parameter>
+ <parameter key="session.handler.write_check.class">Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler</parameter>
<parameter key="session_listener.class">Symfony\Bundle\FrameworkBundle\EventListener\SessionListener</parameter>
</parameters>
@@ -22,13 +25,20 @@
<argument type="service" id="session.flash_bag" />
</service>
+ <service id="session.storage.metadata_bag" class="%session.storage.metadata_bag.class%" public="false">
+ <argument>%session.metadata.storage_key%</argument>
+ <argument>%session.metadata.update_threshold%</argument>
+ </service>
+
<service id="session.storage.native" class="%session.storage.native.class%">
<argument>%session.storage.options%</argument>
<argument type="service" id="session.handler" />
+ <argument type="service" id="session.storage.metadata_bag" />
</service>
<service id="session.storage.php_bridge" class="%session.storage.php_bridge.class%">
<argument type="service" id="session.handler" />
+ <argument type="service" id="session.storage.metadata_bag" />
</service>
<service id="session.flash_bag" class="%session.flashbag.class%" public="false" />
@@ -37,12 +47,16 @@
<service id="session.storage.mock_file" class="%session.storage.mock_file.class%" public="false">
<argument>%kernel.cache_dir%/sessions</argument>
+ <argument>MOCKSESSID</argument>
+ <argument type="service" id="session.storage.metadata_bag" />
</service>
<service id="session.handler.native_file" class="%session.handler.native_file.class%" public="false">
<argument>%session.save_path%</argument>
</service>
+ <service id="session.handler.write_check" class="%session.handler.write_check.class%" public="false" />
+
<service id="session_listener" class="%session_listener.class%">
<tag name="kernel.event_subscriber" />
<argument type="service" id="service_container" />
View
91 src/Symfony/Component/HttpFoundation/Session/Storage/Handler/WriteCheckSessionHandler.php
@@ -0,0 +1,91 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Wraps another SessionHandlerInterface to only write the session when it has been modified.
+ *
+ * @author Adrien Brault <adrien.brault@gmail.com>
+ */
+class WriteCheckSessionHandler implements \SessionHandlerInterface
+{
+ /**
+ * @var \SessionHandlerInterface
+ */
+ private $wrappedSessionHandler;
+
+ /**
+ * @var array sessionId => session
+ */
+ private $readSessions;
+
+ public function __construct(\SessionHandlerInterface $wrappedSessionHandler)
+ {
+ $this->wrappedSessionHandler = $wrappedSessionHandler;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return $this->wrappedSessionHandler->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ return $this->wrappedSessionHandler->destroy($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxLifetime)
+ {
+ return $this->wrappedSessionHandler->gc($maxLifetime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionId)
+ {
+ return $this->wrappedSessionHandler->open($savePath, $sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ $session = $this->wrappedSessionHandler->read($sessionId);
+
+ $this->readSessions[$sessionId] = $session;
+
+ return $session;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($sessionId, $sessionData)
+ {
+ if (isset($this->readSessions[$sessionId]) && $sessionData === $this->readSessions[$sessionId]) {
+ return true;
+ }
+
+ return $this->wrappedSessionHandler->write($sessionId, $sessionData);
+ }
+}
View
17 src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php
@@ -49,13 +49,20 @@ class MetadataBag implements SessionBagInterface
private $lastUsed;
/**
+ * @var integer
+ */
+ private $updateThreshold;
+
+ /**
* Constructor.
*
- * @param string $storageKey The key used to store bag in the session.
+ * @param string $storageKey The key used to store bag in the session.
+ * @param integer $updateThreshold The time to wait between two UPDATED updates
*/
- public function __construct($storageKey = '_sf2_meta')
+ public function __construct($storageKey = '_sf2_meta', $updateThreshold = 0)
{
$this->storageKey = $storageKey;
+ $this->updateThreshold = $updateThreshold;
$this->meta = array(self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0);
}
@@ -68,7 +75,11 @@ public function initialize(array &$array)
if (isset($array[self::CREATED])) {
$this->lastUsed = $this->meta[self::UPDATED];
- $this->meta[self::UPDATED] = time();
+
+ $timeStamp = time();
+ if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) {
+ $this->meta[self::UPDATED] = $timeStamp;
+ }
} else {
$this->stampCreated();
}
View
94 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php
@@ -0,0 +1,94 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler;
+
+/**
+ * @author Adrien Brault <adrien.brault@gmail.com>
+ */
+class WriteCheckSessionHandlerTest extends \PHPUnit_Framework_TestCase
+{
+ public function test()
+ {
+ $wrappedSessionHandlerMock = $this->getMock('SessionHandlerInterface');
+ $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('close')
+ ->with()
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertEquals(true, $writeCheckSessionHandler->close());
+ }
+
+ public function testWrite()
+ {
+ $wrappedSessionHandlerMock = $this->getMock('SessionHandlerInterface');
+ $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo', 'bar')
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertEquals(true, $writeCheckSessionHandler->write('foo', 'bar'));
+ }
+
+ public function testSkippedWrite()
+ {
+ $wrappedSessionHandlerMock = $this->getMock('SessionHandlerInterface');
+ $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('read')
+ ->with('foo')
+ ->will($this->returnValue('bar'))
+ ;
+
+ $wrappedSessionHandlerMock
+ ->expects($this->never())
+ ->method('write')
+ ;
+
+ $this->assertEquals('bar', $writeCheckSessionHandler->read('foo'));
+ $this->assertEquals(true, $writeCheckSessionHandler->write('foo', 'bar'));
+ }
+
+ public function testNonSkippedWrite()
+ {
+ $wrappedSessionHandlerMock = $this->getMock('SessionHandlerInterface');
+ $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('read')
+ ->with('foo')
+ ->will($this->returnValue('bar'))
+ ;
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo', 'baZZZ')
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertEquals('bar', $writeCheckSessionHandler->read('foo'));
+ $this->assertEquals(true, $writeCheckSessionHandler->write('foo', 'baZZZ'));
+ }
+}
View
32 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/MetadataBagTest.php
@@ -104,4 +104,36 @@ public function testClear()
{
$this->bag->clear();
}
+
+ public function testSkipLastUsedUpdate()
+ {
+ $bag = new MetadataBag('', 30);
+ $timeStamp = time();
+
+ $created = $timeStamp - 15;
+ $sessionMetadata = array(
+ MetadataBag::CREATED => $created,
+ MetadataBag::UPDATED => $created,
+ MetadataBag::LIFETIME => 1000
+ );
+ $bag->initialize($sessionMetadata);
+
+ $this->assertEquals($created, $sessionMetadata[MetadataBag::UPDATED]);
+ }
+
+ public function testDoesNotSkipLastUsedUpdate()
+ {
+ $bag = new MetadataBag('', 30);
+ $timeStamp = time();
+
+ $created = $timeStamp - 45;
+ $sessionMetadata = array(
+ MetadataBag::CREATED => $created,
+ MetadataBag::UPDATED => $created,
+ MetadataBag::LIFETIME => 1000
+ );
+ $bag->initialize($sessionMetadata);
+
+ $this->assertEquals($timeStamp, $sessionMetadata[MetadataBag::UPDATED]);
+ }
}
Please sign in to comment.
Something went wrong with that request. Please try again.