Skip to content

Commit

Permalink
[HttpFoundation] Add RedisSessionHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
dkarlovi committed Nov 1, 2017
1 parent 782dc94 commit 0286208
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/Symfony/Component/HttpFoundation/CHANGELOG.md
Expand Up @@ -10,6 +10,7 @@ CHANGELOG
* deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()`
* deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead
* deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead
* added `RedisSessionHandler` to use Redis as a session storage

3.3.0
-----
Expand Down
@@ -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);
}
}
@@ -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),
);
}
}

0 comments on commit 0286208

Please sign in to comment.