feature #18681 [Cache] Add DSN based Redis connection factory (nicolas-grekas)
This PR was merged into the 3.1-dev branch.
Discussion
----------
[Cache] Add DSN based Redis connection factory
| Q | A
| ------------- | ---
| Branch? | 3.1
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | -
| License | MIT
| Doc PR | -
Required by #18667
Commits
-------
3073efb
[Cache] Add DSN based Redis connection factory
This commit is contained in:
commit
8166094dc2
@ -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}
|
||||
*/
|
||||
|
@ -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://'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user