8000 [Cache] Add DSN based Redis connection factory · symfony/symfony@3073efb · GitHub
[go: up one dir, main page]

Skip to content

Commit 3073efb

Browse files
[Cache] Add DSN based Redis connection factory
1 parent 731d7a6 commit 3073efb

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed

src/Symfony/Component/Cache/Adapter/RedisAdapter.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@
1818
*/
1919
class RedisAdapter extends AbstractAdapter
2020
{
21+
private static $defaultConnectionOptions = array(
22+
'class' => \Redis::class,
23+
'persistent' => 0,
24+
'timeout' => 0,
25+
'read_timeout' => 0,
26+
'retry_interval' => 0,
27+
);
2128
private $redis;
2229

2330
public function __construct(\Redis $redisConnection, $namespace = '', $defaultLifetime = 0)
@@ -30,6 +37,80 @@ public function __construct(\Redis $redisConnection, $namespace = '', $defaultLi
3037
parent::__construct($namespace, $defaultLifetime);
3138
}
3239

40+
/**
41+
* Creates a Redis connection using a DSN configuration.
42+
*
43+
* Example DSN:
44+
* - redis://localhost
45+
* - redis://example.com:1234
46+
* - redis://secret@example.com/13
47+
* - redis:///var/run/redis.sock
48+
* - redis://secret@/var/run/redis.sock/13
49+
*
50+
* @param string $dsn
51+
* @param array $options See self::$defaultConnectionOptions
52+
*
53+
* @throws InvalidArgumentException When the DSN is invalid.
54+
*
55+
* @return \Redis
56+
*/
57+
public static function createConnection($dsn, array $options = array())
58+
{
59+
if (0 !== strpos($dsn, 'redis://')) {
60+
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s does not start with "redis://"', $dsn));
61+
}
62+
$params = preg_replace_callback('#^redis://(?:([^@]*)@)?#', function ($m) use (&$auth) {
63+
if (isset($m[1])) {
64+
$auth = $m[1];
65+
}
66+
67+
return 'file://';
68+
}, $dsn);
69+
if (false === $params = parse_url($params)) {
70+
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
71+
}
72+
if (!isset($params['host']) && !isset($params['path'])) {
73+
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
74+
}
75+
if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
76+
$params['dbindex'] = $m[1];
77+
$params['path'] = substr($params['path'], 0, -strlen($m[0]));
78+
}
79+
$params += array(
80+
'host' => isset($params['host']) ? $params['host'] : $params['path'],
81+
'port' => isset($params['host']) ? 6379 : null,
82+
'dbindex' => 0,
83+
);
84+
if (isset($params['query'])) {
85+
parse_str($params['query'], $query);
86+
$params += $query;
87+
}
88+
$params += $options + self::$defaultConnectionOptions;
89+
90+
if (\Redis::class !== $params['class'] && !is_subclass_of($params['class'], \Redis::class)) {
91+
throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis"', $params['class']));
92+
}
93+
$connect = empty($params['persistent']) ? 'connect' : 'pconnect';
94+
$redis = new $params['class']();
95+
@$redis->{$connect}($params['host'], $params['port'], $params['timeout'], null, $params['retry_interval']);
96+
97+
if (@!$redis->isConnected()) {
98+
$e = ($e = error_get_last()) && preg_match('/^Redis::p?connect\(\): (.*)/', $e['message'], $e) ? sprintf(' (%s)', $e[1]) : '';
99+
throw new InvalidArgumentException(sprintf('Redis connection failed%s: %s', $e, $dsn));
100+
}
101+
102+
if ((null !== $auth && !$redis->auth($auth))
103+
|| ($params['dbindex'] && !$redis->select($params['dbindex']))
104+
|| ($params['read_timeout'] && !$redis->setOption(\Redis::OPT_READ_TIMEOUT, $params['read_timeout']))
105+
) {
106+
$e = preg_replace('/^ERR /', '', $redis->getLastError());
107+
$redis->close();
108+
throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e, $dsn));
109+
}
110+
111+
return $redis;
112+
}
113+
33114
/**
34115
* {@inheritdoc}
35116
*/

src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,60 @@ public static function tearDownAfterClass()
4444
self::$redis->flushDB();
4545
self::$redis->close();
4646
}
47+
48+
public function testCreateConnection()
49+
{
50+
$redis = RedisAdapter::createConnection('redis://localhost');
51+
$this->assertTrue($redis->isConnected());
52+
$this->assertSame(0, $redis->getDbNum());
53+
54+
$redis = RedisAdapter::createConnection('redis://localhost/2');
55+
$this->assertSame(2, $redis->getDbNum());
56+
57+
$redis = RedisAdapter::createConnection('redis://localhost', array('timeout' => 3));
58+
$this->assertEquals(3, $redis->getTimeout());
59+
60+
$redis = RedisAdapter::createConnection('redis://localhost?timeout=4');
61+
$this->assertEquals(4, $redis->getTimeout());
62+
63+
$redis = RedisAdapter::createConnection('redis://localhost', array('read_timeout' => 5));
64+
$this->assertEquals(5, $redis->getReadTimeout());
65+
}
66+
67+
/**
68+
* @dataProvider provideFailedCreateConnection
69+
* @expectedException Symfony\Component\Cache\Exception\InvalidArgumentException
70+
* @expectedExceptionMessage Redis connection failed
71+
*/
72+
public function testFailedCreateConnection($dsn)
73+
{
74+
RedisAdapter::createConnection($dsn);
75+
}
76+
77+
public function provideFailedCreateConnection()
78+
{
79+
return array(
80+
array('redis://localhost:1234'),
81+
array('redis://foo@localhost'),
82+
array('redis://localhost/123'),
83+
);
84+
}
85+
86+
/**
87+
* @dataProvider provideInvalidCreateConnection
88+
* @expectedException Symfony\Component\Cache\Exception\InvalidArgumentException
89+
* @expectedExceptionMessage Invalid Redis DSN
90+
*/
91+
public function testInvalidCreateConnection($dsn)
92+
{
93+
RedisAdapter::createConnection($dsn);
94+
}
95+
96+
public function provideInvalidCreateConnection()
97+
{
98+
return array(
99+
array('foo://localhost'),
100+
array('redis://'),
101+
);
102+
}
47103
}

0 commit comments

Comments
 (0)
0