2016-01-19 13:27:46 +00:00
< ? 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\Cache\Adapter ;
2016-03-15 10:06:34 +00:00
use Symfony\Component\Cache\Exception\InvalidArgumentException ;
2016-01-19 13:27:46 +00:00
/**
* @ author Aurimas Niekis < aurimas @ niekis . lt >
*/
class RedisAdapter extends AbstractAdapter
{
2016-04-29 13:31:16 +01:00
private static $defaultConnectionOptions = array (
'class' => \Redis :: class ,
'persistent' => 0 ,
'timeout' => 0 ,
'read_timeout' => 0 ,
'retry_interval' => 0 ,
);
2016-01-19 13:27:46 +00:00
private $redis ;
public function __construct ( \Redis $redisConnection , $namespace = '' , $defaultLifetime = 0 )
{
2016-03-15 10:06:34 +00:00
if ( preg_match ( '#[^-+_.A-Za-z0-9]#' , $namespace , $match )) {
throw new InvalidArgumentException ( sprintf ( 'RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.' , $match [ 0 ]));
}
2016-04-28 08:51:36 +01:00
$this -> redis = $redisConnection ;
2016-03-15 10:06:34 +00:00
2016-01-19 13:27:46 +00:00
parent :: __construct ( $namespace , $defaultLifetime );
}
2016-04-29 13:31:16 +01:00
/**
* 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 ;
}
2016-01-19 13:27:46 +00:00
/**
* { @ inheritdoc }
*/
protected function doFetch ( array $ids )
{
2016-04-28 08:51:36 +01:00
$result = array ();
if ( $ids ) {
$values = $this -> redis -> mget ( $ids );
$index = 0 ;
foreach ( $ids as $id ) {
if ( false !== $value = $values [ $index ++ ]) {
$result [ $id ] = unserialize ( $value );
}
2016-01-19 13:27:46 +00:00
}
}
return $result ;
}
/**
* { @ inheritdoc }
*/
protected function doHave ( $id )
{
return $this -> redis -> exists ( $id );
}
/**
* { @ inheritdoc }
*/
2016-03-15 10:06:34 +00:00
protected function doClear ( $namespace )
2016-01-19 13:27:46 +00:00
{
2016-03-15 10:06:34 +00:00
if ( ! isset ( $namespace [ 0 ])) {
$this -> redis -> flushDB ();
} else {
// As documented in Redis documentation (http://redis.io/commands/keys) using KEYS
// can hang your server when it is executed against large databases (millions of items).
// Whenever you hit this scale, it is advised to deploy one Redis database per cache pool
// instead of using namespaces, so that the above FLUSHDB is used instead.
$this -> redis -> eval ( sprintf ( " local keys=redis.call('KEYS','%s*') for i=1,#keys,5000 do redis.call('DEL',unpack(keys,i,math.min(i+4999,#keys))) end " , $namespace ));
}
return true ;
2016-01-19 13:27:46 +00:00
}
/**
* { @ inheritdoc }
*/
protected function doDelete ( array $ids )
{
2016-04-28 08:51:36 +01:00
if ( $ids ) {
$this -> redis -> del ( $ids );
}
2016-01-19 13:27:46 +00:00
return true ;
}
/**
* { @ inheritdoc }
*/
protected function doSave ( array $values , $lifetime )
{
2016-03-20 20:40:04 +00:00
$serialized = array ();
2016-03-15 10:06:34 +00:00
$failed = array ();
2016-03-21 10:14:47 +00:00
foreach ( $values as $id => $value ) {
2016-03-15 10:06:34 +00:00
try {
2016-03-21 10:14:47 +00:00
$serialized [ $id ] = serialize ( $value );
2016-03-15 10:06:34 +00:00
} catch ( \Exception $e ) {
$failed [] = $id ;
2016-01-19 13:27:46 +00:00
}
2016-03-15 10:06:34 +00:00
}
2016-04-28 08:51:36 +01:00
if ( ! $serialized ) {
return $failed ;
}
2016-03-21 10:14:47 +00:00
if ( $lifetime > 0 ) {
$pipe = $this -> redis -> multi ( \Redis :: PIPELINE );
foreach ( $serialized as $id => $value ) {
$pipe -> setEx ( $id , $lifetime , $value );
2016-01-19 13:27:46 +00:00
}
2016-03-21 10:14:47 +00:00
if ( ! $pipe -> exec ()) {
return false ;
}
} elseif ( ! $this -> redis -> mSet ( $serialized )) {
return false ;
2016-01-19 13:27:46 +00:00
}
2016-03-15 10:06:34 +00:00
return $failed ;
2016-01-19 13:27:46 +00:00
}
}