Merge branch 'master' of github.com:chrisboulton/php-resque

This commit is contained in:
Chris Boulton 2014-06-26 19:19:16 +10:00
commit 1517d45904
7 changed files with 330 additions and 66 deletions

View File

@ -30,7 +30,15 @@ if(empty($QUEUE)) {
die("Set QUEUE env var containing the list of queues to work.\n"); die("Set QUEUE env var containing the list of queues to work.\n");
} }
/**
* REDIS_BACKEND can have simple 'host:port' format or use a DSN-style format like this:
* - redis://user:pass@host:port
*
* Note: the 'user' part of the DSN URI is required but is not used.
*/
$REDIS_BACKEND = getenv('REDIS_BACKEND'); $REDIS_BACKEND = getenv('REDIS_BACKEND');
// A redis database number
$REDIS_BACKEND_DB = getenv('REDIS_BACKEND_DB'); $REDIS_BACKEND_DB = getenv('REDIS_BACKEND_DB');
if(!empty($REDIS_BACKEND)) { if(!empty($REDIS_BACKEND)) {
if (empty($REDIS_BACKEND_DB)) if (empty($REDIS_BACKEND_DB))

View File

@ -20,7 +20,7 @@
"require": { "require": {
"php": ">=5.3.0", "php": ">=5.3.0",
"ext-pcntl": "*", "ext-pcntl": "*",
"colinmollenhour/credis": "1.2.*", "colinmollenhour/credis": "~1.2",
"psr/log": "1.0.0" "psr/log": "1.0.0"
}, },
"suggest": { "suggest": {

View File

@ -7,6 +7,9 @@ require __DIR__ . '/init.php';
date_default_timezone_set('GMT'); date_default_timezone_set('GMT');
Resque::setBackend('127.0.0.1:6379'); Resque::setBackend('127.0.0.1:6379');
// You can also use a DSN-style format:
//Resque::setBackend('redis://user:pass@127.0.0.1:6379');
//Resque::setBackend('redis://user:pass@a.host.name:3432/2');
$status = new Resque_Job_Status($argv[1]); $status = new Resque_Job_Status($argv[1]);
if(!$status->isTracking()) { if(!$status->isTracking()) {

View File

@ -7,6 +7,10 @@ require __DIR__ . '/init.php';
date_default_timezone_set('GMT'); date_default_timezone_set('GMT');
Resque::setBackend('127.0.0.1:6379'); Resque::setBackend('127.0.0.1:6379');
// You can also use a DSN-style format:
//Resque::setBackend('redis://user:pass@127.0.0.1:6379');
//Resque::setBackend('redis://user:pass@a.host.name:3432/2');
$args = array( $args = array(
'time' => time(), 'time' => time(),
'array' => array( 'array' => array(

View File

@ -32,7 +32,7 @@ class Resque
* Given a host/port combination separated by a colon, set it as * Given a host/port combination separated by a colon, set it as
* the redis server that Resque will talk to. * the redis server that Resque will talk to.
* *
* @param mixed $server Host/port combination separated by a colon, or * @param mixed $server Host/port combination separated by a colon, DSN-formatted URI, or
* a nested array of servers with host/port pairs. * a nested array of servers with host/port pairs.
* @param int $database * @param int $database
*/ */
@ -54,12 +54,7 @@ class Resque
return self::$redis; return self::$redis;
} }
$server = self::$redisServer; self::$redis = new Resque_Redis(self::$redisServer, self::$redisDatabase);
if (empty($server)) {
$server = 'localhost:6379';
}
self::$redis = new Resque_Redis($server, self::$redisDatabase);
return self::$redis; return self::$redis;
} }

View File

@ -14,8 +14,20 @@ class Resque_Redis
*/ */
private static $defaultNamespace = 'resque:'; private static $defaultNamespace = 'resque:';
private $server; /**
private $database; * A default host to connect to
*/
const DEFAULT_HOST = 'localhost';
/**
* The default Redis port
*/
const DEFAULT_PORT = 6379;
/**
* The default Redis Database number
*/
const DEFAULT_DATABASE = 0;
/** /**
* @var array List of all commands in Redis that supply a key as their * @var array List of all commands in Redis that supply a key as their
@ -92,47 +104,106 @@ class Resque_Redis
self::$defaultNamespace = $namespace; self::$defaultNamespace = $namespace;
} }
/**
* @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.
*/
public function __construct($server, $database = null) public function __construct($server, $database = null)
{ {
$this->server = $server; if (is_array($server)) {
$this->database = $database;
if (is_array($this->server)) {
$this->driver = new Credis_Cluster($server); $this->driver = new Credis_Cluster($server);
} }
else { else {
$port = null;
$password = null;
$host = $server;
// If not a UNIX socket path or tcp:// formatted connections string list($host, $port, $dsnDatabase, $user, $password, $options) = self::parseDsn($server);
// assume host:port combination. // $user is not used, only $password
if (strpos($server, '/') === false) {
$parts = explode(':', $server);
if (isset($parts[1])) {
$port = $parts[1];
}
$host = $parts[0];
}else if (strpos($server, 'redis://') !== false){
// Redis format is:
// redis://[user]:[password]@[host]:[port]
list($userpwd,$hostport) = explode('@', $server);
$userpwd = substr($userpwd, strpos($userpwd, 'redis://')+8);
list($host, $port) = explode(':', $hostport);
list($user, $password) = explode(':', $userpwd);
}
$this->driver = new Credis_Client($host, $port); // Look for known Credis_Client options
if (isset($password)){ $timeout = isset($options['timeout']) ? intval($options['timeout']) : null;
$persistent = isset($options['persistent']) ? $options['persistent'] : '';
$this->driver = new Credis_Client($host, $port, $timeout, $persistent);
if ($password){
$this->driver->auth($password); $this->driver->auth($password);
} }
// 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;
}
} }
if ($this->database !== null) { if ($database !== null) {
$this->driver->select($database); $this->driver->select($database);
} }
} }
/**
* 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
*
* 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;
}
$parts = parse_url($dsn);
// Check the URI scheme
$validSchemes = array('redis', 'tcp');
if (isset($parts['scheme']) && ! in_array($parts['scheme'], $validSchemes)) {
throw new \InvalidArgumentException("Invalid DSN. Supported schemes are " . implode(', ', $validSchemes));
}
// 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']);
}
// Extract the port number as an integer
$port = isset($parts['port']) ? intval($parts['port']) : self::DEFAULT_PORT;
// 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']));
}
// Extract any 'user' and 'pass' values
$user = isset($parts['user']) ? $parts['user'] : false;
$pass = isset($parts['pass']) ? $parts['pass'] : false;
// Convert the query string into an associative array
$options = array();
if (isset($parts['query'])) {
// Parse the query string into an array
parse_str($parts['query'], $options);
}
return array(
$parts['host'],
$port,
$database,
$user,
$pass,
$options,
);
}
/** /**
* Magic method to handle all function requests and prefix key based * Magic method to handle all function requests and prefix key based
* operations with the {self::$defaultNamespace} key prefix. * operations with the {self::$defaultNamespace} key prefix.
@ -141,13 +212,15 @@ class Resque_Redis
* @param array $args Array of supplied arguments to the method. * @param array $args Array of supplied arguments to the method.
* @return mixed Return value from Resident::call() based on the command. * @return mixed Return value from Resident::call() based on the command.
*/ */
public function __call($name, $args) { public function __call($name, $args)
{
if (in_array($name, $this->keyCommands)) { if (in_array($name, $this->keyCommands)) {
if (is_array($args[0])) { if (is_array($args[0])) {
foreach ($args[0] AS $i => $v) { foreach ($args[0] AS $i => $v) {
$args[0][$i] = self::$defaultNamespace . $v; $args[0][$i] = self::$defaultNamespace . $v;
} }
} else { }
else {
$args[0] = self::$defaultNamespace . $args[0]; $args[0] = self::$defaultNamespace . $args[0];
} }
} }
@ -174,4 +247,3 @@ class Resque_Redis
return $string; return $string;
} }
} }
?>

View File

@ -0,0 +1,182 @@
<?php
/**
* Resque_Redis DSN tests.
*
* @package Resque/Tests
* @author Iskandar Najmuddin <github@iskandar.co.uk>
* @license http://www.opensource.org/licenses/mit-license.php
*/
class Resque_Tests_DsnTest extends Resque_Tests_TestCase
{
/**
* These DNS strings are considered valid.
*
* @return array
*/
public function validDsnStringProvider()
{
return array(
// Input , Expected output
array('', array(
'localhost',
Resque_Redis::DEFAULT_PORT,
false,
false, false,
array(),
)),
array('localhost', array(
'localhost',
Resque_Redis::DEFAULT_PORT,
false,
false, false,
array(),
)),
array('localhost:1234', array(
'localhost',
1234,
false,
false, false,
array(),
)),
array('localhost:1234/2', array(
'localhost',
1234,
2,
false, false,
array(),
)),
array('redis://foobar', array(
'foobar',
Resque_Redis::DEFAULT_PORT,
false,
false, false,
array(),
)),
array('redis://foobar/', array(
'foobar',
Resque_Redis::DEFAULT_PORT,
false,
false, false,
array(),
)),
array('redis://foobar:1234', array(
'foobar',
1234,
false,
false, false,
array(),
)),
array('redis://foobar:1234/15', array(
'foobar',
1234,
15,
false, false,
array(),
)),
array('redis://foobar:1234/0', array(
'foobar',
1234,
0,
false, false,
array(),
)),
array('redis://user@foobar:1234', array(
'foobar',
1234,
false,
'user', false,
array(),
)),
array('redis://user@foobar:1234/15', array(
'foobar',
1234,
15,
'user', false,
array(),
)),
array('redis://user:pass@foobar:1234', array(
'foobar',
1234,
false,
'user', 'pass',
array(),
)),
array('redis://user:pass@foobar:1234?x=y&a=b', array(
'foobar',
1234,
false,
'user', 'pass',
array('x' => 'y', 'a' => 'b'),
)),
array('redis://:pass@foobar:1234?x=y&a=b', array(
'foobar',
1234,
false,
false, 'pass',
array('x' => 'y', 'a' => 'b'),
)),
array('redis://user@foobar:1234?x=y&a=b', array(
'foobar',
1234,
false,
'user', false,
array('x' => 'y', 'a' => 'b'),
)),
array('redis://foobar:1234?x=y&a=b', array(
'foobar',
1234,
false,
false, false,
array('x' => 'y', 'a' => 'b'),
)),
array('redis://user@foobar:1234/12?x=y&a=b', array(
'foobar',
1234,
12,
'user', false,
array('x' => 'y', 'a' => 'b'),
)),
array('tcp://user@foobar:1234/12?x=y&a=b', array(
'foobar',
1234,
12,
'user', false,
array('x' => 'y', 'a' => 'b'),
)),
);
}
/**
* These DSN values should throw exceptions
* @return array
*/
public function bogusDsnStringProvider()
{
return array(
array('http://foo.bar/'),
array('user:@foobar:1234?x=y&a=b'),
array('foobar:1234?x=y&a=b'),
);
}
/**
* @dataProvider validDsnStringProvider
*/
public function testParsingValidDsnString($dsn, $expected)
{
$result = Resque_Redis::parseDsn($dsn);
$this->assertEquals($expected, $result);
}
/**
* @dataProvider bogusDsnStringProvider
* @expectedException InvalidArgumentException
*/
public function testParsingBogusDsnStringThrowsException($dsn)
{
// The next line should throw an InvalidArgumentException
$result = Resque_Redis::parseDsn($dsn);
}
}