[Cache] Add DSN based Redis connection factory

This commit is contained in:
Nicolas Grekas 2016-04-29 14:31:16 +02:00
parent 731d7a6bf0
commit 3073efb6d1
2 changed files with 137 additions and 0 deletions

View File

@ -18,6 +18,13 @@ use Symfony\Component\Cache\Exception\InvalidArgumentException;
*/
class RedisAdapter extends AbstractAdapter
{
private static $defaultConnectionOptions = array(
'class' => \Redis::class,
'persistent' => 0,
'timeout' => 0,
'read_timeout' => 0,
'retry_interval' => 0,
);
private $redis;
public function __construct(\Redis $redisConnection, $namespace = '', $defaultLifetime = 0)
@ -30,6 +37,80 @@ class RedisAdapter extends AbstractAdapter
parent::__construct($namespace, $defaultLifetime);
}
/**
* Creates a Redis connection using a DSN configuration.
*
* Example DSN:
* - redis://localhost
* - redis://example.com:1234
* - redis://secret@example.com/13
* - redis:///var/run/redis.sock
* - redis://secret@/var/run/redis.sock/13
*
* @param string $dsn
* @param array $options See self::$defaultConnectionOptions
*
* @throws InvalidArgumentException When the DSN is invalid.
*
* @return \Redis
*/
public static function createConnection($dsn, array $options = array())
{
if (0 !== strpos($dsn, 'redis://')) {
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s does not start with "redis://"', $dsn));
}
$params = preg_replace_callback('#^redis://(?:([^@]*)@)?#', function ($m) use (&$auth) {
if (isset($m[1])) {
$auth = $m[1];
}
return 'file://';
}, $dsn);
if (false === $params = parse_url($params)) {
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
}
if (!isset($params['host']) && !isset($params['path'])) {
throw new InvalidArgumentException(sprintf('Invalid Redis DSN: %s', $dsn));
}
if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
$params['dbindex'] = $m[1];
$params['path'] = substr($params['path'], 0, -strlen($m[0]));
}
$params += array(
'host' => isset($params['host']) ? $params['host'] : $params['path'],
'port' => isset($params['host']) ? 6379 : null,
'dbindex' => 0,
);
if (isset($params['query'])) {
parse_str($params['query'], $query);
$params += $query;
}
$params += $options + self::$defaultConnectionOptions;
if (\Redis::class !== $params['class'] && !is_subclass_of($params['class'], \Redis::class)) {
throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis"', $params['class']));
}
$connect = empty($params['persistent']) ? 'connect' : 'pconnect';
$redis = new $params['class']();
@$redis->{$connect}($params['host'], $params['port'], $params['timeout'], null, $params['retry_interval']);
if (@!$redis->isConnected()) {
$e = ($e = error_get_last()) && preg_match('/^Redis::p?connect\(\): (.*)/', $e['message'], $e) ? sprintf(' (%s)', $e[1]) : '';
throw new InvalidArgumentException(sprintf('Redis connection failed%s: %s', $e, $dsn));
}
if ((null !== $auth && !$redis->auth($auth))
|| ($params['dbindex'] && !$redis->select($params['dbindex']))
|| ($params['read_timeout'] && !$redis->setOption(\Redis::OPT_READ_TIMEOUT, $params['read_timeout']))
) {
$e = preg_replace('/^ERR /', '', $redis->getLastError());
$redis->close();
throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e, $dsn));
}
return $redis;
}
/**
* {@inheritdoc}
*/

View File

@ -44,4 +44,60 @@ class RedisAdapterTest extends CachePoolTest
self::$redis->flushDB();
self::$redis->close();
}
public function testCreateConnection()
{
$redis = RedisAdapter::createConnection('redis://localhost');
$this->assertTrue($redis->isConnected());
$this->assertSame(0, $redis->getDbNum());
$redis = RedisAdapter::createConnection('redis://localhost/2');
$this->assertSame(2, $redis->getDbNum());
$redis = RedisAdapter::createConnection('redis://localhost', array('timeout' => 3));
$this->assertEquals(3, $redis->getTimeout());
$redis = RedisAdapter::createConnection('redis://localhost?timeout=4');
$this->assertEquals(4, $redis->getTimeout());
$redis = RedisAdapter::createConnection('redis://localhost', array('read_timeout' => 5));
$this->assertEquals(5, $redis->getReadTimeout());
}
/**
* @dataProvider provideFailedCreateConnection
* @expectedException Symfony\Component\Cache\Exception\InvalidArgumentException
* @expectedExceptionMessage Redis connection failed
*/
public function testFailedCreateConnection($dsn)
{
RedisAdapter::createConnection($dsn);
}
public function provideFailedCreateConnection()
{
return array(
array('redis://localhost:1234'),
array('redis://foo@localhost'),
array('redis://localhost/123'),
);
}
/**
* @dataProvider provideInvalidCreateConnection
* @expectedException Symfony\Component\Cache\Exception\InvalidArgumentException
* @expectedExceptionMessage Invalid Redis DSN
*/
public function testInvalidCreateConnection($dsn)
{
RedisAdapter::createConnection($dsn);
}
public function provideInvalidCreateConnection()
{
return array(
array('foo://localhost'),
array('redis://'),
);
}
}