2010-04-18 13:58:43 +00:00
|
|
|
<?php
|
2018-05-25 07:26:54 +00:00
|
|
|
|
2010-04-18 13:58:43 +00:00
|
|
|
/**
|
2018-05-25 07:26:54 +00:00
|
|
|
* Set up phpredis connection
|
2010-04-18 13:58:43 +00:00
|
|
|
*
|
2018-05-25 07:26:54 +00:00
|
|
|
* @package Resque/Redis
|
|
|
|
* @author Chris Boulton <chris@bigcommerce.com>
|
|
|
|
* @license http://www.opensource.org/licenses/mit-license.php
|
2010-04-18 13:58:43 +00:00
|
|
|
*/
|
2018-05-25 07:26:54 +00:00
|
|
|
|
2013-01-12 12:37:38 +00:00
|
|
|
class Resque_Redis
|
2010-04-18 13:58:43 +00:00
|
|
|
{
|
2018-05-30 07:05:13 +00:00
|
|
|
/**
|
|
|
|
* Redis Client
|
|
|
|
* @var Credis_Client
|
|
|
|
*/
|
|
|
|
private $driver;
|
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
/**
|
|
|
|
* Redis namespace
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
private static $defaultNamespace = 'resque:';
|
2014-05-05 13:02:16 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
/**
|
|
|
|
* A default host to connect to
|
|
|
|
*/
|
|
|
|
const DEFAULT_HOST = 'localhost';
|
2014-05-05 13:02:16 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
/**
|
|
|
|
* The default Redis port
|
|
|
|
*/
|
|
|
|
const DEFAULT_PORT = 6379;
|
2014-05-05 13:02:16 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
/**
|
|
|
|
* The default Redis Database number
|
|
|
|
*/
|
|
|
|
const DEFAULT_DATABASE = 0;
|
2013-01-12 12:37:38 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
/**
|
|
|
|
* @var array List of all commands in Redis that supply a key as their
|
|
|
|
* first argument. Used to prefix keys with the Resque namespace.
|
|
|
|
*/
|
|
|
|
private $keyCommands = [
|
|
|
|
'exists',
|
|
|
|
'del',
|
|
|
|
'type',
|
|
|
|
'keys',
|
|
|
|
'expire',
|
|
|
|
'ttl',
|
|
|
|
'move',
|
|
|
|
'set',
|
|
|
|
'setex',
|
|
|
|
'get',
|
|
|
|
'getset',
|
|
|
|
'setnx',
|
|
|
|
'incr',
|
|
|
|
'incrby',
|
|
|
|
'decr',
|
|
|
|
'decrby',
|
|
|
|
'rpush',
|
|
|
|
'lpush',
|
|
|
|
'llen',
|
|
|
|
'lrange',
|
|
|
|
'ltrim',
|
|
|
|
'lindex',
|
|
|
|
'lset',
|
|
|
|
'lrem',
|
|
|
|
'lpop',
|
|
|
|
'blpop',
|
|
|
|
'rpop',
|
|
|
|
'sadd',
|
|
|
|
'srem',
|
|
|
|
'spop',
|
|
|
|
'scard',
|
|
|
|
'sismember',
|
|
|
|
'smembers',
|
|
|
|
'srandmember',
|
|
|
|
'zadd',
|
|
|
|
'zrem',
|
|
|
|
'zrange',
|
|
|
|
'zrevrange',
|
|
|
|
'zrangebyscore',
|
|
|
|
'zcard',
|
|
|
|
'zscore',
|
|
|
|
'zremrangebyscore',
|
|
|
|
'sort',
|
|
|
|
'rename',
|
|
|
|
'rpoplpush'
|
|
|
|
];
|
|
|
|
// sinterstore
|
|
|
|
// sunion
|
|
|
|
// sunionstore
|
|
|
|
// sdiff
|
|
|
|
// sdiffstore
|
|
|
|
// sinter
|
|
|
|
// smove
|
|
|
|
// mget
|
|
|
|
// msetnx
|
|
|
|
// mset
|
|
|
|
// renamenx
|
2013-01-12 12:37:38 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
/**
|
|
|
|
* Set Redis namespace (prefix) default: resque
|
|
|
|
* @param string $namespace
|
|
|
|
*/
|
|
|
|
public static function prefix($namespace)
|
|
|
|
{
|
|
|
|
if (substr($namespace, -1) !== ':' && $namespace != '') {
|
|
|
|
$namespace .= ':';
|
|
|
|
}
|
|
|
|
self::$defaultNamespace = $namespace;
|
|
|
|
}
|
2013-01-12 12:37:38 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
/**
|
|
|
|
* @param string|array $server A DSN or array
|
|
|
|
* @param int $database A database number to select. However, if we find a valid database number in the DSN the
|
|
|
|
* DSN-supplied value will be used instead and this parameter is ignored.
|
2018-05-30 07:05:13 +00:00
|
|
|
* @param object $client Optional Credis_Client instance instantiated by you
|
2018-05-25 07:26:54 +00:00
|
|
|
* @throws Resque_RedisException
|
|
|
|
*/
|
2016-10-15 08:44:03 +00:00
|
|
|
public function __construct($server, $database = null, $client = null)
|
2018-05-25 07:26:54 +00:00
|
|
|
{
|
|
|
|
try {
|
|
|
|
if (is_object($client)) {
|
2018-05-30 07:05:13 +00:00
|
|
|
$this->driver = $client;
|
2018-05-25 07:26:54 +00:00
|
|
|
} else {
|
2018-05-29 20:25:02 +00:00
|
|
|
/** @noinspection PhpUnusedLocalVariableInspection */
|
2018-05-25 07:26:54 +00:00
|
|
|
list($host, $port, $dsnDatabase, $user, $password, $options) = self::parseDsn($server);
|
|
|
|
// $user is not used, only $password
|
|
|
|
$timeout = isset($options['timeout']) ? intval($options['timeout']) : null;
|
2018-05-30 07:05:13 +00:00
|
|
|
$persistent = isset($options['persistent']) ? $options['persistent'] : '';
|
|
|
|
$maxRetries = isset($options['max_connect_retries']) ? $options['max_connect_retries'] : 0;
|
|
|
|
$this->driver = new Credis_Client($host, $port, $timeout, $persistent);
|
|
|
|
$this->driver->setMaxConnectRetries($maxRetries);
|
2018-05-25 07:26:54 +00:00
|
|
|
if ($password) {
|
2018-05-30 07:05:13 +00:00
|
|
|
$this->driver->auth($password);
|
2018-05-25 07:26:54 +00:00
|
|
|
}
|
|
|
|
// If we have found a database in our DSN, use it instead of the `$database`
|
|
|
|
// value passed into the constructor.
|
|
|
|
if ($dsnDatabase !== false) {
|
|
|
|
$database = $dsnDatabase;
|
|
|
|
}
|
|
|
|
}
|
2018-05-30 07:05:13 +00:00
|
|
|
if ($database !== null) {
|
|
|
|
$this->driver->select($database);
|
|
|
|
}
|
|
|
|
} catch (Exception $e) {
|
2018-05-25 07:26:54 +00:00
|
|
|
throw new Resque_RedisException('Error communicating with Redis: ' . $e->getMessage(), 0, $e);
|
|
|
|
}
|
|
|
|
}
|
2013-01-12 12:37:38 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
/**
|
|
|
|
* Parse a DSN string, which can have one of the following formats:
|
|
|
|
*
|
|
|
|
* - host:port
|
|
|
|
* - redis://user:pass@host:port/db?option1=val1&option2=val2
|
|
|
|
* - tcp://user:pass@host:port/db?option1=val1&option2=val2
|
|
|
|
* - unix:///path/to/redis.sock
|
|
|
|
*
|
|
|
|
* Note: the 'user' part of the DSN is not used.
|
|
|
|
*
|
|
|
|
* @param string $dsn A DSN string
|
|
|
|
* @return array An array of DSN compotnents, with 'false' values for any unknown components. e.g.
|
|
|
|
* [host, port, db, user, pass, options]
|
|
|
|
*/
|
|
|
|
public static function parseDsn($dsn)
|
|
|
|
{
|
|
|
|
if ($dsn == '') {
|
|
|
|
// Use a sensible default for an empty DNS string
|
|
|
|
$dsn = 'redis://' . self::DEFAULT_HOST;
|
|
|
|
}
|
|
|
|
if (substr($dsn, 0, 7) === 'unix://') {
|
|
|
|
return [
|
|
|
|
$dsn,
|
|
|
|
null,
|
|
|
|
false,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
$parts = parse_url($dsn);
|
2014-05-05 14:47:43 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
// Check the URI scheme
|
2018-05-29 10:20:56 +00:00
|
|
|
$validSchemes = ['redis', 'tcp'];
|
2018-05-25 07:26:54 +00:00
|
|
|
if (isset($parts['scheme']) && !in_array($parts['scheme'], $validSchemes)) {
|
|
|
|
throw new \InvalidArgumentException("Invalid DSN. Supported schemes are " . implode(', ', $validSchemes));
|
|
|
|
}
|
2014-05-05 13:02:16 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
// Allow simple 'hostname' format, which `parse_url` treats as a path, not host.
|
|
|
|
if (!isset($parts['host']) && isset($parts['path'])) {
|
|
|
|
$parts['host'] = $parts['path'];
|
|
|
|
unset($parts['path']);
|
|
|
|
}
|
2014-05-05 13:02:16 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
// Extract the port number as an integer
|
|
|
|
$port = isset($parts['port']) ? intval($parts['port']) : self::DEFAULT_PORT;
|
2014-05-05 13:02:16 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
// Get the database from the 'path' part of the URI
|
|
|
|
$database = false;
|
|
|
|
if (isset($parts['path'])) {
|
|
|
|
// Strip non-digit chars from path
|
|
|
|
$database = intval(preg_replace('/[^0-9]/', '', $parts['path']));
|
|
|
|
}
|
2014-05-05 13:02:16 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
// Extract any 'user' and 'pass' values
|
|
|
|
$user = isset($parts['user']) ? $parts['user'] : false;
|
|
|
|
$pass = isset($parts['pass']) ? $parts['pass'] : false;
|
2014-05-05 14:47:43 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
// Convert the query string into an associative array
|
2018-05-29 10:20:56 +00:00
|
|
|
$options = [];
|
2018-05-25 07:26:54 +00:00
|
|
|
if (isset($parts['query'])) {
|
|
|
|
// Parse the query string into an array
|
|
|
|
parse_str($parts['query'], $options);
|
|
|
|
}
|
2014-05-05 13:02:16 +00:00
|
|
|
|
2018-05-29 10:20:56 +00:00
|
|
|
return [
|
2018-05-25 07:26:54 +00:00
|
|
|
$parts['host'],
|
|
|
|
$port,
|
|
|
|
$database,
|
|
|
|
$user,
|
|
|
|
$pass,
|
|
|
|
$options,
|
2018-05-29 10:20:56 +00:00
|
|
|
];
|
2018-05-25 07:26:54 +00:00
|
|
|
}
|
2014-05-05 13:02:16 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
/**
|
|
|
|
* Magic method to handle all function requests and prefix key based
|
|
|
|
* operations with the {self::$defaultNamespace} key prefix.
|
|
|
|
*
|
|
|
|
* @param string $name The name of the method called.
|
|
|
|
* @param array $args Array of supplied arguments to the method.
|
|
|
|
* @return mixed Return value from Resident::call() based on the command.
|
2018-05-25 08:52:20 +00:00
|
|
|
* @throws Resque_RedisException
|
2018-05-25 07:26:54 +00:00
|
|
|
*/
|
|
|
|
public function __call($name, $args)
|
|
|
|
{
|
|
|
|
if (in_array($name, $this->keyCommands)) {
|
|
|
|
if (is_array($args[0])) {
|
|
|
|
foreach ($args[0] AS $i => $v) {
|
|
|
|
$args[0][$i] = self::$defaultNamespace . $v;
|
|
|
|
}
|
2018-05-25 09:03:48 +00:00
|
|
|
} else {
|
2018-05-25 07:26:54 +00:00
|
|
|
$args[0] = self::$defaultNamespace . $args[0];
|
|
|
|
}
|
|
|
|
}
|
2018-05-25 08:52:20 +00:00
|
|
|
try {
|
2018-05-30 07:05:13 +00:00
|
|
|
return $this->driver->__call($name, $args);
|
|
|
|
} catch (CredisException $e) {
|
2018-05-25 08:52:20 +00:00
|
|
|
throw new Resque_RedisException('Error communicating with Redis: ' . $e->getMessage(), 0, $e);
|
|
|
|
}
|
2018-05-25 07:26:54 +00:00
|
|
|
}
|
2012-11-26 15:23:57 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
public static function getPrefix()
|
|
|
|
{
|
|
|
|
return self::$defaultNamespace;
|
|
|
|
}
|
2012-11-26 15:23:57 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
public static function removePrefix($string)
|
|
|
|
{
|
|
|
|
$prefix = self::getPrefix();
|
2012-11-26 15:23:57 +00:00
|
|
|
|
2018-05-25 07:26:54 +00:00
|
|
|
if (substr($string, 0, strlen($prefix)) == $prefix) {
|
|
|
|
$string = substr($string, strlen($prefix), strlen($string));
|
|
|
|
}
|
|
|
|
return $string;
|
|
|
|
}
|
2010-04-18 13:58:43 +00:00
|
|
|
}
|