Skip to content

Commit 0286208

Browse files
committed
[HttpFoundation] Add RedisSessionHandler
1 parent 782dc94 commit 0286208

File tree

3 files changed

+241
-0
lines changed

3 files changed

+241
-0
lines changed

src/Symfony/Component/HttpFoundation/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ CHANGELOG
1010
* deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()`
1111
* deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead
1212
* deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead
13+
* added `RedisSessionHandler` to use Redis as a session storage
1314

1415
3.3.0
1516
-----
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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+
* Redis based session storage handler based on the Redis class
16+
* provided by the PHP redis extension.
17+
*
18+
* Direct port of MemcachedSessionHandler to Redis.
19+
*
20+
* @see http://php.net/redis
21+
*
22+
* @author Drak <drak@zikula.org>
23+
* @author Dalibor Karlović <dkarlovi@gmail.com>
24+
*/
25+
class RedisSessionHandler extends AbstractSessionHandler
26+
{
27+
/**
28+
* @var \Redis
29+
*/
30+
private $redis;
31+
32+
/**
33+
* @var int Time to live in seconds
34+
*/
35+
private $ttl;
36+
37+
/**
38+
* @var string Key prefix for shared environments
39+
*/
40+
private $prefix;
41+
42+
/**
43+
* List of available options:
44+
* * prefix: The prefix to use for the Redis keys in order to avoid collision
45+
* * expiretime: The time to live in seconds.
46+
*
47+
* @param \Redis $redis A \Redis instance
48+
* @param array $options An associative array of Redis options
49+
*
50+
* @throws \InvalidArgumentException When unsupported options are passed
51+
*/
52+
public function __construct(\Redis $redis, array $options = null)
53+
{
54+
$this->redis = $redis;
55+
56+
if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) {
57+
throw new \InvalidArgumentException(sprintf(
58+
'The following options are not supported "%s"', implode(', ', $diff)
59+
));
60+
}
61+
62+
$this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400;
63+
$this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s';
64+
}
65+
66+
/**
67+
* {@inheritdoc}
68+
*/
69+
protected function doRead($sessionId)
70+
{
71+
return $this->redis->get($this->prefix.$sessionId) ?: '';
72+
}
73+
74+
/**
75+
* {@inheritdoc}
76+
*/
77+
protected function doWrite($sessionId, $data)
78+
{
79+
return $this->redis->set($this->prefix.$sessionId, $data, $this->ttl);
80+
}
81+
82+
/**
83+
* {@inheritdoc}
84+
*/
85+
protected function doDestroy($sessionId)
86+
{
87+
// number of deleted items
88+
$count = $this->redis->del($this->prefix.$sessionId);
89+
90+
return 1 === $count;
91+
}
92+
93+
/**
94+
* {@inheritdoc}
95+
*/
96+
public function close()
97+
{
98+
return true;
99+
}
100+
101+
/**
102+
* {@inheritdoc}
103+
*/
104+
public function gc($maxlifetime)
105+
{
106+
return true;
107+
}
108+
109+
/**
110+
* {@inheritdoc}
111+
*/
112+
public function updateTimestamp($sessionId, $data)
113+
{
114+
return $this->redis->expire($this->prefix.$sessionId, $this->ttl);
115+
}
116+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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 PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler;
16+
17+
/**
18+
* @requires extension redis
19+
* @group time-sensitive
20+
*/
21+
class RedisSessionHandlerTest extends TestCase
22+
{
23+
const PREFIX = 'prefix_';
24+
const TTL = 1000;
25+
26+
/**
27+
* @var RedisSessionHandler
28+
*/
29+
protected $storage;
30+
31+
/** @var \PHPUnit_Framework_MockObject_MockObject|\Redis $redis */
32+
protected $redis;
33+
34+
protected function setUp()
35+
{
36+
parent::setUp();
37+
38+
$this->redis = $this->getMockBuilder('Redis')->getMock();
39+
$this->storage = new RedisSessionHandler(
40+
$this->redis,
41+
array('prefix' => self::PREFIX, 'expiretime' => self::TTL)
42+
);
43+
}
44+
45+
protected function tearDown()
46+
{
47+
$this->redis = null;
48+
$this->storage = null;
49+
parent::tearDown();
50+
}
51+
52+
public function testOpenSession()
53+
{
54+
$this->assertTrue($this->storage->open('', ''));
55+
}
56+
57+
public function testCloseSession()
58+
{
59+
$this->assertTrue($this->storage->close());
60+
}
61+
62+
public function testReadSession()
63+
{
64+
$this->redis
65+
->expects($this->once())
66+
->method('get')
67+
->with(self::PREFIX.'id')
68+
;
69+
70+
$this->assertEquals('', $this->storage->read('id'));
71+
}
72+
73+
public function testWriteSession()
74+
{
75+
$this->redis
76+
->expects($this->once())
77+
->method('set')
78+
->with(self::PREFIX.'id', 'data', self::TTL)
79+
->will($this->returnValue(true))
80+
;
81+
82+
$this->assertTrue($this->storage->write('id', 'data'));
83+
}
84+
85+
public function testDestroySession()
86+
{
87+
$this->redis
88+
->expects($this->once())
89+
->method('del')
90+
->with(self::PREFIX.'id')
91+
->will($this->returnValue(1))
92+
;
93+
94+
$this->assertTrue($this->storage->destroy('id'));
95+
}
96+
97+
public function testGcSession()
98+
{
99+
$this->assertTrue($this->storage->gc(123));
100+
}
101+
102+
/**
103+
* @dataProvider getOptionFixtures
104+
*/
105+
public function testSupportedOptions(array $options, $supported)
106+
{
107+
try {
108+
new RedisSessionHandler($this->redis, $options);
109+
$this->assertTrue($supported);
110+
} catch (\InvalidArgumentException $e) {
111+
$this->assertFalse($supported);
112+
}
113+
}
114+
115+
public function getOptionFixtures()
116+
{
117+
return array(
118+
array(array('prefix' => 'session'), true),
119+
array(array('expiretime' => 100), true),
120+
array(array('prefix' => 'session', 'expiretime' => 200), true),
121+
array(array('expiretime' => 100, 'foo' => 'bar'), false),
122+
);
123+
}
124+
}

0 commit comments

Comments
 (0)