[HttpFoundation] Add a way to avoid the session be written at each request
This commit is contained in:
parent
c886612c99
commit
38f02eacbf
@ -222,6 +222,10 @@ class Configuration implements ConfigurationInterface
|
|||||||
->scalarNode('gc_probability')->end()
|
->scalarNode('gc_probability')->end()
|
||||||
->scalarNode('gc_maxlifetime')->end()
|
->scalarNode('gc_maxlifetime')->end()
|
||||||
->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end()
|
->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end()
|
||||||
|
->integerNode('metadata_update_threshold')
|
||||||
|
->defaultValue('0')
|
||||||
|
->info('seconds 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()
|
->end()
|
||||||
->end()
|
->end()
|
||||||
|
@ -342,7 +342,14 @@ class FrameworkExtension extends Extension
|
|||||||
$container->getDefinition('session.storage.native')->replaceArgument(1, null);
|
$container->getDefinition('session.storage.native')->replaceArgument(1, null);
|
||||||
$container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null);
|
$container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null);
|
||||||
} else {
|
} 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']);
|
$container->setParameter('session.save_path', $config['save_path']);
|
||||||
@ -362,6 +369,8 @@ class FrameworkExtension extends Extension
|
|||||||
$container->findDefinition('session.storage')->getClass(),
|
$container->findDefinition('session.storage')->getClass(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$container->setParameter('session.metadata.update_threshold', $config['metadata_update_threshold']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,10 +8,13 @@
|
|||||||
<parameter key="session.class">Symfony\Component\HttpFoundation\Session\Session</parameter>
|
<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.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.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.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.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.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.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>
|
<parameter key="session_listener.class">Symfony\Bundle\FrameworkBundle\EventListener\SessionListener</parameter>
|
||||||
</parameters>
|
</parameters>
|
||||||
|
|
||||||
@ -22,13 +25,20 @@
|
|||||||
<argument type="service" id="session.flash_bag" />
|
<argument type="service" id="session.flash_bag" />
|
||||||
</service>
|
</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%">
|
<service id="session.storage.native" class="%session.storage.native.class%">
|
||||||
<argument>%session.storage.options%</argument>
|
<argument>%session.storage.options%</argument>
|
||||||
<argument type="service" id="session.handler" />
|
<argument type="service" id="session.handler" />
|
||||||
|
<argument type="service" id="session.storage.metadata_bag" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="session.storage.php_bridge" class="%session.storage.php_bridge.class%">
|
<service id="session.storage.php_bridge" class="%session.storage.php_bridge.class%">
|
||||||
<argument type="service" id="session.handler" />
|
<argument type="service" id="session.handler" />
|
||||||
|
<argument type="service" id="session.storage.metadata_bag" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="session.flash_bag" class="%session.flashbag.class%" public="false" />
|
<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">
|
<service id="session.storage.mock_file" class="%session.storage.mock_file.class%" public="false">
|
||||||
<argument>%kernel.cache_dir%/sessions</argument>
|
<argument>%kernel.cache_dir%/sessions</argument>
|
||||||
|
<argument>MOCKSESSID</argument>
|
||||||
|
<argument type="service" id="session.storage.metadata_bag" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="session.handler.native_file" class="%session.handler.native_file.class%" public="false">
|
<service id="session.handler.native_file" class="%session.handler.native_file.class%" public="false">
|
||||||
<argument>%session.save_path%</argument>
|
<argument>%session.save_path%</argument>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="session.handler.write_check" class="%session.handler.write_check.class%" public="false" />
|
||||||
|
|
||||||
<service id="session_listener" class="%session_listener.class%">
|
<service id="session_listener" class="%session_listener.class%">
|
||||||
<tag name="kernel.event_subscriber" />
|
<tag name="kernel.event_subscriber" />
|
||||||
<argument type="service" id="service_container" />
|
<argument type="service" id="service_container" />
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -48,14 +48,21 @@ class MetadataBag implements SessionBagInterface
|
|||||||
*/
|
*/
|
||||||
private $lastUsed;
|
private $lastUsed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
private $updateThreshold;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* 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->storageKey = $storageKey;
|
||||||
|
$this->updateThreshold = $updateThreshold;
|
||||||
$this->meta = array(self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0);
|
$this->meta = array(self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +75,11 @@ class MetadataBag implements SessionBagInterface
|
|||||||
|
|
||||||
if (isset($array[self::CREATED])) {
|
if (isset($array[self::CREATED])) {
|
||||||
$this->lastUsed = $this->meta[self::UPDATED];
|
$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 {
|
} else {
|
||||||
$this->stampCreated();
|
$this->stampCreated();
|
||||||
}
|
}
|
||||||
|
@ -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'));
|
||||||
|
}
|
||||||
|
}
|
@ -100,4 +100,36 @@ class MetadataBagTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$this->bag->clear();
|
$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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user