public function __call($name, $args)
{
// Lazy connection
$this->connect();
$name = strtolower($name);
// Send request via native PHP
if ($this->standalone) {
switch ($name) {
case 'eval':
case 'evalsha':
$script = array_shift($args);
$keys = (array) array_shift($args);
$eArgs = (array) array_shift($args);
$args = array($script, count($keys), $keys, $eArgs);
break;
case 'set':
// The php redis module has different behaviour with ttl
// https://github.com/phpredis/phpredis#set
if (count($args) === 3 && is_int($args[2])) {
$args = array($args[0], $args[1], array('EX', $args[2]));
} elseif (count($args) === 3 && is_array($args[2])) {
$tmp_args = $args;
$args = array($tmp_args[0], $tmp_args[1]);
foreach ($tmp_args[2] as $k => $v) {
if (is_string($k)) {
$args[] = array($k, $v);
} elseif (is_int($k)) {
$args[] = $v;
}
}
unset($tmp_args);
}
break;
case 'scan':
$ref =& $args[0];
if (empty($ref)) {
$ref = 0;
}
$eArgs = array($ref);
if (!empty($args[1])) {
$eArgs[] = 'MATCH';
$eArgs[] = $args[1];
}
if (!empty($args[2])) {
$eArgs[] = 'COUNT';
$eArgs[] = $args[2];
}
$args = $eArgs;
break;
case 'sscan':
case 'zscan':
case 'hscan':
$ref =& $args[1];
if (empty($ref)) {
$ref = 0;
}
$eArgs = array($args[0], $ref);
if (!empty($args[2])) {
$eArgs[] = 'MATCH';
$eArgs[] = $args[2];
}
if (!empty($args[3])) {
$eArgs[] = 'COUNT';
$eArgs[] = $args[4];
}
$args = $eArgs;
break;
case 'zrangebyscore':
case 'zrevrangebyscore':
case 'zrange':
case 'zrevrange':
if (isset($args[3]) && is_array($args[3])) {
// map options
$cArgs = array();
if (!empty($args[3]['withscores'])) {
$cArgs[] = 'withscores';
}
if (($name == 'zrangebyscore' || $name == 'zrevrangebyscore') && array_key_exists('limit', $args[3])) {
$cArgs[] = array('limit' => $args[3]['limit']);
}
$args[3] = $cArgs;
}
break;
}
// Flatten arguments
$args = self::_flattenArguments($args);
// In pipeline mode
if ($this->usePipeline) {
if ($name == 'pipeline') {
throw new CredisException('A pipeline is already in use and only one pipeline is supported.');
} else {
if ($name == 'exec') {
if ($this->isMulti) {
$this->commandNames[] = $name;
$this->commands .= self::_prepare_command(array($this->getRenamedCommand($name)));
}
// Write request
if ($this->commands) {
$this->write_command($this->commands);
}
$this->commands = NULL;
// Read response
$response = array();
foreach ($this->commandNames as $command) {
$response[] = $this->read_reply($command);
}
$this->commandNames = NULL;
if ($this->isMulti) {
$response = array_pop($response);
}
$this->usePipeline = $this->isMulti = FALSE;
return $response;
} else {
if ($name == 'multi') {
$this->isMulti = TRUE;
}
array_unshift($args, $this->getRenamedCommand($name));
$this->commandNames[] = $name;
$this->commands .= self::_prepare_command($args);
return $this;
}
}
}
// Start pipeline mode
if ($name == 'pipeline') {
$this->usePipeline = TRUE;
$this->commandNames = array();
$this->commands = '';
return $this;
}
// If unwatching, allow reconnect with no error thrown
if ($name == 'unwatch') {
$this->isWatching = FALSE;
}
// Non-pipeline mode
array_unshift($args, $this->getRenamedCommand($name));
$command = self::_prepare_command($args);
$this->write_command($command);
$response = $this->read_reply($name);
switch ($name) {
case 'scan':
case 'sscan':
$ref = array_shift($response);
$response = empty($response[0]) ? array() : $response[0];
break;
case 'hscan':
case 'zscan':
$ref = array_shift($response);
$response = empty($response[0]) ? array() : $response[0];
if (!empty($response) && is_array($response)) {
$count = count($response);
$out = array();
for ($i = 0; $i < $count; $i += 2) {
$out[$response[$i]] = $response[$i + 1];
}
$response = $out;
}
break;
case 'zrangebyscore':
case 'zrevrangebyscore':
if (in_array('withscores', $args, true)) {
// Map array of values into key=>score list like phpRedis does
$item = null;
$out = array();
foreach ($response as $value) {
if ($item == null) {
$item = $value;
} else {
// 2nd value is the score
$out[$item] = (double) $value;
$item = null;
}
}
$response = $out;
}
break;
}
// Watch mode disables reconnect so error is thrown
if ($name == 'watch') {
$this->isWatching = TRUE;
} else {
if ($this->isMulti && ($name == 'exec' || $name == 'discard')) {
$this->isMulti = FALSE;
} else {
if ($this->isMulti || $name == 'multi') {
$this->isMulti = TRUE;
$response = $this;
}
}
}
} else {
// Tweak arguments
switch ($name) {
case 'get':
// optimize common cases
// optimize common cases
case 'set':
case 'hget':
case 'hset':
case 'setex':
case 'mset':
case 'msetnx':
case 'hmset':
case 'hmget':
case 'del':
case 'zrangebyscore':
case 'zrevrangebyscore':
case 'zrange':
case 'zrevrange':
break;
case 'mget':
if (isset($args[0]) && !is_array($args[0])) {
$args = array($args);
}
break;
case 'lrem':
$args = array($args[0], $args[2], $args[1]);
break;
case 'eval':
case 'evalsha':
if (isset($args[1]) && is_array($args[1])) {
$cKeys = $args[1];
} elseif (isset($args[1]) && is_string($args[1])) {
$cKeys = array($args[1]);
} else {
$cKeys = array();
}
if (isset($args[2]) && is_array($args[2])) {
$cArgs = $args[2];
} elseif (isset($args[2]) && is_string($args[2])) {
$cArgs = array($args[2]);
} else {
$cArgs = array();
}
$args = array($args[0], array_merge($cKeys, $cArgs), count($cKeys));
break;
case 'subscribe':
case 'psubscribe':
break;
case 'scan':
case 'sscan':
case 'hscan':
case 'zscan':
// allow phpredis to see the caller's reference
//$param_ref =& $args[0];
break;
default:
// Flatten arguments
$args = self::_flattenArguments($args);
}
try {
// Proxy pipeline mode to the phpredis library
if ($name == 'pipeline' || $name == 'multi') {
if ($this->isMulti) {
return $this;
} else {
$this->isMulti = TRUE;
$this->redisMulti = call_user_func_array(array($this->redis, $name), $args);
return $this;
}
} else {
if ($name == 'exec' || $name == 'discard') {
$this->isMulti = FALSE;
$response = $this->redisMulti->{$name}();
$this->redisMulti = NULL;
#echo "> $name : ".substr(print_r($response, TRUE),0,100)."\n";
return $response;
}
}
// Use aliases to be compatible with phpredis wrapper
if (isset($this->wrapperMethods[$name])) {
$name = $this->wrapperMethods[$name];
}
// Multi and pipeline return self for chaining
if ($this->isMulti) {
call_user_func_array(array($this->redisMulti, $name), $args);
return $this;
}
// Send request, retry one time when using persistent connections on the first request only
$this->requests++;
try {
$response = call_user_func_array(array($this->redis, $name), $args);
} catch (RedisException $e) {
if ($this->persistent && $this->requests == 1 && $e->getMessage() == 'read error on connection') {
$this->connected = FALSE;
$this->connect();
$response = call_user_func_array(array($this->redis, $name), $args);
} else {
throw $e;
}
}
} catch (RedisException $e) {
$code = 0;
if (!($result = $this->redis->IsConnected())) {
$this->connected = FALSE;
$code = CredisException::CODE_DISCONNECTED;
}
throw new CredisException($e->getMessage(), $code, $e);
}
#echo "> $name : ".substr(print_r($response, TRUE),0,100)."\n";
// change return values where it is too difficult to minim in standalone mode
switch ($name) {
case 'hmget':
$response = array_values($response);
break;
case 'type':
$typeMap = array(self::TYPE_NONE, self::TYPE_STRING, self::TYPE_SET, self::TYPE_LIST, self::TYPE_ZSET, self::TYPE_HASH);
$response = $typeMap[$response];
break;
// Handle scripting errors
// Handle scripting errors
case 'eval':
case 'evalsha':
case 'script':
$error = $this->redis->getLastError();
$this->redis->clearLastError();
if ($error && substr($error, 0, 8) == 'NOSCRIPT') {
$response = NULL;
} else {
if ($error) {
throw new CredisException($error);
}
}
break;
default:
$error = $this->redis->getLastError();
$this->redis->clearLastError();
if ($error) {
throw new CredisException($error);
}
break;
}
}
return $response;
}