Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[HttpFoundation] Add RedisSessionHandler
- Loading branch information
Showing
3 changed files
with
241 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
<?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; | ||
|
||
/** | ||
* Redis based session storage handler based on the Redis class | ||
* provided by the PHP redis extension. | ||
* | ||
* Direct port of MemcachedSessionHandler to Redis. | ||
* | ||
* @see http://php.net/redis | ||
* | ||
* @author Drak <drak@zikula.org> | ||
* @author Dalibor Karlović <dkarlovi@gmail.com> | ||
*/ | ||
class RedisSessionHandler extends AbstractSessionHandler | ||
{ | ||
/** | ||
* @var \Redis | ||
*/ | ||
private $redis; | ||
|
||
/** | ||
* @var int Time to live in seconds | ||
*/ | ||
private $ttl; | ||
|
||
/** | ||
* @var string Key prefix for shared environments | ||
*/ | ||
private $prefix; | ||
|
||
/** | ||
* List of available options: | ||
* * prefix: The prefix to use for the Redis keys in order to avoid collision | ||
* * expiretime: The time to live in seconds. | ||
* | ||
* @param \Redis $redis A \Redis instance | ||
* @param array $options An associative array of Redis options | ||
* | ||
* @throws \InvalidArgumentException When unsupported options are passed | ||
*/ | ||
public function __construct(\Redis $redis, array $options = null) | ||
{ | ||
$this->redis = $redis; | ||
|
||
if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) { | ||
throw new \InvalidArgumentException(sprintf( | ||
'The following options are not supported "%s"', implode(', ', $diff) | ||
)); | ||
} | ||
|
||
$this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400; | ||
$this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s'; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function doRead($sessionId) | ||
{ | ||
return $this->redis->get($this->prefix.$sessionId) ?: ''; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function doWrite($sessionId, $data) | ||
{ | ||
return $this->redis->set($this->prefix.$sessionId, $data, $this->ttl); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function doDestroy($sessionId) | ||
{ | ||
// number of deleted items | ||
$count = $this->redis->del($this->prefix.$sessionId); | ||
|
||
return 1 === $count; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function close() | ||
{ | ||
return true; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function gc($maxlifetime) | ||
{ | ||
return true; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function updateTimestamp($sessionId, $data) | ||
{ | ||
return $this->redis->expire($this->prefix.$sessionId, $this->ttl); | ||
} | ||
} |
124 changes: 124 additions & 0 deletions
124
...ymfony/Component/HttpFoundation/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
<?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 PHPUnit\Framework\TestCase; | ||
use Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler; | ||
|
||
/** | ||
* @requires extension redis | ||
* @group time-sensitive | ||
*/ | ||
class RedisSessionHandlerTest extends TestCase | ||
{ | ||
const PREFIX = 'prefix_'; | ||
const TTL = 1000; | ||
|
||
/** | ||
* @var RedisSessionHandler | ||
*/ | ||
protected $storage; | ||
|
||
/** @var \PHPUnit_Framework_MockObject_MockObject|\Redis $redis */ | ||
protected $redis; | ||
|
||
protected function setUp() | ||
{ | ||
parent::setUp(); | ||
|
||
$this->redis = $this->getMockBuilder('Redis')->getMock(); | ||
$this->storage = new RedisSessionHandler( | ||
$this->redis, | ||
array('prefix' => self::PREFIX, 'expiretime' => self::TTL) | ||
); | ||
} | ||
|
||
protected function tearDown() | ||
{ | ||
$this->redis = null; | ||
$this->storage = null; | ||
parent::tearDown(); | ||
} | ||
|
||
public function testOpenSession() | ||
{ | ||
$this->assertTrue($this->storage->open('', '')); | ||
} | ||
|
||
public function testCloseSession() | ||
{ | ||
$this->assertTrue($this->storage->close()); | ||
} | ||
|
||
public function testReadSession() | ||
{ | ||
$this->redis | ||
->expects($this->once()) | ||
->method('get') | ||
->with(self::PREFIX.'id') | ||
; | ||
|
||
$this->assertEquals('', $this->storage->read('id')); | ||
} | ||
|
||
public function testWriteSession() | ||
{ | ||
$this->redis | ||
->expects($this->once()) | ||
->method('set') | ||
->with(self::PREFIX.'id', 'data', self::TTL) | ||
->will($this->returnValue(true)) | ||
; | ||
|
||
$this->assertTrue($this->storage->write('id', 'data')); | ||
} | ||
|
||
public function testDestroySession() | ||
{ | ||
$this->redis | ||
->expects($this->once()) | ||
->method('del') | ||
->with(self::PREFIX.'id') | ||
->will($this->returnValue(1)) | ||
; | ||
|
||
$this->assertTrue($this->storage->destroy('id')); | ||
} | ||
|
||
public function testGcSession() | ||
{ | ||
$this->assertTrue($this->storage->gc(123)); | ||
} | ||
|
||
/** | ||
* @dataProvider getOptionFixtures | ||
*/ | ||
public function testSupportedOptions(array $options, $supported) | ||
{ | ||
try { | ||
new RedisSessionHandler($this->redis, $options); | ||
$this->assertTrue($supported); | ||
} catch (\InvalidArgumentException $e) { | ||
$this->assertFalse($supported); | ||
} | ||
} | ||
|
||
public function getOptionFixtures() | ||
{ | ||
return array( | ||
array(array('prefix' => 'session'), true), | ||
array(array('expiretime' => 100), true), | ||
array(array('prefix' => 'session', 'expiretime' => 200), true), | ||
array(array('expiretime' => 100, 'foo' => 'bar'), false), | ||
); | ||
} | ||
} |