#!/usr/bin/php -dphar.readonly=1 2, 'c' => 'text/plain', 'cc' => 'text/plain', 'cpp' => 'text/plain', 'c++' => 'text/plain', 'dtd' => 'text/plain', 'h' => 'text/plain', 'log' => 'text/plain', 'rng' => 'text/plain', 'txt' => 'text/plain', 'xsd' => 'text/plain', 'php' => 1, 'inc' => 1, 'avi' => 'video/avi', 'bmp' => 'image/bmp', 'css' => 'text/css', 'gif' => 'image/gif', 'htm' => 'text/html', 'html' => 'text/html', 'htmls' => 'text/html', 'ico' => 'image/x-ico', 'jpe' => 'image/jpeg', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'js' => 'application/x-javascript', 'midi' => 'audio/midi', 'mid' => 'audio/midi', 'mod' => 'audio/mod', 'mov' => 'movie/quicktime', 'mp3' => 'audio/mp3', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg', 'pdf' => 'application/pdf', 'png' => 'image/png', 'swf' => 'application/shockwave-flash', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'wav' => 'audio/wav', 'xbm' => 'image/xbm', 'xml' => 'text/xml', ); header("Cache-Control: no-cache, must-revalidate"); header("Pragma: no-cache"); $basename = basename(__FILE__); if (!strpos($_SERVER['REQUEST_URI'], $basename)) { chdir(Extract_Phar::$temp); include $web; return; } $pt = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], $basename) + strlen($basename)); if (!$pt || $pt == '/') { $pt = $web; header('HTTP/1.1 301 Moved Permanently'); header('Location: ' . $_SERVER['REQUEST_URI'] . '/' . $pt); exit; } $a = realpath(Extract_Phar::$temp . DIRECTORY_SEPARATOR . $pt); if (!$a || strlen(dirname($a)) < strlen(Extract_Phar::$temp)) { header('HTTP/1.0 404 Not Found'); echo "\n \n File Not Found<title>\n </head>\n <body>\n <h1>404 - File ", $pt, " Not Found</h1>\n </body>\n</html>"; exit; } $b = pathinfo($a); if (!isset($b['extension'])) { header('Content-Type: text/plain'); header('Content-Length: ' . filesize($a)); readfile($a); exit; } if (isset($mimes[$b['extension']])) { if ($mimes[$b['extension']] === 1) { include $a; exit; } if ($mimes[$b['extension']] === 2) { highlight_file($a); exit; } header('Content-Type: ' .$mimes[$b['extension']]); header('Content-Length: ' . filesize($a)); readfile($a); exit; } } class Extract_Phar { static $temp; static $origdir; const GZ = 0x1000; const BZ2 = 0x2000; const MASK = 0x3000; const START = 'pharext_installer.php'; const LEN = 6697; static function go($return = false) { $fp = fopen(__FILE__, 'rb'); fseek($fp, self::LEN); $L = unpack('V', $a = (binary)fread($fp, 4)); $m = (binary)''; do { $read = 8192; if ($L[1] - strlen($m) < 8192) { $read = $L[1] - strlen($m); } $last = (binary)fread($fp, $read); $m .= $last; } while (strlen($last) && strlen($m) < $L[1]); if (strlen($m) < $L[1]) { die('ERROR: manifest length read was "' . strlen($m) .'" should be "' . $L[1] . '"'); } $info = self::_unpack($m); $f = $info['c']; if ($f & self::GZ) { if (!function_exists('gzinflate')) { die('Error: zlib extension is not enabled -' . ' gzinflate() function needed for zlib-compressed .phars'); } } if ($f & self::BZ2) { if (!function_exists('bzdecompress')) { die('Error: bzip2 extension is not enabled -' . ' bzdecompress() function needed for bz2-compressed .phars'); } } $temp = self::tmpdir(); if (!$temp || !is_writable($temp)) { $sessionpath = session_save_path(); if (strpos ($sessionpath, ";") !== false) $sessionpath = substr ($sessionpath, strpos ($sessionpath, ";")+1); if (!file_exists($sessionpath) || !is_dir($sessionpath)) { die('Could not locate temporary directory to extract phar'); } $temp = $sessionpath; } $temp .= '/pharextract/'.basename(__FILE__, '.phar'); self::$temp = $temp; self::$origdir = getcwd(); @mkdir($temp, 0777, true); $temp = realpath($temp); if (!file_exists($temp . DIRECTORY_SEPARATOR . md5_file(__FILE__))) { self::_removeTmpFiles($temp, getcwd()); @mkdir($temp, 0777, true); @file_put_contents($temp . '/' . md5_file(__FILE__), ''); foreach ($info['m'] as $path => $file) { $a = !file_exists(dirname($temp . '/' . $path)); @mkdir(dirname($temp . '/' . $path), 0777, true); clearstatcache(); if ($path[strlen($path) - 1] == '/') { @mkdir($temp . '/' . $path, 0777); } else { file_put_contents($temp . '/' . $path, self::extractFile($path, $file, $fp)); @chmod($temp . '/' . $path, 0666); } } } chdir($temp); if (!$return) { include self::START; } } static function tmpdir() { if (strpos(PHP_OS, 'WIN') !== false) { if ($var = getenv('TMP') ? getenv('TMP') : getenv('TEMP')) { return $var; } if (is_dir('/temp') || mkdir('/temp')) { return realpath('/temp'); } return false; } if ($var = getenv('TMPDIR')) { return $var; } return realpath('/tmp'); } static function _unpack($m) { $info = unpack('V', substr($m, 0, 4)); $l = unpack('V', substr($m, 10, 4)); $m = substr($m, 14 + $l[1]); $s = unpack('V', substr($m, 0, 4)); $o = 0; $start = 4 + $s[1]; $ret['c'] = 0; for ($i = 0; $i < $info[1]; $i++) { $len = unpack('V', substr($m, $start, 4)); $start += 4; $savepath = substr($m, $start, $len[1]); $start += $len[1]; $ret['m'][$savepath] = array_values(unpack('Va/Vb/Vc/Vd/Ve/Vf', substr($m, $start, 24))); $ret['m'][$savepath][3] = sprintf('%u', $ret['m'][$savepath][3] & 0xffffffff); $ret['m'][$savepath][7] = $o; $o += $ret['m'][$savepath][2]; $start += 24 + $ret['m'][$savepath][5]; $ret['c'] |= $ret['m'][$savepath][4] & self::MASK; } return $ret; } static function extractFile($path, $entry, $fp) { $data = ''; $c = $entry[2]; while ($c) { if ($c < 8192) { $data .= @fread($fp, $c); $c = 0; } else { $c -= 8192; $data .= @fread($fp, 8192); } } if ($entry[4] & self::GZ) { $data = gzinflate($data); } elseif ($entry[4] & self::BZ2) { $data = bzdecompress($data); } if (strlen($data) != $entry[0]) { die("Invalid internal .phar file (size error " . strlen($data) . " != " . $stat[7] . ")"); } if ($entry[3] != sprintf("%u", crc32((binary)$data) & 0xffffffff)) { die("Invalid internal .phar file (checksum error)"); } return $data; } static function _removeTmpFiles($temp, $origdir) { chdir($temp); foreach (glob('*') as $f) { if (file_exists($f)) { is_dir($f) ? @rmdir($f) : @unlink($f); if (file_exists($f) && is_dir($f)) { self::_removeTmpFiles($f, getcwd()); } } } @rmdir($temp); clearstatcache(); chdir($origdir); } } Extract_Phar::go(); __HALT_COMPILER(); ?>  ��;�����������"��a:8:{s:6:"header";s:61:"pharext v@PHAREXT_VERSION@ (c) Michael Wallner <mike@php.net>";s:7:"version";s:17:"@PHAREXT_VERSION@";s:4:"name";s:5:"redis";s:4:"date";s:10:"2015-05-11";s:4:"stub";s:21:"pharext_installer.php";s:7:"license";b:0;s:7:"release";s:5:"2.2.7";s:4:"type";s:9:"extension";}���pharext/Cli/Args.php��PU�� 7���������pharext/Cli/Command.php��PU�� *۶���������pharext/Command.php��PU��td\���������pharext/Exception.phpc��PUc��U{���������pharext/ExecCmd.php��PU��lʶ���������pharext/Installer.php��PU��WJ���������pharext/Openssl/PrivateKey.php��PU��&P���������pharext/Packager.php$"��PU$"��E���������pharext/SourceDir/Basic.php��PU��zZ���������pharext/SourceDir/Git.php��PU��bc���������pharext/SourceDir/Pecl.php��PU�����������pharext/SourceDir.php]��PU]��S ���������pharext/Task/Activate.phpp ��PUp ��EE���������pharext/Task/Askpass.phpU��PUU��*������ ���pharext/Task/BundleGenerator.php}��PU}�� `Y���������pharext/Task/Cleanup.php���PU���ZͶ���������pharext/Task/Configure.phpT��PUT��}���������pharext/Task/Extract.php��PU��ն���������pharext/Task/GitClone.phpm��PUm��y@���������pharext/Task/Make.php��PU��6 ���������pharext/Task/PaxFixup.php��PU��y���������pharext/Task/PeclFixup.php��PU��et���������pharext/Task/PharBuild.php��PU��D垶���������pharext/Task/PharCompress.phpr��PUr�� ���������pharext/Task/PharRename.php��PU��[˶���������pharext/Task/PharSign.php��PU��ۺi���������pharext/Task/Phpize.php��PU�� 2Ѷ���������pharext/Task/StreamFetch.php��PU��s\���������pharext/Task.phpw���PUw��� IǶ���������pharext/Tempdir.php��PU��,���������pharext/Tempfile.php��PU��#���������pharext/Tempname.php`��PU`��<Np���������pharext/Version.php@���PU@���C뵶���������pharext_installer.php���PU���pDZ���������pharext_packager.php���PU���1���������pharext_package.php2���PU2���vSTҶ������ ���package.xml"��PU"�� ���������tests/array-tests.php%7��PU%7��BŃ���������tests/memory.php��PU��am���������tests/mkring.sh��PU�����������tests/test.php ��PU ��X���������tests/TestRedis.php�PU�P? ���������README.markdown,p�PU,p�]k���������arrays.markdown��PU��_65���������CREDITS���PU���U���������COPYING ��PU ��Q������ ���config.m4 ��PU �� OѶ������ ���config.w32��PU��O���������common.hz��PUz��"������ ���library.cP��PUP��3������ ���library.ho��PUo��|y˶������ ���php_redis.h"��PU"��[ ������ ���redis_array.cޓ��PUޓ��ӑ������ ���redis_array.h��PU��% |���������redis_array_impl.c��PU��8Jn���������redis_array_impl.hL��PUL��m�<���������redis.cӆ�PUӆ�fY���������redis_session.c/��PU/��S���������redis_session.h���PU���;b������<?php namespace pharext\Cli; /** * Command line arguments */ class Args implements \ArrayAccess { /** * Optional option */ const OPTIONAL = 0x000; /** * Required Option */ const REQUIRED = 0x001; /** * Only one value, even when used multiple times */ const SINGLE = 0x000; /** * Aggregate an array, when used multiple times */ const MULTI = 0x010; /** * Option takes no argument */ const NOARG = 0x000; /** * Option requires an argument */ const REQARG = 0x100; /** * Option takes an optional argument */ const OPTARG = 0x200; /** * Option halts processing */ const HALT = 0x10000000; /** * Original option spec * @var array */ private $orig = []; /** * Compiled spec * @var array */ private $spec = []; /** * Parsed args * @var array */ private $args = []; /** * Compile the original spec * @param array|Traversable $spec */ public function __construct($spec = null) { if (is_array($spec) || $spec instanceof Traversable) { $this->compile($spec); } } /** * Compile the original spec * @param array|Traversable $spec * @return pharext\CliArgs self */ public function compile($spec) { foreach ($spec as $arg) { if (isset($arg[0])) { $this->spec["-".$arg[0]] = $arg; } $this->spec["--".$arg[1]] = $arg; $this->orig[] = $arg; } return $this; } /** * Get original spec * @return array */ public function getSpec() { return $this->orig; } /** * Get compiled spec * @return array */ public function getCompiledSpec() { return $this->spec; } /** * Parse command line arguments according to the compiled spec. * * The Generator yields any parsing errors. * Parsing will stop when all arguments are processed or the first option * flagged CliArgs::HALT was encountered. * * @param int $argc * @param array $argv * @return Generator */ public function parse($argc, array $argv) { for ($i = 0; $i < $argc; ++$i) { $o = $argv[$i]; if ($o{0} === '-' && strlen($o) > 2 && $o{1} !== '-') { // multiple short opts, .e.g -vps $argc += strlen($o) - 2; array_splice($argv, $i, 1, array_map(function($s) { return "-$s"; }, str_split(substr($o, 1)))); $o = $argv[$i]; } elseif ($o{0} === '-' && strlen($o) > 2 && $o{1} === '-' && 0 < ($eq = strpos($o, "="))) { $argc++; array_splice($argv, $i, 1, [ substr($o, 0, $eq++), substr($o, $eq) ]); $o = $argv[$i]; } if (!isset($this->spec[$o])) { yield sprintf("Unknown option %s", $o); } elseif (!$this->optAcceptsArg($o)) { $this[$o] = true; } elseif ($i+1 < $argc && !isset($this->spec[$argv[$i+1]])) { $this[$o] = $argv[++$i]; } elseif ($this->optRequiresArg($o)) { yield sprintf("Option --%s requires an argument", $this->optLongName($o)); } else { // OPTARG $this[$o] = $this->optDefaultArg($o); } if ($this->optHalts($o)) { return; } } } /** * Validate that all required options were given. * * The Generator yields any validation errors. * * @return Generator */ public function validate() { $required = array_filter($this->orig, function($spec) { return $spec[3] & self::REQUIRED; }); foreach ($required as $req) { if (!strlen($this[$req[0]])) { yield sprintf("Option --%s is required", $req[1]); } } } public function toArray() { $args = []; foreach ($this->spec as $spec) { $opt = $this->opt($spec[1]); $args[$opt] = $this[$opt]; } return $args; } /** * Retreive the default argument of an option * @param string $o * @return mixed */ private function optDefaultArg($o) { $o = $this->opt($o); if (isset($this->spec[$o][4])) { return $this->spec[$o][4]; } return null; } /** * Retrieve the help message of an option * @param string $o * @return string */ private function optHelp($o) { $o = $this->opt($o); if (isset($this->spec[$o][2])) { return $this->spec[$o][2]; } return ""; } /** * Retrieve option's flags * @param string $o * @return int */ private function optFlags($o) { $o = $this->opt($o); if (isset($this->spec[$o])) { return $this->spec[$o][3]; } return null; } /** * Check whether an option is flagged for halting argument processing * @param string $o * @return boolean */ private function optHalts($o) { return $this->optFlags($o) & self::HALT; } /** * Check whether an option needs an argument * @param string $o * @return boolean */ private function optRequiresArg($o) { return $this->optFlags($o) & self::REQARG; } /** * Check wether an option accepts any argument * @param string $o * @return boolean */ private function optAcceptsArg($o) { return $this->optFlags($o) & 0xf00; } /** * Check whether an option can be used more than once * @param string $o * @return boolean */ private function optIsMulti($o) { return $this->optFlags($o) & self::MULTI; } /** * Retreive the long name of an option * @param string $o * @return string */ private function optLongName($o) { $o = $this->opt($o); return $this->spec[$o][1]; } /** * Retreive the short name of an option * @param string $o * @return string */ private function optShortName($o) { $o = $this->opt($o); return $this->spec[$o][0]; } /** * Retreive the canonical name (--long-name) of an option * @param string $o * @return string */ private function opt($o) { if ($o{0} !== '-') { if (strlen($o) > 1) { $o = "-$o"; } $o = "-$o"; } return $o; } /**@+ * Implements ArrayAccess and virtual properties */ function offsetExists($o) { $o = $this->opt($o); return isset($this->args[$o]); } function __isset($o) { return $this->offsetExists($o); } function offsetGet($o) { $o = $this->opt($o); if (isset($this->args[$o])) { return $this->args[$o]; } return $this->optDefaultArg($o); } function __get($o) { return $this->offsetGet($o); } function offsetSet($o, $v) { $osn = $this->optShortName($o); $oln = $this->optLongName($o); if ($this->optIsMulti($o)) { if (isset($osn)) { $this->args["-$osn"][] = $v; } $this->args["--$oln"][] = $v; } else { if (isset($osn)) { $this->args["-$osn"] = $v; } $this->args["--$oln"] = $v; } } function __set($o, $v) { $this->offsetSet($o, $v); } function offsetUnset($o) { unset($this->args["-".$this->optShortName($o)]); unset($this->args["--".$this->optLongName($o)]); } function __unset($o) { $this->offsetUnset($o); } /**@-*/ } <?php namespace pharext\Cli; use pharext\Cli\Args as CliArgs; use Phar; if (!function_exists("array_column")) { function array_column(array $array, $col, $idx = null) { $result = []; foreach ($array as $el) { if (isset($idx)) { $result[$el[$idx]] = $el[$col]; } else { $result[] = $el[$col]; } } return $result; } } trait Command { /** * Command line arguments * @var pharext\CliArgs */ private $args; /** * @inheritdoc * @see \pharext\Command::getArgs() */ public function getArgs() { return $this->args; } /** * Retrieve metadata of the currently running phar * @param string $key * @return mixed */ public function metadata($key = null) { $running = new Phar(Phar::running(false)); if ($key === "signature") { $sig = $running->getSignature(); return sprintf("%s signature of %s\n%s", $sig["hash_type"], $this->metadata("name"), chunk_split($sig["hash"], 64, "\n")); } $metadata = $running->getMetadata(); if (isset($key)) { return $metadata[$key]; } return $metadata; } /** * Output pharext vX.Y.Z header */ public function header() { if (!headers_sent()) { /* only display header, if we didn't generate any output yet */ printf("%s\n\n", $this->metadata("header")); } } /** * @inheritdoc * @see \pharext\Command::debug() */ public function debug($fmt) { if ($this->args->verbose) { vprintf($fmt, array_slice(func_get_args(), 1)); } } /** * @inheritdoc * @see \pharext\Command::info() */ public function info($fmt) { if (!$this->args->quiet) { vprintf($fmt, array_slice(func_get_args(), 1)); } } /** * @inheritdoc * @see \pharext\Command::warn() */ public function warn($fmt) { if (!$this->args->quiet) { if (!isset($fmt)) { $fmt = "%s\n"; $arg = error_get_last()["message"]; } else { $arg = array_slice(func_get_args(), 1); } vfprintf(STDERR, "Warning: $fmt", $arg); } } /** * @inheritdoc * @see \pharext\Command::error() */ public function error($fmt) { if (!isset($fmt)) { $fmt = "%s\n"; $arg = error_get_last()["message"]; } else { $arg = array_slice(func_get_args(), 1); } vfprintf(STDERR, "ERROR: $fmt", $arg); } /** * Output command line help message * @param string $prog */ public function help($prog) { printf("Usage:\n\n \$ %s", $prog); $flags = []; $required = []; $optional = []; foreach ($this->args->getSpec() as $spec) { if ($spec[3] & CliArgs::REQARG) { if ($spec[3] & CliArgs::REQUIRED) { $required[] = $spec; } else { $optional[] = $spec; } } else { $flags[] = $spec; } } if ($flags) { printf(" [-%s]", implode("", array_column($flags, 0))); } foreach ($required as $req) { printf(" -%s <arg>", $req[0]); } if ($optional) { printf(" [-%s <arg>]", implode("|-", array_column($optional, 0))); } printf("\n\n"); $spc = $this->args->getSpec(); $max = max(array_map("strlen", array_column($spc, 1))); $max += $max % 8 + 2; foreach ($spc as $spec) { if (isset($spec[0])) { printf(" -%s|", $spec[0]); } else { printf(" "); } printf("--%s ", $spec[1]); if ($spec[3] & CliArgs::REQARG) { printf("<arg> "); } elseif ($spec[3] & CliArgs::OPTARG) { printf("[<arg>]"); } else { printf(" "); } printf("%s%s", str_repeat(" ", $max-strlen($spec[1])+3*!isset($spec[0])), $spec[2]); if ($spec[3] & CliArgs::REQUIRED) { printf(" (REQUIRED)"); } if (isset($spec[4])) { printf(" [%s]", $spec[4]); } printf("\n"); } printf("\n"); } /** * Verbosity * @return boolean */ public function verbosity() { if ($this->args->verbose) { return true; } elseif ($this->args->quiet) { return false; } else { return null; } } } <?php namespace pharext; /** * Command interface */ interface Command { /** * Argument error */ const EARGS = 1; /** * Build error */ const EBUILD = 2; /** * Signature error */ const ESIGN = 3; /** * Extract/unpack error */ const EEXTRACT = 4; /** * Install error */ const EINSTALL = 5; /** * Retrieve command line arguments * @return pharext\CliArgs */ public function getArgs(); /** * Print debug message * @param string $fmt * @param string ...$args */ public function debug($fmt); /** * Print info * @param string $fmt * @param string ...$args */ public function info($fmt); /** * Print warning * @param string $fmt * @param string ...$args */ public function warn($fmt); /** * Print error * @param string $fmt * @param string ...$args */ public function error($fmt); /** * Execute the command * @param int $argc command line argument count * @param array $argv command line argument list */ public function run($argc, array $argv); } <?php namespace pharext; class Exception extends \Exception { public function __construct($message = null, $code = 0, $previous = null) { if (!isset($message)) { $last_error = error_get_last(); $message = $last_error["message"]; if (!$code) { $code = $last_error["type"]; } } parent::__construct($message, $code, $previous); } } <?php namespace pharext; /** * Execute system command */ class ExecCmd { /** * Sudo command, if the cmd needs escalated privileges * @var string */ private $sudo; /** * Executable of the cmd * @var string */ private $command; /** * Passthrough cmd output * @var bool */ private $verbose; /** * Output of cmd run * @var string */ private $output; /** * Return code of cmd run * @var int */ private $status; /** * @param string $command * @param bool verbose */ public function __construct($command, $verbose = false) { $this->command = $command; $this->verbose = $verbose; } /** * (Re-)set sudo command * @param string $sudo */ public function setSu($sudo = false) { $this->sudo = $sudo; } /** * Execute a program with escalated privileges handling interactive password prompt * @param string $command * @param bool $verbose * @return int exit status */ private function suExec($command, $verbose = null) { if (!($proc = proc_open($command, [STDIN,["pipe","w"],["pipe","w"]], $pipes))) { $this->status = -1; throw new Exception("Failed to run {$command}"); } $stdout = $pipes[1]; $passwd = 0; $checks = 10; while (!feof($stdout)) { $R = [$stdout]; $W = []; $E = []; if (!stream_select($R, $W, $E, null)) { continue; } $data = fread($stdout, 0x1000); /* only check a few times */ if ($passwd < $checks) { $passwd++; if (stristr($data, "password")) { $passwd = $checks + 1; printf("\n%s", $data); continue; } } elseif ($passwd > $checks) { /* new line after pw entry */ printf("\n"); $passwd = $checks; } if ($verbose === null) { print $this->progress($data, 0); } else { if ($verbose) { printf("%s", $data); } $this->output .= $data; } } if ($verbose === null) { $this->progress("", PHP_OUTPUT_HANDLER_FINAL); } return $this->status = proc_close($proc); } /** * Output handler that displays some progress while soaking output * @param string $string * @param int $flags * @return string */ private function progress($string, $flags) { static $counter = 0; static $symbols = ["\\","|","/","-"]; $this->output .= $string; if (false !== strpos($string, "\n")) { ++$counter; } return $flags & PHP_OUTPUT_HANDLER_FINAL ? " \r" : sprintf(" %s\r", $symbols[$counter % 4]); } /** * Run the command * @param array $args * @return \pharext\ExecCmd self * @throws \pharext\Exception */ public function run(array $args = null) { $exec = escapeshellcmd($this->command); if ($args) { $exec .= " ". implode(" ", array_map("escapeshellarg", (array) $args)); } if ($this->sudo) { $this->suExec(sprintf($this->sudo." 2>&1", $exec), $this->verbose); } elseif ($this->verbose) { ob_start(function($s) { $this->output .= $s; return $s; }, 1); passthru($exec, $this->status); ob_end_flush(); } elseif ($this->verbose !== false /* !quiet */) { ob_start([$this, "progress"], 1); passthru($exec . " 2>&1", $this->status); ob_end_flush(); } else { exec($exec ." 2>&1", $output, $this->status); $this->output = implode("\n", $output); } if ($this->status) { throw new Exception("Command {$exec} failed ({$this->status})"); } return $this; } /** * Retrieve exit code of cmd run * @return int */ public function getStatus() { return $this->status; } /** * Retrieve output of cmd run * @return string */ public function getOutput() { return $this->output; } } <?php namespace pharext; use pharext\Cli\Args as CliArgs; use pharext\Cli\Command as CliCommand; use Phar; use SplObjectStorage; /** * The extension install command executed by the extension phar */ class Installer implements Command { use CliCommand; /** * Cleanups * @var array */ private $cleanup = []; /** * Create the command */ public function __construct() { $this->args = new CliArgs([ ["h", "help", "Display help", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], ["v", "verbose", "More output", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["q", "quiet", "Less output", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["p", "prefix", "PHP installation prefix if phpize is not in \$PATH, e.g. /opt/php7", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG], ["n", "common-name", "PHP common program name, e.g. php5 or zts-php", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG, "php"], ["c", "configure", "Additional extension configure flags, e.g. -c --with-flag", CliArgs::OPTIONAL|CliArgs::MULTI|CliArgs::REQARG], ["s", "sudo", "Installation might need increased privileges", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::OPTARG, "sudo -S %s"], ["i", "ini", "Activate in this php.ini instead of loaded default php.ini", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG], [null, "signature", "Show package signature", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], [null, "license", "Show package license", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], [null, "name", "Show package name", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], [null, "date", "Show package release date", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], [null, "release", "Show package release version", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], [null, "version", "Show pharext version", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], ]); } /** * Perform cleaniup */ function __destruct() { foreach ($this->cleanup as $cleanup) { $cleanup->run(); } } private function extract(Phar $phar) { $temp = (new Task\Extract($phar))->run($this->verbosity()); $this->cleanup[] = new Task\Cleanup($temp); return $temp; } private function hooks(SplObjectStorage $phars) { $hook = []; foreach ($phars as $phar) { if (isset($phar["pharext_package.php"])) { $sdir = include $phar["pharext_package.php"]; if ($sdir instanceof SourceDir) { $this->args->compile($sdir->getArgs()); $hook[] = $sdir; } } } return $hook; } private function load() { $list = new SplObjectStorage(); $phar = new Phar(Phar::running(false)); $temp = $this->extract($phar); foreach ($phar as $entry) { $dep_file = $entry->getBaseName(); if (fnmatch("*.ext.phar*", $dep_file)) { $dep_phar = new Phar("$temp/$dep_file"); $list[$dep_phar] = $this->extract($dep_phar); } } /* the actual ext.phar at last */ $list[$phar] = $temp; return $list; } /** * @inheritdoc * @see \pharext\Command::run() */ public function run($argc, array $argv) { try { /* load the phar(s) */ $list = $this->load(); /* installer hooks */ $hook = $this->hooks($list); } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EEXTRACT); } /* standard arg stuff */ $errs = []; $prog = array_shift($argv); foreach ($this->args->parse(--$argc, $argv) as $error) { $errs[] = $error; } if ($this->args["help"]) { $this->header(); $this->help($prog); exit; } try { foreach (["signature", "name", "date", "license", "release", "version"] as $opt) { if ($this->args[$opt]) { printf("%s\n", $this->metadata($opt)); exit; } } } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EARGS); } foreach ($this->args->validate() as $error) { $errs[] = $error; } if ($errs) { if (!$this->args["quiet"]) { $this->header(); } foreach ($errs as $err) { $this->error("%s\n", $err); } if (!$this->args["quiet"]) { $this->help($prog); } exit(self::EARGS); } try { /* post process hooks */ foreach ($hook as $sdir) { $sdir->setArgs($this->args); } } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EARGS); } /* install packages */ try { foreach ($list as $phar) { $this->info("Installing %s ...\n", basename($phar->getPath())); $this->install($list[$phar]); $this->activate($list[$phar]); $this->info("Successfully installed %s!\n", basename($phar->getPath())); } } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EINSTALL); } } /** * Phpize + trinity */ private function install($temp) { // phpize $phpize = new Task\Phpize($temp, $this->args->prefix, $this->args->{"common-name"}); $phpize->run($this->verbosity()); // configure $configure = new Task\Configure($temp, $this->args->configure, $this->args->prefix, $this->args{"common-name"}); $configure->run($this->verbosity()); // make $make = new Task\Make($temp); $make->run($this->verbosity()); // install $sudo = isset($this->args->sudo) ? $this->args->sudo : null; $install = new Task\Make($temp, ["install"], $sudo); $install->run($this->verbosity()); } private function activate($temp) { if ($this->args->ini) { $files = [realpath($this->args->ini)]; } else { $files = array_filter(array_map("trim", explode(",", php_ini_scanned_files()))); $files[] = php_ini_loaded_file(); } $sudo = isset($this->args->sudo) ? $this->args->sudo : null; $type = $this->metadata("type") ?: "extension"; $activate = new Task\Activate($temp, $files, $type, $this->args->prefix, $this->args{"common-name"}, $sudo); if (!$activate->run($this->verbosity())) { $this->info("Extension already activated ...\n"); } } } <?php namespace pharext\Openssl; use pharext\Exception; class PrivateKey { /** * Private key * @var string */ private $key; /** * Public key * @var string */ private $pub; /** * Read a private key * @param string $file * @param string $password * @throws \pharext\Exception */ function __construct($file, $password) { /* there appears to be a bug with refcount handling of this * resource; when the resource is stored as property, it cannot be * "coerced to a private key" on openssl_sign() later in another method */ $key = openssl_pkey_get_private("file://$file", $password); if (!is_resource($key)) { throw new Exception("Could not load private key"); } openssl_pkey_export($key, $this->key); $this->pub = openssl_pkey_get_details($key)["key"]; } /** * Sign the PHAR * @param \Phar $package */ function sign(\Phar $package) { $package->setSignatureAlgorithm(\Phar::OPENSSL, $this->key); } /** * Export the public key to a file * @param string $file * @throws \pharext\Exception */ function exportPublicKey($file) { if (!file_put_contents("$file.tmp", $this->pub) || !rename("$file.tmp", $file)) { throw new Exception; } } } <?php namespace pharext; use Phar; use pharext\Cli\Args as CliArgs; use pharext\Cli\Command as CliCommand; use pharext\Exception; /** * The extension packaging command executed by bin/pharext */ class Packager implements Command { use CliCommand; /** * Extension source directory * @var pharext\SourceDir */ private $source; /** * Cleanups * @var array */ private $cleanup = []; /** * Create the command */ public function __construct() { $this->args = new CliArgs([ ["h", "help", "Display this help", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], ["v", "verbose", "More output", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["q", "quiet", "Less output", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["n", "name", "Extension name", CliArgs::REQUIRED|CliArgs::SINGLE|CliArgs::REQARG], ["r", "release", "Extension release version", CliArgs::REQUIRED|CliArgs::SINGLE|CliArgs::REQARG], ["s", "source", "Extension source directory", CliArgs::REQUIRED|CliArgs::SINGLE|CliArgs::REQARG], ["g", "git", "Use `git ls-tree` to determine file list", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["b", "branch", "Checkout this tag/branch", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG], ["p", "pecl", "Use PECL package.xml to determine file list, name and release", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["d", "dest", "Destination directory", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG, "."], ["z", "gzip", "Create additional PHAR compressed with gzip", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["Z", "bzip", "Create additional PHAR compressed with bzip", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["S", "sign", "Sign the PHAR with a private key", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG], ["E", "zend", "Mark as Zend Extension", CliArgs::OPTIONAL|CliARgs::SINGLE|CliArgs::NOARG], [null, "signature", "Show pharext signature", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], [null, "license", "Show pharext license", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], [null, "version", "Show pharext version", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], ]); } /** * Perform cleaniup */ function __destruct() { foreach ($this->cleanup as $cleanup) { $cleanup->run(); } } /** * @inheritdoc * @see \pharext\Command::run() */ public function run($argc, array $argv) { $errs = []; $prog = array_shift($argv); foreach ($this->args->parse(--$argc, $argv) as $error) { $errs[] = $error; } if ($this->args["help"]) { $this->header(); $this->help($prog); exit; } try { foreach (["signature", "license", "version"] as $opt) { if ($this->args[$opt]) { printf("%s\n", $this->metadata($opt)); exit; } } } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EARGS); } try { /* source needs to be evaluated before CliArgs validation, * so e.g. name and version can be overriden and CliArgs * does not complain about missing arguments */ $this->loadSource(); } catch (\Exception $e) { $errs[] = $e->getMessage(); } foreach ($this->args->validate() as $error) { $errs[] = $error; } if ($errs) { if (!$this->args["quiet"]) { $this->header(); } foreach ($errs as $err) { $this->error("%s\n", $err); } printf("\n"); if (!$this->args["quiet"]) { $this->help($prog); } exit(self::EARGS); } $this->createPackage(); } /** * Download remote source * @param string $source * @return string local source */ private function download($source) { if ($this->args->git) { $task = new Task\GitClone($source, $this->args->branch); } else { /* print newline only once */ $done = false; $task = new Task\StreamFetch($source, function($bytes_pct) use(&$done) { if (!$done) { $this->info(" %3d%% [%s>%s] \r", floor($bytes_pct*100), str_repeat("=", round(50*$bytes_pct)), str_repeat(" ", round(50*(1-$bytes_pct))) ); if ($bytes_pct == 1) { $done = true; printf("\n"); } } }); } $local = $task->run($this->verbosity()); $this->cleanup[] = new Task\Cleanup($local); return $local; } /** * Extract local archive * @param stirng $source * @return string extracted directory */ private function extract($source) { try { $task = new Task\Extract($source); $dest = $task->run($this->verbosity()); } catch (\Exception $e) { if (false === strpos($e->getMessage(), "checksum mismatch")) { throw $e; } $dest = (new Task\PaxFixup($source))->run($this->verbosity()); } $this->cleanup[] = new Task\Cleanup($dest); return $dest; } /** * Localize a possibly remote source * @param string $source * @return string local source directory */ private function localize($source) { if (!stream_is_local($source) || ($this->args->git && isset($this->args->branch))) { $source = $this->download($source); $this->cleanup[] = new Task\Cleanup($source); } $source = realpath($source); if (!is_dir($source)) { $source = $this->extract($source); $this->cleanup[] = new Task\Cleanup($source); if (!$this->args->git) { $source = (new Task\PeclFixup($source))->run($this->verbosity()); } } return $source; } /** * Load the source dir * @throws \pharext\Exception */ private function loadSource(){ if ($this->args["source"]) { $source = $this->localize($this->args["source"]); if ($this->args["pecl"]) { $this->source = new SourceDir\Pecl($source); } elseif ($this->args["git"]) { $this->source = new SourceDir\Git($source); } elseif (is_file("$source/pharext_package.php")) { $this->source = include "$source/pharext_package.php"; } else { $this->source = new SourceDir\Basic($source); } if (!$this->source instanceof SourceDir) { throw new Exception("Unknown source dir $source"); } foreach ($this->source->getPackageInfo() as $key => $val) { $this->args->$key = $val; } } } /** * Creates the extension phar */ private function createPackage() { try { $meta = array_merge($this->metadata(), [ "date" => date("Y-m-d"), "name" => $this->args->name, "release" => $this->args->release, "license" => @file_get_contents(current(glob($this->source->getBaseDir()."/LICENSE*"))), "stub" => "pharext_installer.php", "type" => $this->args->zend ? "zend_extension" : "extension", ]); $file = (new Task\PharBuild($this->source, $meta))->run($this->verbosity()); } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EBUILD); } try { if ($this->args->sign) { $this->info("Using private key to sign phar ...\n"); $pass = (new Task\Askpass)->run($this->verbosity()); $sign = new Task\PharSign($file, $this->args->sign, $pass); $pkey = $sign->run($this->verbosity()); } } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::ESIGN); } if ($this->args->gzip) { try { $gzip = (new Task\PharCompress($file, Phar::GZ))->run(); $move = new Task\PharRename($gzip, $this->args->dest, $this->args->name ."-". $this->args->release); $name = $move->run($this->verbosity()); $this->info("Created gzipped phar %s\n", $name); if ($this->args->sign) { $sign = new Task\PharSign($name, $this->args->sign, $pass); $sign->run($this->verbosity())->exportPublicKey($name.".pubkey"); } } catch (\Exception $e) { $this->warn("%s\n", $e->getMessage()); } } if ($this->args->bzip) { try { $bzip = (new Task\PharCompress($file, Phar::BZ2))->run(); $move = new Task\PharRename($bzip, $this->args->dest, $this->args->name ."-". $this->args->release); $name = $move->run($this->verbosity()); $this->info("Created bzipped phar %s\n", $name); if ($this->args->sign) { $sign = new Task\PharSign($name, $this->args->sign, $pass); $sign->run($this->verbosity())->exportPublicKey($name.".pubkey"); } } catch (\Exception $e) { $this->warn("%s\n", $e->getMessage()); } } try { $move = new Task\PharRename($file, $this->args->dest, $this->args->name ."-". $this->args->release); $name = $move->run($this->verbosity()); $this->info("Created executable phar %s\n", $name); if (isset($pkey)) { $pkey->exportPublicKey($name.".pubkey"); } } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EBUILD); } } } <?php namespace pharext\SourceDir; use pharext\Cli\Args; use pharext\SourceDir; use FilesystemIterator; use IteratorAggregate; use RecursiveCallbackFilterIterator; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; class Basic implements IteratorAggregate, SourceDir { private $path; public function __construct($path) { $this->path = $path; } public function getBaseDir() { return $this->path; } public function getPackageInfo() { return []; } public function getArgs() { return []; } public function setArgs(Args $args) { } public function filter($current, $key, $iterator) { $sub = $current->getSubPath(); if ($sub === ".git" || $sub === ".hg" || $sub === ".svn") { return false; } return true; } public function getIterator() { $rdi = new RecursiveDirectoryIterator($this->path, FilesystemIterator::CURRENT_AS_SELF | // needed for 5.5 FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::SKIP_DOTS); $rci = new RecursiveCallbackFilterIterator($rdi, [$this, "filter"]); $rii = new RecursiveIteratorIterator($rci); foreach ($rii as $path => $child) { if (!$child->isDir()) { yield $path; } } } } <?php namespace pharext\SourceDir; use pharext\Command; use pharext\Cli\Args; use pharext\SourceDir; /** * Extension source directory which is a git repo */ class Git implements \IteratorAggregate, SourceDir { /** * Base directory * @var string */ private $path; /** * @inheritdoc * @see \pharext\SourceDir::__construct() */ public function __construct($path) { $this->path = $path; } /** * @inheritdoc * @see \pharext\SourceDir::getBaseDir() */ public function getBaseDir() { return $this->path; } /** * @inheritdoc * @return array */ public function getPackageInfo() { return []; } /** * @inheritdoc * @return array */ public function getArgs() { return []; } /** * @inheritdoc */ public function setArgs(Args $args) { } /** * Generate a list of files by `git ls-files` * @return Generator */ private function generateFiles() { $pwd = getcwd(); chdir($this->path); if (($pipe = popen("git ls-tree -r --name-only HEAD", "r"))) { $path = realpath($this->path); while (!feof($pipe)) { if (strlen($file = trim(fgets($pipe)))) { /* there may be symlinks, so no realpath here */ yield "$path/$file"; } } pclose($pipe); } chdir($pwd); } /** * Implements IteratorAggregate * @see IteratorAggregate::getIterator() */ public function getIterator() { return $this->generateFiles(); } } <?php namespace pharext\SourceDir; use pharext\Cli\Args; use pharext\Exception; use pharext\SourceDir; use pharext\Tempfile; /** * A PECL extension source directory containing a v2 package.xml */ class Pecl implements \IteratorAggregate, SourceDir { /** * The package.xml * @var SimpleXmlElement */ private $sxe; /** * The base directory * @var string */ private $path; /** * The package.xml * @var string */ private $file; /** * @inheritdoc * @see \pharext\SourceDir::__construct() */ public function __construct($path) { if (is_file("$path/package2.xml")) { $sxe = simplexml_load_file($this->file = "$path/package2.xml"); } elseif (is_file("$path/package.xml")) { $sxe = simplexml_load_file($this->file = "$path/package.xml"); } else { throw new Exception("Missing package.xml in $path"); } $sxe->registerXPathNamespace("pecl", $sxe->getDocNamespaces()[""]); $this->sxe = $sxe; $this->path = realpath($path); } /** * @inheritdoc * @see \pharext\SourceDir::getBaseDir() */ public function getBaseDir() { return $this->path; } /** * Retrieve gathered package info * @return Generator */ public function getPackageInfo() { if (($name = $this->sxe->xpath("/pecl:package/pecl:name"))) { yield "name" => (string) $name[0]; } if (($release = $this->sxe->xpath("/pecl:package/pecl:version/pecl:release"))) { yield "release" => (string) $release[0]; } if ($this->sxe->xpath("/pecl:package/pecl:zendextsrcrelease")) { yield "zend" => true; } } /** * @inheritdoc * @see \pharext\SourceDir::getArgs() */ public function getArgs() { $configure = $this->sxe->xpath("/pecl:package/pecl:extsrcrelease/pecl:configureoption"); foreach ($configure as $cfg) { yield [null, $cfg["name"], ucfirst($cfg["prompt"]), Args::OPTARG, strlen($cfg["default"]) ? $cfg["default"] : null]; } $configure = $this->sxe->xpath("/pecl:package/pecl:zendextsrcrelease/pecl:configureoption"); foreach ($configure as $cfg) { yield [null, $cfg["name"], ucfirst($cfg["prompt"]), Args::OPTARG, strlen($cfg["default"]) ? $cfg["default"] : null]; } } /** * @inheritdoc * @see \pharext\SourceDir::setArgs() */ public function setArgs(Args $args) { $configure = $this->sxe->xpath("/pecl:package/pecl:extsrcrelease/pecl:configureoption"); foreach ($configure as $cfg) { if (isset($args[$cfg["name"]])) { $args->configure = "--{$cfg["name"]}={$args[$cfg["name"]]}"; } } $configure = $this->sxe->xpath("/pecl:package/pecl:zendextsrcrelease/pecl:configureoption"); foreach ($configure as $cfg) { if (isset($args[$cfg["name"]])) { $args->configure = "--{$cfg["name"]}={$args[$cfg["name"]]}"; } } } /** * Compute the path of a file by parent dir nodes * @param \SimpleXMLElement $ele * @return string */ private function dirOf($ele) { $path = ""; while (($ele = current($ele->xpath(".."))) && $ele->getName() == "dir") { $path = trim($ele["name"], "/") ."/". $path ; } return trim($path, "/"); } /** * Generate a list of files from the package.xml * @return Generator */ private function generateFiles() { /* hook */ $temp = tmpfile(); fprintf($temp, "<?php\nreturn new %s(__DIR__);\n", get_class($this)); rewind($temp); yield "pharext_package.php" => $temp; /* deps */ $dependencies = $this->sxe->xpath("/pecl:package/pecl:dependencies/pecl:required/pecl:package"); foreach ($dependencies as $key => $dep) { if (($glob = glob("{$this->path}/{$dep->name}-*.ext.phar*"))) { usort($glob, function($a, $b) { return version_compare( substr($a, strpos(".ext.phar", $a)), substr($b, strpos(".ext.phar", $b)) ); }); yield end($glob); } } /* files */ yield realpath($this->file); foreach ($this->sxe->xpath("//pecl:file") as $file) { yield realpath($this->path ."/". $this->dirOf($file) ."/". $file["name"]); } } /** * Implements IteratorAggregate * @see IteratorAggregate::getIterator() */ public function getIterator() { return $this->generateFiles(); } } <?php namespace pharext; /** * Source directory interface, which should yield file names to package on traversal */ interface SourceDir extends \Traversable { /** * Retrieve the base directory * @return string */ public function getBaseDir(); /** * Retrieve gathered package info * @return array|Traversable */ public function getPackageInfo(); /** * Provide installer command line args * @return array|Traversable */ public function getArgs(); /** * Process installer command line args * @param \pharext\Cli\Args $args */ public function setArgs(Cli\Args $args); } <?php namespace pharext\Task; use pharext\Exception; use pharext\ExecCmd; use pharext\Task; use pharext\Tempfile; /** * PHP INI activation */ class Activate implements Task { /** * @var string */ private $cwd; /** * @var array */ private $inis; /** * @var string */ private $type; /** * @var string */ private $php_config; /** * @var string */ private $sudo; /** * @param string $cwd working directory * @param array $inis custom INI or list of loaded/scanned INI files * @param string $type extension or zend_extension * @param string $prefix install prefix, e.g. /usr/local * @param string $common_name PHP programs common name, e.g. php5 * @param string $sudo sudo command * @throws \pharext\Exception */ public function __construct($cwd, array $inis, $type = "extension", $prefix = null, $common_name = "php", $sudo = null) { $this->cwd = $cwd; $this->type = $type; $this->sudo = $sudo; if (!$this->inis = $inis) { throw new Exception("No PHP INIs given"); } $cmd = $common_name . "-config"; if (isset($prefix)) { $cmd = $prefix . "/bin/" . $cmd; } $this->php_config = $cmd; } /** * @param bool $verbose * @return boolean false, if extension was already activated */ public function run($verbose = false) { if ($verbose !== false) { printf("Running INI activation ...\n"); } $extension = basename(current(glob("{$this->cwd}/modules/*.so"))); if ($this->type === "zend_extension") { $pattern = preg_quote((new ExecCmd($this->php_config))->run(["--extension-dir"])->getOutput() . "/$extension", "/"); } else { $pattern = preg_quote($extension, "/"); } foreach ($this->inis as $file) { if ($verbose) { printf("Checking %s ...\n", $file); } $temp = new Tempfile("phpini"); foreach (file($file) as $line) { if (preg_match("/^\s*{$this->type}\s*=\s*[\"']?{$pattern}[\"']?\s*(;.*)?\$/", $line)) { return false; } fwrite($temp->getStream(), $line); } } /* not found; append to last processed file, which is the main by default */ if ($verbose) { printf("Activating in %s ...\n", $file); } fprintf($temp->getStream(), $this->type . "=%s\n", $extension); $temp->closeStream(); $path = $temp->getPathname(); $stat = stat($file); // owner transfer $ugid = sprintf("%d:%d", $stat["uid"], $stat["gid"]); $cmd = new ExecCmd("chown", $verbose); if (isset($this->sudo)) { $cmd->setSu($this->sudo); } $cmd->run([$ugid, $path]); // permission transfer $perm = decoct($stat["mode"] & 0777); $cmd = new ExecCmd("chmod", $verbose); if (isset($this->sudo)) { $cmd->setSu($this->sudo); } $cmd->run([$perm, $path]); // rename $cmd = new ExecCmd("mv", $verbose); if (isset($this->sudo)) { $cmd->setSu($this->sudo); } $cmd->run([$path, $file]); if ($verbose) { printf("Replaced %s ...\n", $file); } return true; } } <?php namespace pharext\Task; use pharext\Task; /** * Ask password on console */ class Askpass implements Task { /** * @var string */ private $prompt; /** * @param string $prompt */ public function __construct($prompt = "Password:") { $this->prompt = $prompt; } /** * @param bool $verbose * @return string */ public function run($verbose = false) { system("stty -echo"); printf("%s ", $this->prompt); $pass = fgets(STDIN, 1024); printf("\n"); system("stty echo"); if (substr($pass, -1) == "\n") { $pass = substr($pass, 0, -1); } return $pass; } } <?php namespace pharext\Task; use pharext\Task; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; /** * List all library files of pharext to bundle with a phar */ class BundleGenerator implements Task { /** * @param bool $verbose * @return Generator */ public function run($verbose = false) { if ($verbose) { printf("Packaging pharext ... \n"); } $rdi = new RecursiveDirectoryIterator(dirname(dirname(__DIR__))); $rii = new RecursiveIteratorIterator($rdi); for ($rii->rewind(); $rii->valid(); $rii->next()) { if (!$rii->isDot()) { yield $rii->getSubPathname() => $rii->key(); } } } } <?php namespace pharext\Task; use pharext\Task; use FilesystemIterator; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; /** * Recursively cleanup FS entries */ class Cleanup implements Task { /** * @var string */ private $rm; public function __construct($rm) { $this->rm = $rm; } /** * @param bool $verbose */ public function run($verbose = false) { if ($verbose) { printf("Cleaning up %s ...\n", $this->rm); } if ($this->rm instanceof Tempfile) { unset($this->rm); } elseif (is_dir($this->rm)) { $rdi = new RecursiveDirectoryIterator($this->rm, FilesystemIterator::CURRENT_AS_SELF | // needed for 5.5 FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::SKIP_DOTS); $rii = new RecursiveIteratorIterator($rdi, RecursiveIteratorIterator::CHILD_FIRST); foreach ($rii as $path => $child) { if ($child->isDir()) { rmdir($path); } else { unlink($path); } } rmdir($this->rm); } else { @unlink($this->rm); } } } <?php namespace pharext\Task; use pharext\Exception; use pharext\ExecCmd; use pharext\Task; /** * Runs extension's configure */ class Configure implements Task { /** * @var array */ private $args; /** * @var string */ private $cwd; /** * @param string $cwd working directory * @param array $args configure args * @param string $prefix install prefix, e.g. /usr/local * @param string $common_name PHP programs common name, e.g. php5 */ public function __construct($cwd, array $args = null, $prefix = null, $common_name = "php") { $this->cwd = $cwd; $cmd = $common_name . "-config"; if (isset($prefix)) { $cmd = $prefix . "/bin/" . $cmd; } $this->args = ["--with-php-config=$cmd"]; if ($args) { $this->args = array_merge($this->args, $args); } } public function run($verbose = false) { if ($verbose !== false) { printf("Running ./configure ...\n"); } $pwd = getcwd(); if (!chdir($this->cwd)) { throw new Exception; } try { $cmd = new ExecCmd("./configure", $verbose); $cmd->run($this->args); } finally { chdir($pwd); } } } <?php namespace pharext\Task; use pharext\Task; use pharext\Tempdir; use Phar; use PharData; /** * Extract a package archive */ class Extract implements Task { /** * @var Phar(Data) */ private $source; /** * @param mixed $source archive location */ public function __construct($source) { if ($source instanceof Phar || $source instanceof PharData) { $this->source = $source; } else { $this->source = new PharData($source); } } /** * @param bool $verbose * @return \pharext\Tempdir */ public function run($verbose = false) { if ($verbose) { printf("Extracting %s ...\n", basename($this->source->getPath())); } $dest = new Tempdir("extract"); $this->source->extractTo($dest); return $dest; } } <?php namespace pharext\Task; use pharext\ExecCmd; use pharext\Task; use pharext\Tempdir; /** * Clone a git repo */ class GitClone implements Task { /** * @var string */ private $source; /** * @var string */ private $branch; /** * @param string $source git repo location */ public function __construct($source, $branch = null) { $this->source = $source; $this->branch = $branch; } /** * @param bool $verbose * @return \pharext\Tempdir */ public function run($verbose = false) { if ($verbose !== false) { printf("Fetching %s ...\n", $this->source); } $local = new Tempdir("gitclone"); $cmd = new ExecCmd("git", $verbose); if (strlen($this->branch)) { $cmd->run(["clone", "--depth", 1, "--branch", $this->branch, $this->source, $local]); } else { $cmd->run(["clone", $this->source, $local]); } return $local; } } <?php namespace pharext\Task; use pharext\ExecCmd; use pharext\Exception; use pharext\Task; /** * Run make in the source dir */ class Make implements Task { /** * @var string */ private $cwd; /** * @var array */ private $args; /** * @var string */ private $sudo; /** * * @param string $cwd working directory * @param array $args make's arguments * @param string $sudo sudo command */ public function __construct($cwd, array $args = null, $sudo = null) { $this->cwd = $cwd; $this->sudo = $sudo; $this->args = $args; } /** * * @param bool $verbose * @throws \pharext\Exception */ public function run($verbose = false) { if ($verbose !== false) { printf("Running make"); if ($this->args) { foreach ($this->args as $arg) { printf(" %s", $arg); } } printf(" ...\n"); } $pwd = getcwd(); if (!chdir($this->cwd)) { throw new Exception; } try { $cmd = new ExecCmd("make", $verbose); if (isset($this->sudo)) { $cmd->setSu($this->sudo); } $args = $this->args; if (!$verbose) { $args = array_merge((array) $args, ["-s"]); } $cmd->run($args); } finally { chdir($pwd); } } } <?php namespace pharext\Task; use pharext\Exception; use pharext\Task; use pharext\Tempfile; class PaxFixup implements Task { private $source; public function __construct($source) { $this->source = $source; } private function openArchive($source) { $hdr = file_get_contents($source, false, null, 0, 3); if ($hdr === "\x1f\x8b\x08") { $fd = fopen("compress.zlib://$source", "r"); } elseif ($hdr === "BZh") { $fd = fopen("compress.bzip2://$source", "r"); } else { $fd = fopen($source, "r"); } if (!is_resource($fd)) { throw new Exception; } return $fd; } public function run($verbose = false) { if ($verbose !== false) { printf("Fixing up a tarball with global pax header ...\n"); } $temp = new Tempfile("paxfix"); stream_copy_to_stream($this->openArchive($this->source), $temp->getStream(), -1, 1024); $temp->closeStream(); return (new Extract((string) $temp))->run($verbose); } }<?php namespace pharext\Task; use pharext\Exception; use pharext\Task; /** * Fixup package.xml files in an extracted PECL dir */ class PeclFixup implements Task { /** * @var string */ private $source; /** * @param string $source source directory */ public function __construct($source) { $this->source = $source; } /** * @param bool $verbose * @return string sanitized source location * @throws \pahrext\Exception */ public function run($verbose = false) { if ($verbose !== false) { printf("Sanitizing PECL dir ...\n"); } $dirs = glob("{$this->source}/*", GLOB_ONLYDIR); $files = array_diff(glob("{$this->source}/*"), $dirs); $check = array_reduce($files, function($r, $v) { return $v && fnmatch("package*.xml", basename($v)); }, true); if (count($dirs) !== 1 || !$check) { throw new Exception("Does not look like an extracted PECL dir: {$this->source}"); } $dest = current($dirs); foreach ($files as $file) { if ($verbose) { printf("Moving %s into %s ...\n", basename($file), basename($dest)); } if (!rename($file, "$dest/" . basename($file))) { throw new Exception; } } return $dest; } } <?php namespace pharext\Task; use pharext\Exception; use pharext\SourceDir; use pharext\Task; use pharext\Tempname; use Phar; /** * Build phar */ class PharBuild implements Task { /** * @var \pharext\SourceDir */ private $source; /** * @var array */ private $meta; /** * @var bool */ private $readonly; /** * @param SourceDir $source extension source directory * @param array $meta phar meta data * @param bool $readonly whether the stub has -dphar.readonly=1 set */ public function __construct(SourceDir $source = null, array $meta = null, $readonly = true) { $this->source = $source; $this->meta = $meta; $this->readonly = $readonly; } /** * @param bool $verbose * @return \pharext\Tempname * @throws \pharext\Exception */ public function run($verbose = false) { /* Phar::compress() and ::convert*() use strtok("."), ugh! * so, be sure to not use any other dots in the filename * except for .phar */ $temp = new Tempname("", "-pharext.phar"); $phar = new Phar($temp); $phar->startBuffering(); if ($this->meta) { $phar->setMetadata($this->meta); if (isset($this->meta["stub"])) { $phar->setDefaultStub($this->meta["stub"]); $phar->setStub("#!/usr/bin/php -dphar.readonly=" . intval($this->readonly) ."\n". $phar->getStub()); } } $phar->buildFromIterator((new Task\BundleGenerator)->run()); if ($this->source) { if ($verbose) { $bdir = $this->source->getBaseDir(); $blen = strlen($bdir); foreach ($this->source as $index => $file) { if (is_resource($file)) { printf("Packaging %s ...\n", $index); $phar[$index] = $file; } else { printf("Packaging %s ...\n", $index = trim(substr($file, $blen), "/")); $phar->addFile($file, $index); } } } else { $phar->buildFromIterator($this->source, $this->source->getBaseDir()); } } $phar->stopBuffering(); if (!chmod($temp, fileperms($temp) | 0111)) { throw new Exception; } return $temp; } }<?php namespace pharext\Task; use pharext\Task; use Phar; /** * Clone a compressed copy of a phar */ class PharCompress implements Task { /** * @var string */ private $file; /** * @var Phar */ private $package; /** * @var int */ private $encoding; /** * @var string */ private $extension; /** * @param string $file path to the original phar * @param int $encoding Phar::GZ or Phar::BZ2 */ public function __construct($file, $encoding) { $this->file = $file; $this->package = new Phar($file); $this->encoding = $encoding; switch ($encoding) { case Phar::GZ: $this->extension = ".gz"; break; case Phar::BZ2: $this->extension = ".bz2"; break; } } /** * @param bool $verbose * @return string */ public function run($verbose = false) { if ($verbose) { printf("Compressing %s ...\n", basename($this->package->getPath())); } $phar = $this->package->compress($this->encoding); $meta = $phar->getMetadata(); if (isset($meta["stub"])) { /* drop shebang */ $phar->setDefaultStub($meta["stub"]); } return $this->file . $this->extension; } } <?php namespace pharext\Task; use pharext\Exception; use pharext\Task; /** * Rename the phar archive */ class PharRename implements Task { /** * @var string */ private $phar; /** * @var string */ private $dest; /** * @var string */ private $name; /** * @param string $phar path to phar * @param string $dest destination dir * @param string $name package name */ public function __construct($phar, $dest, $name) { $this->phar = $phar; $this->dest = $dest; $this->name = $name; } /** * @param bool $verbose * @return string path to renamed phar * @throws \pharext\Exception */ public function run($verbose = false) { $extension = substr(strstr($this->phar, "-pharext.phar"), 8); $name = sprintf("%s/%s.ext%s", $this->dest, $this->name, $extension); if ($verbose) { printf("Renaming %s to %s ...\n", basename($this->phar), basename($name)); } if (!rename($this->phar, $name)) { throw new Exception; } return $name; } } <?php namespace pharext\Task; use pharext\Openssl; use pharext\Task; use Phar; /** * Sign the phar with a private key */ class PharSign implements Task { /** * @var Phar */ private $phar; /** * @var \pharext\Openssl\PrivateKey */ private $pkey; /** * * @param mixed $phar phar instance or path to phar * @param string $pkey path to private key * @param string $pass password for the private key */ public function __construct($phar, $pkey, $pass) { if ($phar instanceof Phar || $phar instanceof PharData) { $this->phar = $phar; } else { $this->phar = new Phar($phar); } $this->pkey = new Openssl\PrivateKey($pkey, $pass); } /** * @param bool $verbose * @return \pharext\Openssl\PrivateKey */ public function run($verbose = false) { if ($verbose) { printf("Signing %s ...\n", basename($this->phar->getPath())); } $this->pkey->sign($this->phar); return $this->pkey; } } <?php namespace pharext\Task; use pharext\Exception; use pharext\ExecCmd; use pharext\Task; /** * Run phpize in the extension source directory */ class Phpize implements Task { /** * @var string */ private $phpize; /** * * @var string */ private $cwd; /** * @param string $cwd working directory * @param string $prefix install prefix, e.g. /usr/local * @param string $common_name PHP program common name, e.g. php5 */ public function __construct($cwd, $prefix = null, $common_name = "php") { $this->cwd = $cwd; $cmd = $common_name . "ize"; if (isset($prefix)) { $cmd = $prefix . "/bin/" . $cmd; } $this->phpize = $cmd; } /** * @param bool $verbose * @throws \pharext\Exception */ public function run($verbose = false) { if ($verbose !== false) { printf("Running %s ...\n", $this->phpize); } $pwd = getcwd(); if (!chdir($this->cwd)) { throw new Exception; } try { $cmd = new ExecCmd($this->phpize, $verbose); $cmd->run(); } finally { chdir($pwd); } } } <?php namespace pharext\Task; use pharext\Exception; use pharext\Task; use pharext\Tempfile; /** * Fetch a remote archive */ class StreamFetch implements Task { /** * @var string */ private $source; /** * @var callable */ private $progress; /** * @param string $source remote file location * @param callable $progress progress callback */ public function __construct($source, callable $progress) { $this->source = $source; $this->progress = $progress; } private function createStreamContext() { $progress = $this->progress; /* avoid bytes_max bug of older PHP versions */ $maxbytes = 0; return stream_context_create([],["notification" => function($notification, $severity, $message, $code, $bytes_cur, $bytes_max) use($progress, &$maxbytes) { if ($bytes_max > $maxbytes) { $maxbytes = $bytes_max; } switch ($notification) { case STREAM_NOTIFY_CONNECT: $progress(0); break; case STREAM_NOTIFY_PROGRESS: $progress($maxbytes > 0 ? $bytes_cur/$maxbytes : .5); break; case STREAM_NOTIFY_COMPLETED: /* this is sometimes not generated, why? */ $progress(1); break; } }]); } /** * @param bool $verbose * @return \pharext\Task\Tempfile * @throws \pharext\Exception */ public function run($verbose = false) { if ($verbose !== false) { printf("Fetching %s ...\n", $this->source); } $context = $this->createStreamContext(); if (!$remote = fopen($this->source, "r", false, $context)) { throw new Exception; } $local = new Tempfile("remote"); if (!stream_copy_to_stream($remote, $local->getStream())) { throw new Exception; } $local->closeStream(); /* STREAM_NOTIFY_COMPLETED is not generated, see above */ call_user_func($this->progress, 1); return $local; } } <?php namespace pharext; /** * Simple task interface */ interface Task { public function run($verbose = false); } <?php namespace pharext; /** * Create a temporary directory */ class Tempdir extends \SplFileInfo { /** * @param string $prefix prefix to uniqid() * @throws \pharext\Exception */ public function __construct($prefix) { $temp = new Tempname($prefix); if (!is_dir($temp) && !mkdir($temp, 0700, true)) { throw new Exception("Could not create tempdir: ".error_get_last()["message"]); } parent::__construct($temp); } } <?php namespace pharext; /** * Create a new temporary file */ class Tempfile extends \SplFileInfo { /** * @var resource */ private $handle; /** * @param string $prefix uniqid() prefix * @param string $suffix e.g. file extension * @throws \pharext\Exception */ public function __construct($prefix, $suffix = ".tmp") { $tries = 0; $omask = umask(077); do { $path = new Tempname($prefix, $suffix); $this->handle = fopen($path, "x"); } while (!is_resource($this->handle) && $tries++ < 10); umask($omask); if (!is_resource($this->handle)) { throw new Exception("Could not create temporary file"); } parent::__construct($path); } /** * Unlink the file */ public function __destruct() { @unlink($this->getPathname()); } /** * Close the stream */ public function closeStream() { fclose($this->handle); } /** * Retrieve the stream resource * @return resource */ public function getStream() { return $this->handle; } } <?php namespace pharext; use pharext\Exception; /** * A temporary file/directory name */ class Tempname { /** * @var string */ private $name; /** * @param string $prefix uniqid() prefix * @param string $suffix e.g. file extension */ public function __construct($prefix, $suffix = null) { $temp = sys_get_temp_dir() . "/pharext-" . posix_getlogin(); if (!is_dir($temp) && !mkdir($temp, 0700, true)) { throw new Exception; } $this->name = $temp ."/". uniqid($prefix) . $suffix; } /** * @return string */ public function __toString() { return (string) $this->name; } } <?php namespace pharext; const VERSION = "@PHAREXT_VERSION@"; <?php /** * The installer sub-stub for extension phars */ spl_autoload_register(function($c) { return include strtr($c, "\\_", "//") . ".php"; }); $installer = new pharext\Installer(); $installer->run($argc, $argv); <?php /** * The packager sub-stub for bin/pharext */ spl_autoload_register(function($c) { return include strtr($c, "\\_", "//") . ".php"; }); $packager = new pharext\Packager(); $packager->run($argc, $argv); <?php return new pharext\SourceDir\Pecl(__DIR__); <?xml version="1.0" encoding="ISO-8859-1"?> <package packagerversion="1.9.5" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd"> <name>redis</name> <channel>pecl.php.net</channel> <summary>PHP extension for interfacing with Redis</summary> <description>This extension provides an API for communicating with Redis servers.</description> <lead> <name>Nicolas Favre-Felix</name> <user>nff</user> <email>n.favrefelix@gmail.com</email> <active>yes</active> </lead> <lead> <name>Michael Grunder</name> <user>mgrunder</user> <email>michael.grunder@gmail.com</email> <active>yes</active> </lead> <date>2015-03-03</date> <time>16:01:22</time> <version> <release>2.2.7</release> <api>2.2.7</api> </version> <stability> <release>stable</release> <api>stable</api> </stability> <license uri="http://www.php.net/license">PHP</license> <notes> phpredis 2.2.7 -- Improvements --- * Implemented PFADD, PFMERGE, and PFCOUNT command handling * Implemented ZRANGEBYLEX command (holding off on ZREVRANGEBYLEX as that won't be out until 3.0) * Implemented getMode() so clients can detect whether we're in ATOMIC/MULTI/PIPELINE mode. * Implemented rawCommand() so clients can send arbitrary things to the redis server * Implemented DEBUG OBJECT (@michael-grunder, @isage) * Added/abide by connect timeout for RedisArray * Select to the last selected DB when phpredis reconnects -- Fixes --- * Fix a possible invalid free in _serialize * Added SAVE and BGSAVE to "distributable" commands for RedisArray * @welting -- Fixed invalid "argc" calculation re HLL commands * Allow clients to break out of the subscribe loop and return context. * Fixes a memory leak in SCAN when OPT_SCAN_RETRY id. * @remicollet -- Fix possible segfault when igbinary is enabled. * Add a couple of cases where we throw on an error (LOADING/NOAUTH/MASTERDOWN) * Fix several issues with serialization NARY * @itcom -- Fix missing TSRMLS_CC and a TSRMLS_DC/TSRMLS_CC typo </notes> <contents> <dir name="/"> <file md5sum="f10f321850d5dc07c0655e041847153b" name="tests/array-tests.php" role="test" /> <file md5sum="0723c02df1d2ca3dbfb758c554c989ad" name="tests/memory.php" role="test" /> <file md5sum="e9ddcb5f7abcef8507c394d8386f4742" name="tests/mkring.sh" role="test" /> <file md5sum="6c0a076fc65ce69da2fe2f1c7eba996d" name="tests/test.php" role="test" /> <file md5sum="0b95a5d739fb3e170aec6bbb7b48cf75" name="tests/TestRedis.php" role="test" /> <file md5sum="5291228c3cfca6bfc53a4906f3619f7e" name="README.markdown" role="doc" /> <file md5sum="45ab5b906f4dbe202b607df6a6b2c2fa" name="arrays.markdown" role="doc" /> <file md5sum="d56374bf738d2b10cb1f97cd7c9cfb30" name="CREDITS" role="doc" /> <file md5sum="cb564efdf78cce8ea6e4b5a4f7c05d97" name="COPYING" role="doc" /> <file md5sum="d06f81b166948f20bca8b16b2a5e9816" name="config.m4" role="src" /> <file md5sum="508a97287ede07a76f31e83597e69d2d" name="config.w32" role="src" /> <file md5sum="7f01a683331e81cfc6ebb0430c79e5b8" name="common.h" role="src" /> <file md5sum="a3ec1c30b71dc5fc8fbc22ed75146213" name="library.c" role="src" /> <file md5sum="a21efa2b1cd19d86ecf68be960aed781" name="library.h" role="src" /> <file md5sum="3d26699062b9e6dd501b7e80dbce2f81" name="php_redis.h" role="src" /> <file md5sum="40aff84d0c4001f3bdf40d14fef26a91" name="redis_array.c" role="src" /> <file md5sum="73f53430cdcf4e54bc635fac76d0ebc6" name="redis_array.h" role="src" /> <file md5sum="aa18a66f719c011b5850da74c87d5ee1" name="redis_array_impl.c" role="src" /> <file md5sum="72b88f5dae02f78527a0d243c2fa03d2" name="redis_array_impl.h" role="src" /> <file md5sum="667605089801db60e6795170439d9950" name="redis.c" role="src" /> <file md5sum="adb325494d8526fe02ad114930380724" name="redis_session.c" role="src" /> <file md5sum="9852b80834e10ebe8a14d957a1eba203" name="redis_session.h" role="src" /> </dir> </contents> <dependencies> <required> <php> <min>5.2.0</min> <max>6.0.0</max> <exclude>6.0.0</exclude> </php> <pearinstaller> <min>1.4.0b1</min> </pearinstaller> </required> </dependencies> <providesextension>redis</providesextension> <extsrcrelease /> <changelog> <release> <stability> <release>stable</release> <api>stable</api> </stability> <version> <release>2.2.7</release> <api>2.2.7</api> </version> <date>2015-03-03</date> <notes> phpredis 2.2.7 -- Improvements --- * Implemented PFADD, PFMERGE, and PFCOUNT command handling * Implemented ZRANGEBYLEX command (holding off on ZREVRANGEBYLEX as that won't be out until 3.0) * Implemented getMode() so clients can detect whether we're in ATOMIC/MULTI/PIPELINE mode. * Implemented rawCommand() so clients can send arbitrary things to the redis server * Implemented DEBUG OBJECT (@michael-grunder, @isage) * Added/abide by connect timeout for RedisArray * Select to the last selected DB when phpredis reconnects -- Fixes --- * Fix a possible invalid free in _serialize * Added SAVE and BGSAVE to "distributable" commands for RedisArray * @welting -- Fixed invalid "argc" calculation re HLL commands * Allow clients to break out of the subscribe loop and return context. * Fixes a memory leak in SCAN when OPT_SCAN_RETRY id. * @remicollet -- Fix possible segfault when igbinary is enabled. * Add a couple of cases where we throw on an error (LOADING/NOAUTH/MASTERDOWN) * Fix several issues with serialization NARY * @itcom -- Fix missing TSRMLS_CC and a TSRMLS_DC/TSRMLS_CC typo </notes> </release> <release> <stability> <release>stable</release> <api>stable</api> </stability> <version> <release>2.2.5</release> <api>2.2.5</api> </version> <date>2014-03-15</date> <notes> phpredis 2.2.5 This is a minor release with several bug fixes as well as additions to support new commands that have been introduced to Redis since our last release. A special thanks to everyone who helps the project by commenting on issues and submitting pull requests! :) [NEW] Support for the BITPOS command [NEW] Connection timeout option for RedisArray (@MikeToString) [NEW] A _serialize method, to complement our existing _unserialize method [NEW] Support for the PUBSUB command [NEW] Support for SCAN, SSCAN, HSCAN, and ZSCAN [NEW] Support for the WAIT command [FIX] Handle the COPY and REPLACE arguments for the MIGRATE command [DOC] Fix syntax error in documentation for the SET command (@mithunsatheesh) [DOC] Homebrew documentation instructions (@mathias) </notes> </release> <release> <stability> <release>stable</release> <api>stable</api> </stability> <version> <release>2.2.4</release> <api>2.2.4</api> </version> <date>2013-09-01</date> <notes> ** ** Features / Improvements ** * Randomized reconnect delay for RedisArray @mobli This feature adds an optional parameter when constructing a RedisArray object such that a random delay will be introduced if reconnections are made, mitigating any 'thundering herd' type problems. * Lazy connections to RedisArray servers @mobli By default, RedisArray will attempt to connect to each server you pass in the ring on construction. This feature lets you specify that you would rather have RedisArray only attempt a connection when it needs to get data from a particular node (throughput/performance improvement). * Allow LONG and STRING keys in MGET/MSET * Extended SET options for Redis >= 2.6.12 * Persistent connections and UNIX SOCKET support for RedisArray * Allow aggregates for ZUNION/ZINTER without weights @mheijkoop * Support for SLOWLOG command * Reworked MGET algorithm to run in linear time regardless of key count. * Reworked ZINTERSTORE/ZUNIONSTORE algorithm to run in linear time ** ** Bug fixes ** * C99 Compliance (or rather lack thereof) fix @mobli * Added ZEND_ACC_CTOR and ZEND_ACC_DTOR @euskadi31 * Stop throwing and clearing an exception on connect failure @matmoi * Fix a false positive unit test failure having to do with TTL returns </notes> </release> <release> <stability> <release>stable</release> <api>stable</api> </stability> <version> <release>2.2.3</release> <api>2.2.3</api> </version> <date>2013-04-29</date> <notes> First release to PECL </notes> </release> </changelog> </package> <?php require_once(dirname($_SERVER['PHP_SELF'])."/test.php"); echo "Redis Array tests.\n\n"; function custom_hash($str) { // str has the following format: $APPID_fb$FACEBOOKID_$key. $pos = strpos($str, '_fb'); if(preg_match("#\w+_fb(?<facebook_id>\d+)_\w+#", $str, $out)) { return $out['facebook_id']; } return $str; } class Redis_Array_Test extends TestSuite { private $strings; public $ra = NULL; private $data = NULL; public function setUp() { // initialize strings. $n = REDIS_ARRAY_DATA_SIZE; $this->strings = array(); for($i = 0; $i < $n; $i++) { $this->strings['key-'.$i] = 'val-'.$i; } global $newRing, $oldRing, $useIndex; $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex)); } public function testMSet() { // run mset $this->assertTrue(TRUE === $this->ra->mset($this->strings)); // check each key individually using the array foreach($this->strings as $k => $v) { $this->assertTrue($v === $this->ra->get($k)); } // check each key individually using a new connection foreach($this->strings as $k => $v) { list($host, $port) = split(':', $this->ra->_target($k)); $r = new Redis; $r->pconnect($host, (int)$port); $this->assertTrue($v === $r->get($k)); } } public function testMGet() { $this->assertTrue(array_values($this->strings) === $this->ra->mget(array_keys($this->strings))); } private function addData($commonString) { $this->data = array(); for($i = 0; $i < REDIS_ARRAY_DATA_SIZE; $i++) { $k = rand().'_'.$commonString.'_'.rand(); $this->data[$k] = rand(); } $this->ra->mset($this->data); } private function checkCommonLocality() { // check that they're all on the same node. $lastNode = NULL; foreach($this->data as $k => $v) { $node = $this->ra->_target($k); if($lastNode) { $this->assertTrue($node === $lastNode); } $this->assertTrue($this->ra->get($k) == $v); $lastNode = $node; } } public function testKeyLocality() { // basic key locality with default hash $this->addData('{hashed part of the key}'); $this->checkCommonLocality(); // with common hashing function global $newRing, $oldRing, $useIndex; $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'function' => 'custom_hash')); // basic key locality with custom hash $this->addData('fb'.rand()); $this->checkCommonLocality(); } public function customDistributor($key) { $a = unpack("N*", md5($key, true)); global $newRing; $pos = abs($a[1]) % count($newRing); return $pos; } public function testKeyDistributor() { global $newRing, $useIndex; $this->ra = new RedisArray($newRing, array( 'index' => $useIndex, 'function' => 'custom_hash', 'distributor' => array($this, "customDistributor"))); // custom key distribution function. $this->addData('fb'.rand()); // check that they're all on the expected node. $lastNode = NULL; foreach($this->data as $k => $v) { $node = $this->ra->_target($k); $pos = $this->customDistributor($k); $this->assertTrue($node === $newRing[$pos]); } } } class Redis_Rehashing_Test extends TestSuite { public $ra = NULL; private $useIndex; // data private $strings; private $sets; private $lists; private $hashes; private $zsets; public function setUp() { // initialize strings. $n = REDIS_ARRAY_DATA_SIZE; $this->strings = array(); for($i = 0; $i < $n; $i++) { $this->strings['key-'.$i] = 'val-'.$i; } // initialize sets for($i = 0; $i < $n; $i++) { // each set has 20 elements $this->sets['set-'.$i] = range($i, $i+20); } // initialize lists for($i = 0; $i < $n; $i++) { // each list has 20 elements $this->lists['list-'.$i] = range($i, $i+20); } // initialize hashes for($i = 0; $i < $n; $i++) { // each hash has 5 keys $this->hashes['hash-'.$i] = array('A' => $i, 'B' => $i+1, 'C' => $i+2, 'D' => $i+3, 'E' => $i+4); } // initialize sorted sets for($i = 0; $i < $n; $i++) { // each sorted sets has 5 elements $this->zsets['zset-'.$i] = array($i, 'A', $i+1, 'B', $i+2, 'C', $i+3, 'D', $i+4, 'E'); } global $newRing, $oldRing, $useIndex; // create array $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex)); } public function testFlush() { // flush all servers first. global $serverList; foreach($serverList as $s) { list($host, $port) = explode(':', $s); $r = new Redis; $r->pconnect($host, (int)$port); $r->flushdb(); } } private function distributeKeys() { // strings foreach($this->strings as $k => $v) { $this->ra->set($k, $v); } // sets foreach($this->sets as $k => $v) { call_user_func_array(array($this->ra, 'sadd'), array_merge(array($k), $v)); } // lists foreach($this->lists as $k => $v) { call_user_func_array(array($this->ra, 'rpush'), array_merge(array($k), $v)); } // hashes foreach($this->hashes as $k => $v) { $this->ra->hmset($k, $v); } // sorted sets foreach($this->zsets as $k => $v) { call_user_func_array(array($this->ra, 'zadd'), array_merge(array($k), $v)); } } public function testDistribution() { $this->distributeKeys(); } public function testSimpleRead() { $this->readAllvalues(); } private function readAllvalues() { // strings foreach($this->strings as $k => $v) { $this->assertTrue($this->ra->get($k) === $v); } // sets foreach($this->sets as $k => $v) { $ret = $this->ra->smembers($k); // get values // sort sets sort($v); sort($ret); $this->assertTrue($ret == $v); } // lists foreach($this->lists as $k => $v) { $ret = $this->ra->lrange($k, 0, -1); $this->assertTrue($ret == $v); } // hashes foreach($this->hashes as $k => $v) { $ret = $this->ra->hgetall($k); // get values $this->assertTrue($ret == $v); } // sorted sets foreach($this->zsets as $k => $v) { $ret = $this->ra->zrange($k, 0, -1, TRUE); // get values with scores // create assoc array from local dataset $tmp = array(); for($i = 0; $i < count($v); $i += 2) { $tmp[$v[$i+1]] = $v[$i]; } // compare to RA value $this->assertTrue($ret == $tmp); } } // add a new node. public function testCreateSecondRing() { global $newRing, $oldRing, $serverList; $oldRing = $newRing; // back up the original. $newRing = $serverList; // add a new node to the main ring. } public function testReadUsingFallbackMechanism() { $this->readAllvalues(); // some of the reads will fail and will go to another target node. } public function testRehash() { $this->ra->_rehash(); // this will redistribute the keys } public function testRehashWithCallback() { $total = 0; $this->ra->_rehash(function ($host, $count) use (&$total) { $total += $count; }); $this->assertTrue($total > 0); } public function testReadRedistributedKeys() { $this->readAllvalues(); // we shouldn't have any missed reads now. } } // Test auto-migration of keys class Redis_Auto_Rehashing_Test extends TestSuite { public $ra = NULL; // data private $strings; public function setUp() { // initialize strings. $n = REDIS_ARRAY_DATA_SIZE; $this->strings = array(); for($i = 0; $i < $n; $i++) { $this->strings['key-'.$i] = 'val-'.$i; } global $newRing, $oldRing, $useIndex; // create array $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'autorehash' => TRUE)); } public function testDistribute() { // strings foreach($this->strings as $k => $v) { $this->ra->set($k, $v); } } private function readAllvalues() { foreach($this->strings as $k => $v) { $this->assertTrue($this->ra->get($k) === $v); } } public function testReadAll() { $this->readAllvalues(); } // add a new node. public function testCreateSecondRing() { global $newRing, $oldRing, $serverList; $oldRing = $newRing; // back up the original. $newRing = $serverList; // add a new node to the main ring. } // Read and migrate keys on fallback, causing the whole ring to be rehashed. public function testReadAndMigrateAll() { $this->readAllvalues(); } // Read and migrate keys on fallback, causing the whole ring to be rehashed. public function testAllKeysHaveBeenMigrated() { foreach($this->strings as $k => $v) { // get the target for each key $target = $this->ra->_target($k); // connect to the target host list($host,$port) = split(':', $target); $r = new Redis; $r->pconnect($host, $port); $this->assertTrue($v === $r->get($k)); // check that the key has actually been migrated to the new node. } } } // Test node-specific multi/exec class Redis_Multi_Exec_Test extends TestSuite { public $ra = NULL; public function setUp() { global $newRing, $oldRing, $useIndex; // create array $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex)); } public function testInit() { $this->ra->set('{groups}:managers', 2); $this->ra->set('{groups}:executives', 3); $this->ra->set('1_{employee:joe}_name', 'joe'); $this->ra->set('1_{employee:joe}_group', 2); $this->ra->set('1_{employee:joe}_salary', 2000); } public function testKeyDistribution() { // check that all of joe's keys are on the same instance $lastNode = NULL; foreach(array('name', 'group', 'salary') as $field) { $node = $this->ra->_target('1_{employee:joe}_'.$field); if($lastNode) { $this->assertTrue($node === $lastNode); } $lastNode = $node; } } public function testMultiExec() { // Joe gets a promotion $newGroup = $this->ra->get('{groups}:executives'); $newSalary = 4000; // change both in a transaction. $host = $this->ra->_target('{employee:joe}'); // transactions are per-node, so we need a reference to it. $tr = $this->ra->multi($host) ->set('1_{employee:joe}_group', $newGroup) ->set('1_{employee:joe}_salary', $newSalary) ->exec(); // check that the group and salary have been changed $this->assertTrue($this->ra->get('1_{employee:joe}_group') === $newGroup); $this->assertTrue($this->ra->get('1_{employee:joe}_salary') == $newSalary); } public function testMultiExecMSet() { global $newGroup, $newSalary; $newGroup = 1; $newSalary = 10000; // test MSET, making Joe a top-level executive $out = $this->ra->multi($this->ra->_target('{employee:joe}')) ->mset(array('1_{employee:joe}_group' => $newGroup, '1_{employee:joe}_salary' => $newSalary)) ->exec(); $this->assertTrue($out[0] === TRUE); } public function testMultiExecMGet() { global $newGroup, $newSalary; // test MGET $out = $this->ra->multi($this->ra->_target('{employee:joe}')) ->mget(array('1_{employee:joe}_group', '1_{employee:joe}_salary')) ->exec(); $this->assertTrue($out[0][0] == $newGroup); $this->assertTrue($out[0][1] == $newSalary); } public function testMultiExecDel() { // test DEL $out = $this->ra->multi($this->ra->_target('{employee:joe}')) ->del('1_{employee:joe}_group', '1_{employee:joe}_salary') ->exec(); $this->assertTrue($out[0] === 2); $this->assertTrue($this->ra->exists('1_{employee:joe}_group') === FALSE); $this->assertTrue($this->ra->exists('1_{employee:joe}_salary') === FALSE); } public function testDiscard() { /* phpredis issue #87 */ $key = 'test_err'; $this->assertTrue($this->ra->set($key, 'test')); $this->assertTrue('test' === $this->ra->get($key)); $this->ra->watch($key); // After watch, same $this->assertTrue('test' === $this->ra->get($key)); // change in a multi/exec block. $ret = $this->ra->multi($this->ra->_target($key))->set($key, 'test1')->exec(); $this->assertTrue($ret === array(true)); // Get after exec, 'test1': $this->assertTrue($this->ra->get($key) === 'test1'); $this->ra->watch($key); // After second watch, still test1. $this->assertTrue($this->ra->get($key) === 'test1'); $ret = $this->ra->multi($this->ra->_target($key))->set($key, 'test2')->discard(); // Ret after discard: NULL"; $this->assertTrue($ret === NULL); // Get after discard, unchanged: $this->assertTrue($this->ra->get($key) === 'test1'); } } // Test custom distribution function class Redis_Distributor_Test extends TestSuite { public $ra = NULL; public function setUp() { global $newRing, $oldRing, $useIndex; // create array $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'distributor' => array($this, 'distribute'))); } public function testInit() { $this->ra->set('{uk}test', 'joe'); $this->ra->set('{us}test', 'bob'); } public function distribute($key) { $matches = array(); if (preg_match('/{([^}]+)}.*/', $key, $matches) == 1) { $countries = array('uk' => 0, 'us' => 1); if (array_key_exists($matches[1], $countries)) { return $countries[$matches[1]]; } } return 2; // default server } public function testDistribution() { $ukServer = $this->ra->_target('{uk}test'); $usServer = $this->ra->_target('{us}test'); $deServer = $this->ra->_target('{de}test'); $defaultServer = $this->ra->_target('unknown'); $nodes = $this->ra->_hosts(); $this->assertTrue($ukServer === $nodes[0]); $this->assertTrue($usServer === $nodes[1]); $this->assertTrue($deServer === $nodes[2]); $this->assertTrue($defaultServer === $nodes[2]); } } function run_tests($className) { // reset rings global $newRing, $oldRing, $serverList; $newRing = array('localhost:6379', 'localhost:6380', 'localhost:6381'); $oldRing = array(); $serverList = array('localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382'); // run TestSuite::run($className); } define('REDIS_ARRAY_DATA_SIZE', 1000); global $useIndex; foreach(array(true, false) as $useIndex) { echo "\n".($useIndex?"WITH":"WITHOUT"). " per-node index:\n"; run_tests('Redis_Array_Test'); run_tests('Redis_Rehashing_Test'); run_tests('Redis_Auto_Rehashing_Test'); run_tests('Redis_Multi_Exec_Test'); run_tests('Redis_Distributor_Test'); } ?> <?php echo "Memory usage should remain stable after the first iteration:\n"; function ra(){ return new RedisArray(array('localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382')); } function data() { srand(1); $data = array(); for($i = 0; $i < 10; $i++) { $data['key-'.$i] = rand(); } return $data; } $data = data(); $last = memory_get_usage(); for($i = 0; $i < 10; $i++) { $ra = ra(); echo "$i) " . (memory_get_usage() - $last) . " bytes\n"; $ra->mset($data); foreach($data as $k => $v) { if($v != $ra->get($k)) { echo "Expected $v\n"; die("FAIL"); } } $ra = ra(); $data = data(); if(array_values($data) != $ra->mget(array_keys($data))) { die("FAIL"); } } ?> #!/bin/bash PORTS="6379 6380 6381 6382" REDIS=redis-server function start_node() { P=$1 echo "starting node on port $P"; CONFIG_FILE=`tempfile` cat > $CONFIG_FILE << CONFIG port $P CONFIG $REDIS $CONFIG_FILE > /dev/null 2>/dev/null & sleep 1 rm -f $CONFIG_FILE } function stop_node() { P=$1 PID=$2 redis-cli -h localhost -p $P shutdown kill -9 $PID 2>/dev/null } function stop() { for P in $PORTS; do PID=`lsof -i :$P | tail -1 | cut -f 2 -d " "` if [ "$PID" != "" ]; then stop_node $P $PID fi done } function start() { for P in $PORTS; do start_node $P done } case "$1" in start) start ;; stop) stop ;; restart) stop start ;; *) echo "Usage: $0 [start|stop|restart]" ;; esac <?php // phpunit is such a pain to install, we're going with pure-PHP here. class TestSuite { public static $errors = array(); public static $warnings = array(); protected function assertFalse($bool) { $this->assertTrue(!$bool); } protected function assertTrue($bool) { if($bool) return; $bt = debug_backtrace(false); self::$errors []= sprintf("Assertion failed: %s:%d (%s)\n", $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]); } protected function assertLess($a, $b) { if($a < $b) return; $bt = debug_backtrace(false); self::$errors[] = sprintf("Assertion failed (%s >= %s): %s: %d (%s\n", print_r($a, true), print_r($b, true), $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]); } protected function assertEquals($a, $b) { if($a === $b) return; $bt = debug_backtrace(false); self::$errors []= sprintf("Assertion failed (%s !== %s): %s:%d (%s)\n", print_r($a, true), print_r($b, true), $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]); } protected function markTestSkipped($msg='') { $bt = debug_backtrace(false); self::$warnings []= sprintf("Skipped test: %s:%d (%s) %s\n", $bt[0]["file"], $bt[0]["line"], $bt[1]["function"], $msg); throw new Exception($msg); } public static function run($className, $str_limit) { $rc = new ReflectionClass($className); $methods = $rc->GetMethods(ReflectionMethod::IS_PUBLIC); if ($str_limit) { echo "Limiting to tests with the substring: '$str_limit'\n"; } foreach($methods as $m) { $name = $m->name; if(substr($name, 0, 4) !== 'test') continue; /* If TestRedis.php was envoked with an argument, do a simple * match against the routine. Useful to limit to one test */ if ($str_limit && strpos(strtolower($name),strtolower($str_limit))===false) continue; $count = count($className::$errors); $rt = new $className; try { $rt->setUp(); $rt->$name(); echo ($count === count($className::$errors)) ? "." : "F"; } catch (Exception $e) { if ($e instanceof RedisException) { $className::$errors[] = "Uncaught exception '".$e->getMessage()."' ($name)\n"; echo 'F'; } else { echo 'S'; } } } echo "\n"; echo implode('', $className::$warnings); if(empty($className::$errors)) { echo "All tests passed.\n"; return 0; } echo implode('', $className::$errors); return 1; } } ?> <?php require_once(dirname($_SERVER['PHP_SELF'])."/test.php"); echo "Note: these tests might take up to a minute. Don't worry :-)\n"; class Redis_Test extends TestSuite { const HOST = '127.0.0.1'; const PORT = 6379; const AUTH = NULL; //replace with a string to use Redis authentication /** * @var Redis */ public $redis; public function setUp() { $this->redis = $this->newInstance(); $info = $this->redis->info(); $this->version = (isset($info['redis_version'])?$info['redis_version']:'0.0.0'); } private function newInstance() { $r = new Redis(); $r->connect(self::HOST, self::PORT); if(self::AUTH) { $this->assertTrue($r->auth(self::AUTH)); } return $r; } public function tearDown() { if($this->redis) { $this->redis->close(); } // unset($this->redis); } public function reset() { $this->setUp(); $this->tearDown(); } public function testMinimumVersion() { // Minimum server version required for tests $this->assertTrue(version_compare($this->version, "2.4.0", "ge")); } public function testPing() { $this->assertEquals('+PONG', $this->redis->ping()); $count = 1000; while($count --) { $this->assertEquals('+PONG', $this->redis->ping()); } } public function testPipelinePublish() { $ret = $this->redis->pipeline() ->publish('chan', 'msg') ->exec(); $this->assertTrue(is_array($ret) && count($ret) === 1 && $ret[0] >= 0); } // Run some simple tests against the PUBSUB command. This is problematic, as we // can't be sure what's going on in the instance, but we can do some things. public function testPubSub() { // Only available since 2.8.0 if(version_compare($this->version, "2.8.0", "lt")) { $this->markTestSkipped(); return; } // PUBSUB CHANNELS ... $result = $this->redis->pubsub("channels", "*"); $this->assertTrue(is_array($result)); $result = $this->redis->pubsub("channels"); $this->assertTrue(is_array($result)); // PUBSUB NUMSUB $c1 = uniqid() . '-' . rand(1,100); $c2 = uniqid() . '-' . rand(1,100); $result = $this->redis->pubsub("numsub", Array($c1, $c2)); // Should get an array back, with two elements $this->assertTrue(is_array($result)); $this->assertEquals(count($result), 2); // Make sure the elements are correct, and have zero counts foreach(Array($c1,$c2) as $channel) { $this->assertTrue(isset($result[$channel])); $this->assertEquals($result[$channel], 0); } // PUBSUB NUMPAT $result = $this->redis->pubsub("numpat"); $this->assertTrue(is_int($result)); // Invalid calls $this->assertFalse($this->redis->pubsub("notacommand")); $this->assertFalse($this->redis->pubsub("numsub", "not-an-array")); } public function testBitsets() { $this->redis->delete('key'); $this->assertTrue(0 === $this->redis->getBit('key', 0)); $this->assertTrue(FALSE === $this->redis->getBit('key', -1)); $this->assertTrue(0 === $this->redis->getBit('key', 100000)); $this->redis->set('key', "\xff"); for($i = 0; $i < 8; $i++) { $this->assertTrue(1 === $this->redis->getBit('key', $i)); } $this->assertTrue(0 === $this->redis->getBit('key', 8)); // negative offset doesn't work $this->assertTrue(FALSE === $this->redis->setBit('key', -1, 0)); $this->assertTrue(1 === $this->redis->getBit('key', 0)); // change bit 0 $this->assertTrue(1 === $this->redis->setBit('key', 0, 0)); $this->assertTrue(0 === $this->redis->setBit('key', 0, 0)); $this->assertTrue(0 === $this->redis->getBit('key', 0)); $this->assertTrue("\x7f" === $this->redis->get('key')); // change bit 1 $this->assertTrue(1 === $this->redis->setBit('key', 1, 0)); $this->assertTrue(0 === $this->redis->setBit('key', 1, 0)); $this->assertTrue(0 === $this->redis->getBit('key', 1)); $this->assertTrue("\x3f" === $this->redis->get('key')); // change bit > 1 $this->assertTrue(1 === $this->redis->setBit('key', 2, 0)); $this->assertTrue(0 === $this->redis->setBit('key', 2, 0)); $this->assertTrue(0 === $this->redis->getBit('key', 2)); $this->assertTrue("\x1f" === $this->redis->get('key')); // values above 1 are changed to 1 but don't overflow on bits to the right. $this->assertTrue(0 === $this->redis->setBit('key', 0, 0xff)); $this->assertTrue("\x9f" === $this->redis->get('key')); // Verify valid offset ranges $this->assertFalse($this->redis->getBit('key', -1)); $this->assertFalse($this->redis->getBit('key', 4294967296)); $this->assertFalse($this->redis->setBit('key', -1, 1)); $this->assertFalse($this->redis->setBit('key', 4294967296, 1)); } public function testBitPos() { if(version_compare($this->version, "2.8.7", "lt")) { $this->MarkTestSkipped(); return; } $this->redis->del('bpkey'); $this->redis->set('bpkey', "\xff\xf0\x00"); $this->assertEquals($this->redis->bitpos('bpkey', 0), 12); $this->redis->set('bpkey', "\x00\xff\xf0"); $this->assertEquals($this->redis->bitpos('bpkey', 1, 0), 8); $this->assertEquals($this->redis->bitpos('bpkey', 1, 1), 8); $this->redis->set('bpkey', "\x00\x00\x00"); $this->assertEquals($this->redis->bitpos('bpkey', 1), -1); } public function test1000() { $s = str_repeat('A', 1000); $this->redis->set('x', $s); $this->assertEquals($s, $this->redis->get('x')); $s = str_repeat('A', 1000000); $this->redis->set('x', $s); $this->assertEquals($s, $this->redis->get('x')); } public function testEcho() { $this->assertEquals($this->redis->echo("hello"), "hello"); $this->assertEquals($this->redis->echo(""), ""); $this->assertEquals($this->redis->echo(" 0123 "), " 0123 "); } public function testErr() { $this->redis->set('x', '-ERR'); $this->assertEquals($this->redis->get('x'), '-ERR'); } public function testSet() { $this->assertEquals(TRUE, $this->redis->set('key', 'nil')); $this->assertEquals('nil', $this->redis->get('key')); $this->assertEquals(TRUE, $this->redis->set('key', 'val')); $this->assertEquals('val', $this->redis->get('key')); $this->assertEquals('val', $this->redis->get('key')); $this->redis->delete('keyNotExist'); $this->assertEquals(FALSE, $this->redis->get('keyNotExist')); $this->redis->set('key2', 'val'); $this->assertEquals('val', $this->redis->get('key2')); $value = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; $this->redis->set('key2', $value); $this->assertEquals($value, $this->redis->get('key2')); $this->assertEquals($value, $this->redis->get('key2')); $this->redis->delete('key'); $this->redis->delete('key2'); $i = 66000; $value2 = 'X'; while($i--) { $value2 .= 'A'; } $value2 .= 'X'; $this->redis->set('key', $value2); $this->assertEquals($value2, $this->redis->get('key')); $this->redis->delete('key'); $this->assertEquals(False, $this->redis->get('key')); $data = gzcompress('42'); $this->assertEquals(True, $this->redis->set('key', $data)); $this->assertEquals('42', gzuncompress($this->redis->get('key'))); $this->redis->delete('key'); $data = gzcompress('value1'); $this->assertEquals(True, $this->redis->set('key', $data)); $this->assertEquals('value1', gzuncompress($this->redis->get('key'))); $this->redis->delete('key'); $this->assertEquals(TRUE, $this->redis->set('key', 0)); $this->assertEquals('0', $this->redis->get('key')); $this->assertEquals(TRUE, $this->redis->set('key', 1)); $this->assertEquals('1', $this->redis->get('key')); $this->assertEquals(TRUE, $this->redis->set('key', 0.1)); $this->assertEquals('0.1', $this->redis->get('key')); $this->assertEquals(TRUE, $this->redis->set('key', '0.1')); $this->assertEquals('0.1', $this->redis->get('key')); $this->assertEquals(TRUE, $this->redis->set('key', TRUE)); $this->assertEquals('1', $this->redis->get('key')); $this->assertEquals(True, $this->redis->set('key', '')); $this->assertEquals('', $this->redis->get('key')); $this->assertEquals(True, $this->redis->set('key', NULL)); $this->assertEquals('', $this->redis->get('key')); $this->assertEquals(True, $this->redis->set('key', gzcompress('42'))); $this->assertEquals('42', gzuncompress($this->redis->get('key'))); } /* Extended SET options for Redis >= 2.6.12 */ public function testExtendedSet() { // Skip the test if we don't have a new enough version of Redis if(version_compare($this->version, '2.6.12', 'lt')) { $this->markTestSkipped(); return; } /* Legacy SETEX redirection */ $this->redis->del('foo'); $this->assertTrue($this->redis->set('foo','bar', 20)); $this->assertEquals($this->redis->get('foo'), 'bar'); $this->assertEquals($this->redis->ttl('foo'), 20); /* Invalid third arguments */ $this->assertFalse($this->redis->set('foo','bar','baz')); $this->assertFalse($this->redis->set('foo','bar',new StdClass())); /* Set if not exist */ $this->redis->del('foo'); $this->assertTrue($this->redis->set('foo','bar',Array('nx'))); $this->assertEquals($this->redis->get('foo'), 'bar'); $this->assertFalse($this->redis->set('foo','bar',Array('nx'))); /* Set if exists */ $this->assertTrue($this->redis->set('foo','bar',Array('xx'))); $this->assertEquals($this->redis->get('foo'), 'bar'); $this->redis->del('foo'); $this->assertFalse($this->redis->set('foo','bar',Array('xx'))); /* Set with a TTL */ $this->assertTrue($this->redis->set('foo','bar',Array('ex'=>100))); $this->assertEquals($this->redis->ttl('foo'), 100); /* Set with a PTTL */ $this->assertTrue($this->redis->set('foo','bar',Array('px'=>100000))); $this->assertTrue(100000 - $this->redis->pttl('foo') < 1000); /* Set if exists, with a TTL */ $this->assertTrue($this->redis->set('foo','bar',Array('xx','ex'=>105))); $this->assertEquals($this->redis->ttl('foo'), 105); $this->assertEquals($this->redis->get('foo'), 'bar'); /* Set if not exists, with a TTL */ $this->redis->del('foo'); $this->assertTrue($this->redis->set('foo','bar', Array('nx', 'ex'=>110))); $this->assertEquals($this->redis->ttl('foo'), 110); $this->assertEquals($this->redis->get('foo'), 'bar'); $this->assertFalse($this->redis->set('foo','bar', Array('nx', 'ex'=>110))); /* Throw some nonsense into the array, and check that the TTL came through */ $this->redis->del('foo'); $this->assertTrue($this->redis->set('foo','barbaz', Array('not-valid','nx','invalid','ex'=>200))); $this->assertEquals($this->redis->ttl('foo'), 200); $this->assertEquals($this->redis->get('foo'), 'barbaz'); /* Pass NULL as the optional arguments which should be ignored */ $this->redis->del('foo'); $this->redis->set('foo','bar', NULL); $this->assertEquals($this->redis->get('foo'), 'bar'); $this->assertTrue($this->redis->ttl('foo')<0); } public function testGetSet() { $this->redis->delete('key'); $this->assertTrue($this->redis->getSet('key', '42') === FALSE); $this->assertTrue($this->redis->getSet('key', '123') === '42'); $this->assertTrue($this->redis->getSet('key', '123') === '123'); } public function testRandomKey() { for($i = 0; $i < 1000; $i++) { $k = $this->redis->randomKey(); $this->assertTrue($this->redis->exists($k)); } } public function testRename() { // strings $this->redis->delete('key0'); $this->redis->set('key0', 'val0'); $this->redis->renameKey('key0', 'key1'); $this->assertTrue($this->redis->get('key0') === FALSE); $this->assertTrue($this->redis->get('key1') === 'val0'); // lists $this->redis->delete('key0'); $this->redis->lPush('key0', 'val0'); $this->redis->lPush('key0', 'val1'); $this->redis->renameKey('key0', 'key1'); $this->assertTrue($this->redis->lGetRange('key0', 0, -1) === array()); $this->assertTrue($this->redis->lGetRange('key1', 0, -1) === array('val1', 'val0')); // variadic $this->redis->delete('key0'); $this->assertTrue(3 === $this->redis->lPush('key0', 'val0', 'val1', 'val2')); $this->assertTrue(array('val2', 'val1', 'val0') === $this->redis->lrange('key0', 0, -1)); $this->redis->delete('key0'); $this->assertTrue(3 === $this->redis->rPush('key0', 'val0', 'val1', 'val2')); $this->assertTrue(array('val0', 'val1', 'val2') === $this->redis->lrange('key0', 0, -1)); } public function testRenameNx() { // strings $this->redis->delete('key0', 'key1'); $this->redis->set('key0', 'val0'); $this->redis->set('key1', 'val1'); $this->assertTrue($this->redis->renameNx('key0', 'key1') === FALSE); $this->assertTrue($this->redis->get('key0') === 'val0'); $this->assertTrue($this->redis->get('key1') === 'val1'); // lists $this->redis->delete('key0'); $this->redis->delete('key1'); $this->redis->lPush('key0', 'val0'); $this->redis->lPush('key0', 'val1'); $this->redis->lPush('key1', 'val1-0'); $this->redis->lPush('key1', 'val1-1'); $this->assertTrue($this->redis->renameNx('key0', 'key1') === FALSE); $this->assertTrue($this->redis->lGetRange('key0', 0, -1) === array('val1', 'val0')); $this->assertTrue($this->redis->lGetRange('key1', 0, -1) === array('val1-1', 'val1-0')); $this->redis->delete('key2'); $this->assertTrue($this->redis->renameNx('key0', 'key2') === TRUE); $this->assertTrue($this->redis->lGetRange('key0', 0, -1) === array()); $this->assertTrue($this->redis->lGetRange('key2', 0, -1) === array('val1', 'val0')); } public function testMultiple() { $this->redis->delete('k1'); $this->redis->delete('k2'); $this->redis->delete('k3'); $this->redis->set('k1', 'v1'); $this->redis->set('k2', 'v2'); $this->redis->set('k3', 'v3'); $this->redis->set(1, 'test'); $this->assertEquals(array('v1'), $this->redis->getMultiple(array('k1'))); $this->assertEquals(array('v1', 'v3', false), $this->redis->getMultiple(array('k1', 'k3', 'NoKey'))); $this->assertEquals(array('v1', 'v2', 'v3'), $this->redis->getMultiple(array('k1', 'k2', 'k3'))); $this->assertEquals(array('v1', 'v2', 'v3'), $this->redis->getMultiple(array('k1', 'k2', 'k3'))); $this->redis->set('k5', '$1111111111'); $this->assertEquals(array(0 => '$1111111111'), $this->redis->getMultiple(array('k5'))); $this->assertEquals(array(0 => 'test'), $this->redis->getMultiple(array(1))); // non-string } public function testMultipleBin() { $this->redis->delete('k1'); $this->redis->delete('k2'); $this->redis->delete('k3'); $this->redis->set('k1', gzcompress('v1')); $this->redis->set('k2', gzcompress('v2')); $this->redis->set('k3', gzcompress('v3')); $this->assertEquals(array(gzcompress('v1'), gzcompress('v2'), gzcompress('v3')), $this->redis->getMultiple(array('k1', 'k2', 'k3'))); $this->assertEquals(array(gzcompress('v1'), gzcompress('v2'), gzcompress('v3')), $this->redis->getMultiple(array('k1', 'k2', 'k3'))); } public function testSetTimeout() { $this->redis->delete('key'); $this->redis->set('key', 'value'); $this->assertEquals('value', $this->redis->get('key')); $this->redis->setTimeout('key', 1); $this->assertEquals('value', $this->redis->get('key')); sleep(2); $this->assertEquals(False, $this->redis->get('key')); } public function testExpireAt() { $this->redis->delete('key'); $this->redis->set('key', 'value'); $now = time(NULL); $this->redis->expireAt('key', $now + 1); $this->assertEquals('value', $this->redis->get('key')); sleep(2); $this->assertEquals(FALSE, $this->redis->get('key')); } public function testSetEx() { $this->redis->delete('key'); $this->assertTrue($this->redis->setex('key', 7, 'val') === TRUE); $this->assertTrue($this->redis->ttl('key') ===7); $this->assertTrue($this->redis->get('key') === 'val'); } public function testSetNX() { $this->redis->set('key', 42); $this->assertTrue($this->redis->setnx('key', 'err') === FALSE); $this->assertTrue($this->redis->get('key') === '42'); $this->redis->delete('key'); $this->assertTrue($this->redis->setnx('key', '42') === TRUE); $this->assertTrue($this->redis->get('key') === '42'); } public function testExpireAtWithLong() { $longExpiryTimeExceedingInt = 3153600000; $this->redis->delete('key'); $this->assertTrue($this->redis->setex('key', $longExpiryTimeExceedingInt, 'val') === TRUE); $this->assertTrue($this->redis->ttl('key') === $longExpiryTimeExceedingInt); } public function testIncr() { $this->redis->set('key', 0); $this->redis->incr('key'); $this->assertEquals(1, (int)$this->redis->get('key')); $this->redis->incr('key'); $this->assertEquals(2, (int)$this->redis->get('key')); $this->redis->incr('key', 3); $this->assertEquals(5, (int)$this->redis->get('key')); $this->redis->incrBy('key', 3); $this->assertEquals(8, (int)$this->redis->get('key')); $this->redis->incrBy('key', 1); $this->assertEquals(9, (int)$this->redis->get('key')); $this->redis->incrBy('key', -1); $this->assertEquals(8, (int)$this->redis->get('key')); $this->redis->delete('key'); $this->redis->set('key', 'abc'); $this->redis->incr('key'); $this->assertTrue("abc" === $this->redis->get('key')); $this->redis->incr('key'); $this->assertTrue("abc" === $this->redis->get('key')); } public function testIncrByFloat() { // incrbyfloat is new in 2.6.0 if (version_compare($this->version, "2.5.0", "lt")) { $this->markTestSkipped(); } $this->redis->delete('key'); $this->redis->set('key', 0); $this->redis->incrbyfloat('key', 1.5); $this->assertEquals('1.5', $this->redis->get('key')); $this->redis->incrbyfloat('key', 2.25); $this->assertEquals('3.75', $this->redis->get('key')); $this->redis->incrbyfloat('key', -2.25); $this->assertEquals('1.5', $this->redis->get('key')); $this->redis->set('key', 'abc'); $this->redis->incrbyfloat('key', 1.5); $this->assertTrue("abc" === $this->redis->get('key')); $this->redis->incrbyfloat('key', -1.5); $this->assertTrue("abc" === $this->redis->get('key')); // Test with prefixing $this->redis->setOption(Redis::OPT_PREFIX, 'someprefix:'); $this->redis->del('key'); $this->redis->incrbyfloat('key',1.8); $this->assertEquals('1.8', $this->redis->get('key')); $this->redis->setOption(Redis::OPT_PREFIX, ''); $this->assertTrue($this->redis->exists('someprefix:key')); $this->redis->del('someprefix:key'); } public function testDecr() { $this->redis->set('key', 5); $this->redis->decr('key'); $this->assertEquals(4, (int)$this->redis->get('key')); $this->redis->decr('key'); $this->assertEquals(3, (int)$this->redis->get('key')); $this->redis->decr('key', 2); $this->assertEquals(1, (int)$this->redis->get('key')); $this->redis->decr('key', 2); $this->assertEquals(-1, (int)$this->redis->get('key')); $this->redis->decrBy('key', 2); $this->assertEquals(-3, (int)$this->redis->get('key')); $this->redis->decrBy('key', 1); $this->assertEquals(-4, (int)$this->redis->get('key')); $this->redis->decr('key', -10); $this->assertEquals(6, (int)$this->redis->get('key')); } public function testExists() { $this->redis->delete('key'); $this->assertFalse($this->redis->exists('key')); $this->redis->set('key', 'val'); $this->assertEquals(True, $this->redis->exists('key')); } public function testGetKeys() { $pattern = 'getKeys-test-'; for($i = 1; $i < 10; $i++) { $this->redis->set($pattern.$i, $i); } $this->redis->delete($pattern.'3'); $keys = $this->redis->getKeys($pattern.'*'); $this->redis->set($pattern.'3', 'something'); $keys2 = $this->redis->getKeys($pattern.'*'); $this->assertEquals((count($keys) + 1), count($keys2)); // empty array when no key matches $this->assertEquals(array(), $this->redis->getKeys(rand().rand().rand().'*')); } public function testDelete() { $key = 'key' . rand(); $this->redis->set($key, 'val'); $this->assertEquals('val', $this->redis->get($key)); $this->assertEquals(1, $this->redis->delete($key)); $this->assertEquals(false, $this->redis->get($key)); // multiple, all existing $this->redis->set('x', 0); $this->redis->set('y', 1); $this->redis->set('z', 2); $this->assertEquals(3, $this->redis->delete('x', 'y', 'z')); $this->assertEquals(false, $this->redis->get('x')); $this->assertEquals(false, $this->redis->get('y')); $this->assertEquals(false, $this->redis->get('z')); // multiple, none existing $this->assertEquals(0, $this->redis->delete('x', 'y', 'z')); $this->assertEquals(false, $this->redis->get('x')); $this->assertEquals(false, $this->redis->get('y')); $this->assertEquals(false, $this->redis->get('z')); // multiple, some existing $this->redis->set('y', 1); $this->assertEquals(1, $this->redis->delete('x', 'y', 'z')); $this->assertEquals(false, $this->redis->get('y')); $this->redis->set('x', 0); $this->redis->set('y', 1); $this->assertEquals(2, $this->redis->delete(array('x', 'y'))); } public function testType() { // 0 => none, (key didn't exist) // 1=> string, // 2 => set, // 3 => list, // 4 => zset, // 5 => hash // string $this->redis->set('key', 'val'); $this->assertEquals(Redis::REDIS_STRING, $this->redis->type('key')); // list $this->redis->lPush('keyList', 'val0'); $this->redis->lPush('keyList', 'val1'); $this->assertEquals(Redis::REDIS_LIST, $this->redis->type('keyList')); // set $this->redis->delete('keySet'); $this->redis->sAdd('keySet', 'val0'); $this->redis->sAdd('keySet', 'val1'); $this->assertEquals(Redis::REDIS_SET, $this->redis->type('keySet')); // sadd with numeric key $this->redis->delete(123); $this->assertTrue(1 === $this->redis->sAdd(123, 'val0')); $this->assertTrue(array('val0') === $this->redis->sMembers(123)); // zset $this->redis->delete('keyZSet'); $this->redis->zAdd('keyZSet', 0, 'val0'); $this->redis->zAdd('keyZSet', 1, 'val1'); $this->assertEquals(Redis::REDIS_ZSET, $this->redis->type('keyZSet')); // hash $this->redis->delete('keyHash'); $this->redis->hSet('keyHash', 'key0', 'val0'); $this->redis->hSet('keyHash', 'key1', 'val1'); $this->assertEquals(Redis::REDIS_HASH, $this->redis->type('keyHash')); //None $this->assertEquals(Redis::REDIS_NOT_FOUND, $this->redis->type('keyNotExists')); } public function testStr() { $this->redis->set('key', 'val1'); $this->assertTrue($this->redis->append('key', 'val2') === 8); $this->assertTrue($this->redis->get('key') === 'val1val2'); $this->assertTrue($this->redis->append('keyNotExist', 'value') === 5); $this->assertTrue($this->redis->get('keyNotExist') === 'value'); $this->redis->set('key', 'This is a string') ; $this->assertTrue($this->redis->getRange('key', 0, 3) === 'This'); $this->assertTrue($this->redis->getRange('key', -6, -1) === 'string'); $this->assertTrue($this->redis->getRange('key', -6, 100000) === 'string'); $this->assertTrue($this->redis->get('key') === 'This is a string'); $this->redis->set('key', 'This is a string') ; $this->assertTrue($this->redis->strlen('key') === 16); $this->redis->set('key', 10) ; $this->assertTrue($this->redis->strlen('key') === 2); $this->redis->set('key', '') ; $this->assertTrue($this->redis->strlen('key') === 0); $this->redis->set('key', '000') ; $this->assertTrue($this->redis->strlen('key') === 3); } // PUSH, POP : LPUSH, LPOP public function testlPop() { // rpush => tail // lpush => head $this->redis->delete('list'); $this->redis->lPush('list', 'val'); $this->redis->lPush('list', 'val2'); $this->redis->rPush('list', 'val3'); // 'list' = [ 'val2', 'val', 'val3'] $this->assertEquals('val2', $this->redis->lPop('list')); $this->assertEquals('val', $this->redis->lPop('list')); $this->assertEquals('val3', $this->redis->lPop('list')); $this->assertEquals(FALSE, $this->redis->lPop('list')); // testing binary data $this->redis->delete('list'); $this->assertEquals(1, $this->redis->lPush('list', gzcompress('val1'))); $this->assertEquals(2, $this->redis->lPush('list', gzcompress('val2'))); $this->assertEquals(3, $this->redis->lPush('list', gzcompress('val3'))); $this->assertEquals('val3', gzuncompress($this->redis->lPop('list'))); $this->assertEquals('val2', gzuncompress($this->redis->lPop('list'))); $this->assertEquals('val1', gzuncompress($this->redis->lPop('list'))); } // PUSH, POP : RPUSH, RPOP public function testrPop() { // rpush => tail // lpush => head $this->redis->delete('list'); $this->redis->rPush('list', 'val'); $this->redis->rPush('list', 'val2'); $this->redis->lPush('list', 'val3'); // 'list' = [ 'val3', 'val', 'val2'] $this->assertEquals('val2', $this->redis->rPop('list')); $this->assertEquals('val', $this->redis->rPop('list')); $this->assertEquals('val3', $this->redis->rPop('list')); $this->assertEquals(FALSE, $this->redis->rPop('list')); // testing binary data $this->redis->delete('list'); $this->assertEquals(1, $this->redis->rPush('list', gzcompress('val1'))); $this->assertEquals(2, $this->redis->rPush('list', gzcompress('val2'))); $this->assertEquals(3, $this->redis->rPush('list', gzcompress('val3'))); $this->assertEquals('val3', gzuncompress($this->redis->rPop('list'))); $this->assertEquals('val2', gzuncompress($this->redis->rPop('list'))); $this->assertEquals('val1', gzuncompress($this->redis->rPop('list'))); } public function testblockingPop() { // non blocking blPop, brPop $this->redis->delete('list'); $this->redis->lPush('list', 'val1'); $this->redis->lPush('list', 'val2'); $this->assertTrue($this->redis->blPop(array('list'), 2) === array('list', 'val2')); $this->assertTrue($this->redis->blPop(array('list'), 2) === array('list', 'val1')); $this->redis->delete('list'); $this->redis->lPush('list', 'val1'); $this->redis->lPush('list', 'val2'); $this->assertTrue($this->redis->brPop(array('list'), 1) === array('list', 'val1')); $this->assertTrue($this->redis->brPop(array('list'), 1) === array('list', 'val2')); // blocking blpop, brpop $this->redis->delete('list'); $this->assertTrue($this->redis->blPop(array('list'), 1) === array()); $this->assertTrue($this->redis->brPop(array('list'), 1) === array()); // TODO: fix this broken test. // $this->redis->delete('list'); // $params = array( // 0 => array("pipe", "r"), // 1 => array("pipe", "w"), // 2 => array("file", "/dev/null", "w") // ); // if(function_exists('proc_open')) { // $env = array('PHPREDIS_key' =>'list', 'PHPREDIS_value' => 'value'); // $process = proc_open('php', $params, $pipes, '/tmp', $env); // // if (is_resource($process)) { // fwrite($pipes[0], '<?php // sleep(2); // $r = new Redis; // $r->connect("'.self::HOST.'", '.self::PORT.'); // if("'.addslashes(self::AUTH).'") { // $r->auth("'.addslashes(self::AUTH).'"); // } // $r->lPush($_ENV["PHPREDIS_key"], $_ENV["PHPREDIS_value"]); // ?' . '>'); // // fclose($pipes[0]); // fclose($pipes[1]); // $re = proc_close($process); // // $this->assertTrue($this->redis->blPop(array('list'), 5) === array("list", "value")); // } // } } public function testlSize() { $this->redis->delete('list'); $this->redis->lPush('list', 'val'); $this->assertEquals(1, $this->redis->lSize('list')); $this->redis->lPush('list', 'val2'); $this->assertEquals(2, $this->redis->lSize('list')); $this->assertEquals('val2', $this->redis->lPop('list')); $this->assertEquals(1, $this->redis->lSize('list')); $this->assertEquals('val', $this->redis->lPop('list')); $this->assertEquals(0, $this->redis->lSize('list')); $this->assertEquals(FALSE, $this->redis->lPop('list')); $this->assertEquals(0, $this->redis->lSize('list')); // empty returns 0 $this->redis->delete('list'); $this->assertEquals(0, $this->redis->lSize('list')); // non-existent returns 0 $this->redis->set('list', 'actually not a list'); $this->assertEquals(FALSE, $this->redis->lSize('list'));// not a list returns FALSE } //lInsert, lPopx, rPopx public function testlPopx() { //test lPushx/rPushx $this->redis->delete('keyNotExists'); $this->assertTrue($this->redis->lPushx('keyNotExists', 'value') === 0); $this->assertTrue($this->redis->rPushx('keyNotExists', 'value') === 0); $this->redis->delete('key'); $this->redis->lPush('key', 'val0'); $this->assertTrue($this->redis->lPushx('key', 'val1') === 2); $this->assertTrue($this->redis->rPushx('key', 'val2') === 3); $this->assertTrue($this->redis->lGetRange('key', 0, -1) === array('val1', 'val0', 'val2')); //test linsert $this->redis->delete('key'); $this->redis->lPush('key', 'val0'); $this->assertTrue($this->redis->lInsert('keyNotExists', Redis::AFTER, 'val1', 'val2') === 0); $this->assertTrue($this->redis->lInsert('key', Redis::BEFORE, 'valX', 'val2') === -1); $this->assertTrue($this->redis->lInsert('key', Redis::AFTER, 'val0', 'val1') === 2); $this->assertTrue($this->redis->lInsert('key', Redis::BEFORE, 'val0', 'val2') === 3); $this->assertTrue($this->redis->lGetRange('key', 0, -1) === array('val2', 'val0', 'val1')); } // ltrim, lsize, lpop public function testlistTrim() { $this->redis->delete('list'); $this->redis->lPush('list', 'val'); $this->redis->lPush('list', 'val2'); $this->redis->lPush('list', 'val3'); $this->redis->lPush('list', 'val4'); $this->assertEquals(TRUE, $this->redis->listTrim('list', 0, 2)); $this->assertEquals(3, $this->redis->lSize('list')); $this->redis->listTrim('list', 0, 0); $this->assertEquals(1, $this->redis->lSize('list')); $this->assertEquals('val4', $this->redis->lPop('list')); $this->assertEquals(TRUE, $this->redis->listTrim('list', 10, 10000)); $this->assertEquals(TRUE, $this->redis->listTrim('list', 10000, 10)); // test invalid type $this->redis->set('list', 'not a list...'); $this->assertEquals(FALSE, $this->redis->listTrim('list', 0, 2)); } public function setupSort() { // people with name, age, salary $this->redis->set('person:name_1', 'Alice'); $this->redis->set('person:age_1', 27); $this->redis->set('person:salary_1', 2500); $this->redis->set('person:name_2', 'Bob'); $this->redis->set('person:age_2', 34); $this->redis->set('person:salary_2', 2000); $this->redis->set('person:name_3', 'Carol'); $this->redis->set('person:age_3', 25); $this->redis->set('person:salary_3', 2800); $this->redis->set('person:name_4', 'Dave'); $this->redis->set('person:age_4', 41); $this->redis->set('person:salary_4', 3100); // set-up $this->redis->delete('person:id'); foreach(array(1,2,3,4) as $id) { $this->redis->lPush('person:id', $id); } } public function testSortPrefix() { // Make sure that sorting works with a prefix $this->redis->setOption(Redis::OPT_PREFIX, 'some-prefix:'); $this->redis->del('some-item'); $this->redis->sadd('some-item', 1); $this->redis->sadd('some-item', 2); $this->redis->sadd('some-item', 3); $this->assertEquals(array('1','2','3'), $this->redis->sortAsc('some-item')); $this->assertEquals(array('3','2','1'), $this->redis->sortDesc('some-item')); $this->assertEquals(array('1','2','3'), $this->redis->sort('some-item')); // Kill our set/prefix $this->redis->del('some-item'); $this->redis->setOption(Redis::OPT_PREFIX, ''); } public function testSortAsc() { $this->setupSort(); $this->assertTrue(FALSE === $this->redis->sortAsc(NULL)); // sort by age and get IDs $byAgeAsc = array('3','1','2','4'); $this->assertEquals($byAgeAsc, $this->redis->sortAsc('person:id', 'person:age_*')); $this->assertEquals($byAgeAsc, $this->redis->sort('person:id', array('by' => 'person:age_*', 'sort' => 'asc'))); $this->assertEquals(array('1', '2', '3', '4'), $this->redis->sortAsc('person:id', NULL)); // check that NULL works. $this->assertEquals(array('1', '2', '3', '4'), $this->redis->sortAsc('person:id', NULL, NULL)); // for all fields. $this->assertEquals(array('1', '2', '3', '4'), $this->redis->sort('person:id', array('sort' => 'asc'))); // sort by age and get names $byAgeAsc = array('Carol','Alice','Bob','Dave'); $this->assertEquals($byAgeAsc, $this->redis->sortAsc('person:id', 'person:age_*', 'person:name_*')); $this->assertEquals($byAgeAsc, $this->redis->sort('person:id', array('by' => 'person:age_*', 'get' => 'person:name_*', 'sort' => 'asc'))); $this->assertEquals(array_slice($byAgeAsc, 0, 2), $this->redis->sortAsc('person:id', 'person:age_*', 'person:name_*', 0, 2)); $this->assertEquals(array_slice($byAgeAsc, 0, 2), $this->redis->sort('person:id', array('by' => 'person:age_*', 'get' => 'person:name_*', 'limit' => array(0, 2), 'sort' => 'asc'))); $this->assertEquals(array_slice($byAgeAsc, 1, 2), $this->redis->sortAsc('person:id', 'person:age_*', 'person:name_*', 1, 2)); $this->assertEquals(array_slice($byAgeAsc, 1, 2), $this->redis->sort('person:id', array('by' => 'person:age_*', 'get' => 'person:name_*', 'limit' => array(1, 2), 'sort' => 'asc'))); $this->assertEquals(array_slice($byAgeAsc, 0, 3), $this->redis->sortAsc('person:id', 'person:age_*', 'person:name_*', NULL, 3)); // NULL is transformed to 0 if there is something after it. $this->assertEquals($byAgeAsc, $this->redis->sortAsc('person:id', 'person:age_*', 'person:name_*', 0, 4)); $this->assertEquals($byAgeAsc, $this->redis->sort('person:id', array('by' => 'person:age_*', 'get' => 'person:name_*', 'limit' => array(0, 4)))); $this->assertEquals($byAgeAsc, $this->redis->sort('person:id', array('by' => 'person:age_*', 'get' => 'person:name_*', 'limit' => array(0, "4")))); // with strings $this->assertEquals($byAgeAsc, $this->redis->sort('person:id', array('by' => 'person:age_*', 'get' => 'person:name_*', 'limit' => array("0", 4)))); $this->assertEquals(array(), $this->redis->sortAsc('person:id', 'person:age_*', 'person:name_*', NULL, NULL)); // NULL, NULL is the same as (0,0). That returns no element. // sort by salary and get ages $agesBySalaryAsc = array('34', '27', '25', '41'); $this->assertEquals($agesBySalaryAsc, $this->redis->sortAsc('person:id', 'person:salary_*', 'person:age_*')); $this->assertEquals($agesBySalaryAsc, $this->redis->sort('person:id', array('by' => 'person:salary_*', 'get' => 'person:age_*', 'sort' => 'asc'))); $agesAndSalaries = $this->redis->sort('person:id', array('by' => 'person:salary_*', 'get' => array('person:age_*', 'person:salary_*'), 'sort' => 'asc')); $this->assertEquals(array('34', '2000', '27', '2500', '25', '2800', '41', '3100'), $agesAndSalaries); // sort non-alpha doesn't change all-string lists // list → [ghi, def, abc] $list = array('abc', 'def', 'ghi'); $this->redis->delete('list'); foreach($list as $i) { $this->redis->lPush('list', $i); } // SORT list → [ghi, def, abc] if (version_compare($this->version, "2.5.0", "lt")) { $this->assertEquals(array_reverse($list), $this->redis->sortAsc('list')); $this->assertEquals(array_reverse($list), $this->redis->sort('list', array('sort' => 'asc'))); } else { // TODO rewrite, from 2.6.0 release notes: // SORT now will refuse to sort in numerical mode elements that can't be parsed // as numbers } // SORT list ALPHA → [abc, def, ghi] $this->assertEquals($list, $this->redis->sortAscAlpha('list')); $this->assertEquals($list, $this->redis->sort('list', array('sort' => 'asc', 'alpha' => TRUE))); } public function testSortDesc() { $this->setupSort(); // sort by age and get IDs $byAgeDesc = array('4','2','1','3'); $this->assertEquals($byAgeDesc, $this->redis->sortDesc('person:id', 'person:age_*')); // sort by age and get names $byAgeDesc = array('Dave', 'Bob', 'Alice', 'Carol'); $this->assertEquals($byAgeDesc, $this->redis->sortDesc('person:id', 'person:age_*', 'person:name_*')); $this->assertEquals(array_slice($byAgeDesc, 0, 2), $this->redis->sortDesc('person:id', 'person:age_*', 'person:name_*', 0, 2)); $this->assertEquals(array_slice($byAgeDesc, 1, 2), $this->redis->sortDesc('person:id', 'person:age_*', 'person:name_*', 1, 2)); // sort by salary and get ages $agesBySalaryDesc = array('41', '25', '27', '34'); $this->assertEquals($agesBySalaryDesc, $this->redis->sortDesc('person:id', 'person:salary_*', 'person:age_*')); // sort non-alpha doesn't change all-string lists $list = array('def', 'abc', 'ghi'); $this->redis->delete('list'); foreach($list as $i) { $this->redis->lPush('list', $i); } // SORT list → [ghi, abc, def] if (version_compare($this->version, "2.5.0", "lt")) { $this->assertEquals(array_reverse($list), $this->redis->sortDesc('list')); } else { // TODO rewrite, from 2.6.0 release notes: // SORT now will refuse to sort in numerical mode elements that can't be parsed // as numbers } // SORT list ALPHA → [abc, def, ghi] $this->assertEquals(array('ghi', 'def', 'abc'), $this->redis->sortDescAlpha('list')); } // LINDEX public function testlGet() { $this->redis->delete('list'); $this->redis->lPush('list', 'val'); $this->redis->lPush('list', 'val2'); $this->redis->lPush('list', 'val3'); $this->assertEquals('val3', $this->redis->lGet('list', 0)); $this->assertEquals('val2', $this->redis->lGet('list', 1)); $this->assertEquals('val', $this->redis->lGet('list', 2)); $this->assertEquals('val', $this->redis->lGet('list', -1)); $this->assertEquals('val2', $this->redis->lGet('list', -2)); $this->assertEquals('val3', $this->redis->lGet('list', -3)); $this->assertEquals(FALSE, $this->redis->lGet('list', -4)); $this->redis->rPush('list', 'val4'); $this->assertEquals('val4', $this->redis->lGet('list', 3)); $this->assertEquals('val4', $this->redis->lGet('list', -1)); } // lRem testing public function testlRemove() { $this->redis->delete('list'); $this->redis->lPush('list', 'a'); $this->redis->lPush('list', 'b'); $this->redis->lPush('list', 'c'); $this->redis->lPush('list', 'c'); $this->redis->lPush('list', 'b'); $this->redis->lPush('list', 'c'); // ['c', 'b', 'c', 'c', 'b', 'a'] $return = $this->redis->lRemove('list', 'b', 2); // ['c', 'c', 'c', 'a'] $this->assertEquals(2, $return); $this->assertEquals('c', $this->redis->lGET('list', 0)); $this->assertEquals('c', $this->redis->lGET('list', 1)); $this->assertEquals('c', $this->redis->lGET('list', 2)); $this->assertEquals('a', $this->redis->lGET('list', 3)); $this->redis->delete('list'); $this->redis->lPush('list', 'a'); $this->redis->lPush('list', 'b'); $this->redis->lPush('list', 'c'); $this->redis->lPush('list', 'c'); $this->redis->lPush('list', 'b'); $this->redis->lPush('list', 'c'); // ['c', 'b', 'c', 'c', 'b', 'a'] $this->redis->lRemove('list', 'c', -2); // ['c', 'b', 'b', 'a'] $this->assertEquals(2, $return); $this->assertEquals('c', $this->redis->lGET('list', 0)); $this->assertEquals('b', $this->redis->lGET('list', 1)); $this->assertEquals('b', $this->redis->lGET('list', 2)); $this->assertEquals('a', $this->redis->lGET('list', 3)); // remove each element $this->assertEquals(1, $this->redis->lRemove('list', 'a', 0)); $this->assertEquals(0, $this->redis->lRemove('list', 'x', 0)); $this->assertEquals(2, $this->redis->lRemove('list', 'b', 0)); $this->assertEquals(1, $this->redis->lRemove('list', 'c', 0)); $this->assertEquals(FALSE, $this->redis->get('list')); $this->redis->set('list', 'actually not a list'); $this->assertEquals(FALSE, $this->redis->lRemove('list', 'x')); } public function testsAdd() { $this->redis->delete('set'); $this->assertEquals(1, $this->redis->sAdd('set', 'val')); $this->assertEquals(0, $this->redis->sAdd('set', 'val')); $this->assertTrue($this->redis->sContains('set', 'val')); $this->assertFalse($this->redis->sContains('set', 'val2')); $this->assertEquals(1, $this->redis->sAdd('set', 'val2')); $this->assertTrue($this->redis->sContains('set', 'val2')); } public function testsSize() { $this->redis->delete('set'); $this->assertEquals(1, $this->redis->sAdd('set', 'val')); $this->assertEquals(1, $this->redis->sSize('set')); $this->assertEquals(1, $this->redis->sAdd('set', 'val2')); $this->assertEquals(2, $this->redis->sSize('set')); } public function testsRemove() { $this->redis->delete('set'); $this->redis->sAdd('set', 'val'); $this->redis->sAdd('set', 'val2'); $this->redis->sRemove('set', 'val'); $this->assertEquals(1, $this->redis->sSize('set')); $this->redis->sRemove('set', 'val2'); $this->assertEquals(0, $this->redis->sSize('set')); } public function testsMove() { $this->redis->delete('set0'); $this->redis->delete('set1'); $this->redis->sAdd('set0', 'val'); $this->redis->sAdd('set0', 'val2'); $this->assertTrue($this->redis->sMove('set0', 'set1', 'val')); $this->assertFalse($this->redis->sMove('set0', 'set1', 'val')); $this->assertFalse($this->redis->sMove('set0', 'set1', 'val-what')); $this->assertEquals(1, $this->redis->sSize('set0')); $this->assertEquals(1, $this->redis->sSize('set1')); $this->assertEquals(array('val2'), $this->redis->sGetMembers('set0')); $this->assertEquals(array('val'), $this->redis->sGetMembers('set1')); } public function testsPop() { $this->redis->delete('set0'); $this->assertTrue($this->redis->sPop('set0') === FALSE); $this->redis->sAdd('set0', 'val'); $this->redis->sAdd('set0', 'val2'); $v0 = $this->redis->sPop('set0'); $this->assertTrue(1 === $this->redis->sSize('set0')); $this->assertTrue($v0 === 'val' || $v0 === 'val2'); $v1 = $this->redis->sPop('set0'); $this->assertTrue(0 === $this->redis->sSize('set0')); $this->assertTrue(($v0 === 'val' && $v1 === 'val2') || ($v1 === 'val' && $v0 === 'val2')); $this->assertTrue($this->redis->sPop('set0') === FALSE); } public function testsRandMember() { $this->redis->delete('set0'); $this->assertTrue($this->redis->sRandMember('set0') === FALSE); $this->redis->sAdd('set0', 'val'); $this->redis->sAdd('set0', 'val2'); $got = array(); while(true) { $v = $this->redis->sRandMember('set0'); $this->assertTrue(2 === $this->redis->sSize('set0')); // no change. $this->assertTrue($v === 'val' || $v === 'val2'); $got[$v] = $v; if(count($got) == 2) { break; } } // // With and without count, while serializing // $this->redis->delete('set0'); $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); for($i=0;$i<5;$i++) { $member = "member:$i"; $this->redis->sAdd('set0', $member); $mems[] = $member; } $member = $this->redis->srandmember('set0'); $this->assertTrue(in_array($member, $mems)); $rmembers = $this->redis->srandmember('set0', $i); foreach($rmembers as $reply_mem) { $this->assertTrue(in_array($reply_mem, $mems)); } $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); } public function testSRandMemberWithCount() { // Make sure the set is nuked $this->redis->delete('set0'); // Run with a count (positive and negative) on an empty set $ret_pos = $this->redis->sRandMember('set0', 10); $ret_neg = $this->redis->sRandMember('set0', -10); // Should both be empty arrays $this->assertTrue(is_array($ret_pos) && empty($ret_pos)); $this->assertTrue(is_array($ret_neg) && empty($ret_neg)); // Add a few items to the set for($i=0;$i<100;$i++) { $this->redis->sadd('set0', "member$i"); } // Get less than the size of the list $ret_slice = $this->redis->srandmember('set0', 20); // Should be an array with 20 items $this->assertTrue(is_array($ret_slice) && count($ret_slice) == 20); // Ask for more items than are in the list (but with a positive count) $ret_slice = $this->redis->srandmember('set0', 200); // Should be an array, should be however big the set is, exactly $this->assertTrue(is_array($ret_slice) && count($ret_slice) == $i); // Now ask for too many items but negative $ret_slice = $this->redis->srandmember('set0', -200); // Should be an array, should have exactly the # of items we asked for (will be dups) $this->assertTrue(is_array($ret_slice) && count($ret_slice) == 200); // // Test in a pipeline // $pipe = $this->redis->pipeline(); $pipe->srandmember('set0', 20); $pipe->srandmember('set0', 200); $pipe->srandmember('set0', -200); $ret = $this->redis->exec(); $this->assertTrue(is_array($ret[0]) && count($ret[0]) == 20); $this->assertTrue(is_array($ret[1]) && count($ret[1]) == $i); $this->assertTrue(is_array($ret[2]) && count($ret[2]) == 200); // Kill the set $this->redis->del('set0'); } public function testsContains() { $this->redis->delete('set'); $this->redis->sAdd('set', 'val'); $this->assertTrue($this->redis->sContains('set', 'val')); $this->assertFalse($this->redis->sContains('set', 'val2')); } public function testsGetMembers() { $this->redis->delete('set'); $this->redis->sAdd('set', 'val'); $this->redis->sAdd('set', 'val2'); $this->redis->sAdd('set', 'val3'); $array = array('val', 'val2', 'val3'); $sGetMembers = $this->redis->sGetMembers('set'); sort($sGetMembers); $this->assertEquals($array, $sGetMembers); $sMembers = $this->redis->sMembers('set'); sort($sMembers); $this->assertEquals($array, $sMembers); // test alias } public function testlSet() { $this->redis->delete('list'); $this->redis->lPush('list', 'val'); $this->redis->lPush('list', 'val2'); $this->redis->lPush('list', 'val3'); $this->assertEquals($this->redis->lGet('list', 0), 'val3'); $this->assertEquals($this->redis->lGet('list', 1), 'val2'); $this->assertEquals($this->redis->lGet('list', 2), 'val'); $this->assertEquals(TRUE, $this->redis->lSet('list', 1, 'valx')); $this->assertEquals($this->redis->lGet('list', 0), 'val3'); $this->assertEquals($this->redis->lGet('list', 1), 'valx'); $this->assertEquals($this->redis->lGet('list', 2), 'val'); } public function testsInter() { $this->redis->delete('x'); // set of odd numbers $this->redis->delete('y'); // set of prime numbers $this->redis->delete('z'); // set of squares $this->redis->delete('t'); // set of numbers of the form n^2 - 1 $x = array(1,3,5,7,9,11,13,15,17,19,21,23,25); foreach($x as $i) { $this->redis->sAdd('x', $i); } $y = array(1,2,3,5,7,11,13,17,19,23); foreach($y as $i) { $this->redis->sAdd('y', $i); } $z = array(1,4,9,16,25); foreach($z as $i) { $this->redis->sAdd('z', $i); } $t = array(2,5,10,17,26); foreach($t as $i) { $this->redis->sAdd('t', $i); } $xy = $this->redis->sInter('x', 'y'); // odd prime numbers foreach($xy as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_intersect($x, $y))); } $xy = $this->redis->sInter(array('x', 'y')); // odd prime numbers, as array. foreach($xy as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_intersect($x, $y))); } $yz = $this->redis->sInter('y', 'z'); // set of odd squares foreach($yz as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_intersect($y, $z))); } $yz = $this->redis->sInter(array('y', 'z')); // set of odd squares, as array foreach($yz as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_intersect($y, $z))); } $zt = $this->redis->sInter('z', 't'); // prime squares $this->assertTrue($zt === array()); $zt = $this->redis->sInter(array('z', 't')); // prime squares, as array $this->assertTrue($zt === array()); $xyz = $this->redis->sInter('x', 'y', 'z');// odd prime squares $this->assertTrue($xyz === array('1')); $xyz = $this->redis->sInter(array('x', 'y', 'z'));// odd prime squares, with an array as a parameter $this->assertTrue($xyz === array('1')); $nil = $this->redis->sInter(); $this->assertTrue($nil === FALSE); $nil = $this->redis->sInter(array()); $this->assertTrue($nil === FALSE); } public function testsInterStore() { $this->redis->delete('x'); // set of odd numbers $this->redis->delete('y'); // set of prime numbers $this->redis->delete('z'); // set of squares $this->redis->delete('t'); // set of numbers of the form n^2 - 1 $x = array(1,3,5,7,9,11,13,15,17,19,21,23,25); foreach($x as $i) { $this->redis->sAdd('x', $i); } $y = array(1,2,3,5,7,11,13,17,19,23); foreach($y as $i) { $this->redis->sAdd('y', $i); } $z = array(1,4,9,16,25); foreach($z as $i) { $this->redis->sAdd('z', $i); } $t = array(2,5,10,17,26); foreach($t as $i) { $this->redis->sAdd('t', $i); } $count = $this->redis->sInterStore('k', 'x', 'y'); // odd prime numbers $this->assertEquals($count, $this->redis->sSize('k')); foreach(array_intersect($x, $y) as $i) { $this->assertTrue($this->redis->sContains('k', $i)); } $count = $this->redis->sInterStore('k', 'y', 'z'); // set of odd squares $this->assertEquals($count, $this->redis->sSize('k')); foreach(array_intersect($y, $z) as $i) { $this->assertTrue($this->redis->sContains('k', $i)); } $count = $this->redis->sInterStore('k', 'z', 't'); // squares of the form n^2 + 1 $this->assertEquals($count, 0); $this->assertEquals($count, $this->redis->sSize('k')); $this->redis->delete('z'); $xyz = $this->redis->sInterStore('k', 'x', 'y', 'z'); // only z missing, expect 0. $this->assertTrue($xyz === 0); $this->redis->delete('y'); $xyz = $this->redis->sInterStore('k', 'x', 'y', 'z'); // y and z missing, expect 0. $this->assertTrue($xyz === 0); $this->redis->delete('x'); $xyz = $this->redis->sInterStore('k', 'x', 'y', 'z'); // x y and z ALL missing, expect 0. $this->assertTrue($xyz === 0); $o = $this->redis->sInterStore('k'); $this->assertTrue($o === FALSE); // error, wrong parameter count } public function testsUnion() { $this->redis->delete('x'); // set of odd numbers $this->redis->delete('y'); // set of prime numbers $this->redis->delete('z'); // set of squares $this->redis->delete('t'); // set of numbers of the form n^2 - 1 $x = array(1,3,5,7,9,11,13,15,17,19,21,23,25); foreach($x as $i) { $this->redis->sAdd('x', $i); } $y = array(1,2,3,5,7,11,13,17,19,23); foreach($y as $i) { $this->redis->sAdd('y', $i); } $z = array(1,4,9,16,25); foreach($z as $i) { $this->redis->sAdd('z', $i); } $t = array(2,5,10,17,26); foreach($t as $i) { $this->redis->sAdd('t', $i); } $xy = $this->redis->sUnion('x', 'y'); // x U y foreach($xy as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_merge($x, $y))); } $yz = $this->redis->sUnion('y', 'z'); // y U Z foreach($yz as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_merge($y, $z))); } $zt = $this->redis->sUnion('z', 't'); // z U t foreach($zt as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_merge($z, $t))); } $xyz = $this->redis->sUnion('x', 'y', 'z'); // x U y U z foreach($xyz as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_merge($x, $y, $z))); } $nil = $this->redis->sUnion(); $this->assertTrue($nil === FALSE); } public function testsUnionStore() { $this->redis->delete('x'); // set of odd numbers $this->redis->delete('y'); // set of prime numbers $this->redis->delete('z'); // set of squares $this->redis->delete('t'); // set of numbers of the form n^2 - 1 $x = array(1,3,5,7,9,11,13,15,17,19,21,23,25); foreach($x as $i) { $this->redis->sAdd('x', $i); } $y = array(1,2,3,5,7,11,13,17,19,23); foreach($y as $i) { $this->redis->sAdd('y', $i); } $z = array(1,4,9,16,25); foreach($z as $i) { $this->redis->sAdd('z', $i); } $t = array(2,5,10,17,26); foreach($t as $i) { $this->redis->sAdd('t', $i); } $count = $this->redis->sUnionStore('k', 'x', 'y'); // x U y $xy = array_unique(array_merge($x, $y)); $this->assertEquals($count, count($xy)); foreach($xy as $i) { $i = (int)$i; $this->assertTrue($this->redis->sContains('k', $i)); } $count = $this->redis->sUnionStore('k', 'y', 'z'); // y U z $yz = array_unique(array_merge($y, $z)); $this->assertEquals($count, count($yz)); foreach($yz as $i) { $i = (int)$i; $this->assertTrue($this->redis->sContains('k', $i)); } $count = $this->redis->sUnionStore('k', 'z', 't'); // z U t $zt = array_unique(array_merge($z, $t)); $this->assertEquals($count, count($zt)); foreach($zt as $i) { $i = (int)$i; $this->assertTrue($this->redis->sContains('k', $i)); } $count = $this->redis->sUnionStore('k', 'x', 'y', 'z'); // x U y U z $xyz = array_unique(array_merge($x, $y, $z)); $this->assertEquals($count, count($xyz)); foreach($xyz as $i) { $i = (int)$i; $this->assertTrue($this->redis->sContains('k', $i)); } $this->redis->delete('x'); // x missing now $count = $this->redis->sUnionStore('k', 'x', 'y', 'z'); // x U y U z $this->assertTrue($count === count(array_unique(array_merge($y, $z)))); $this->redis->delete('y'); // x and y missing $count = $this->redis->sUnionStore('k', 'x', 'y', 'z'); // x U y U z $this->assertTrue($count === count(array_unique($z))); $this->redis->delete('z'); // x, y, and z ALL missing $count = $this->redis->sUnionStore('k', 'x', 'y', 'z'); // x U y U z $this->assertTrue($count === 0); $count = $this->redis->sUnionStore('k'); // Union on nothing... $this->assertTrue($count === FALSE); } public function testsDiff() { $this->redis->delete('x'); // set of odd numbers $this->redis->delete('y'); // set of prime numbers $this->redis->delete('z'); // set of squares $this->redis->delete('t'); // set of numbers of the form n^2 - 1 $x = array(1,3,5,7,9,11,13,15,17,19,21,23,25); foreach($x as $i) { $this->redis->sAdd('x', $i); } $y = array(1,2,3,5,7,11,13,17,19,23); foreach($y as $i) { $this->redis->sAdd('y', $i); } $z = array(1,4,9,16,25); foreach($z as $i) { $this->redis->sAdd('z', $i); } $t = array(2,5,10,17,26); foreach($t as $i) { $this->redis->sAdd('t', $i); } $xy = $this->redis->sDiff('x', 'y'); // x U y foreach($xy as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_diff($x, $y))); } $yz = $this->redis->sDiff('y', 'z'); // y U Z foreach($yz as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_diff($y, $z))); } $zt = $this->redis->sDiff('z', 't'); // z U t foreach($zt as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_diff($z, $t))); } $xyz = $this->redis->sDiff('x', 'y', 'z'); // x U y U z foreach($xyz as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_diff($x, $y, $z))); } $nil = $this->redis->sDiff(); $this->assertTrue($nil === FALSE); } public function testsDiffStore() { $this->redis->delete('x'); // set of odd numbers $this->redis->delete('y'); // set of prime numbers $this->redis->delete('z'); // set of squares $this->redis->delete('t'); // set of numbers of the form n^2 - 1 $x = array(1,3,5,7,9,11,13,15,17,19,21,23,25); foreach($x as $i) { $this->redis->sAdd('x', $i); } $y = array(1,2,3,5,7,11,13,17,19,23); foreach($y as $i) { $this->redis->sAdd('y', $i); } $z = array(1,4,9,16,25); foreach($z as $i) { $this->redis->sAdd('z', $i); } $t = array(2,5,10,17,26); foreach($t as $i) { $this->redis->sAdd('t', $i); } $count = $this->redis->sDiffStore('k', 'x', 'y'); // x - y $xy = array_unique(array_diff($x, $y)); $this->assertEquals($count, count($xy)); foreach($xy as $i) { $i = (int)$i; $this->assertTrue($this->redis->sContains('k', $i)); } $count = $this->redis->sDiffStore('k', 'y', 'z'); // y - z $yz = array_unique(array_diff($y, $z)); $this->assertEquals($count, count($yz)); foreach($yz as $i) { $i = (int)$i; $this->assertTrue($this->redis->sContains('k', $i)); } $count = $this->redis->sDiffStore('k', 'z', 't'); // z - t $zt = array_unique(array_diff($z, $t)); $this->assertEquals($count, count($zt)); foreach($zt as $i) { $i = (int)$i; $this->assertTrue($this->redis->sContains('k', $i)); } $count = $this->redis->sDiffStore('k', 'x', 'y', 'z'); // x - y - z $xyz = array_unique(array_diff($x, $y, $z)); $this->assertEquals($count, count($xyz)); foreach($xyz as $i) { $i = (int)$i; $this->assertTrue($this->redis->sContains('k', $i)); } $this->redis->delete('x'); // x missing now $count = $this->redis->sDiffStore('k', 'x', 'y', 'z'); // x - y - z $this->assertTrue($count === 0); $this->redis->delete('y'); // x and y missing $count = $this->redis->sDiffStore('k', 'x', 'y', 'z'); // x - y - z $this->assertTrue($count === 0); $this->redis->delete('z'); // x, y, and z ALL missing $count = $this->redis->sDiffStore('k', 'x', 'y', 'z'); // x - y - z $this->assertTrue($count === 0); $count = $this->redis->sDiffStore('k'); // diff on nothing... $this->assertTrue($count === FALSE); } public function testlGetRange() { $this->redis->delete('list'); $this->redis->lPush('list', 'val'); $this->redis->lPush('list', 'val2'); $this->redis->lPush('list', 'val3'); // pos : 0 1 2 // pos : -3 -2 -1 // list: [val3, val2, val] $this->assertEquals($this->redis->lGetRange('list', 0, 0), array('val3')); $this->assertEquals($this->redis->lGetRange('list', 0, 1), array('val3', 'val2')); $this->assertEquals($this->redis->lGetRange('list', 0, 2), array('val3', 'val2', 'val')); $this->assertEquals($this->redis->lGetRange('list', 0, 3), array('val3', 'val2', 'val')); $this->assertEquals($this->redis->lGetRange('list', 0, -1), array('val3', 'val2', 'val')); $this->assertEquals($this->redis->lGetRange('list', 0, -2), array('val3', 'val2')); $this->assertEquals($this->redis->lGetRange('list', -2, -1), array('val2', 'val')); $this->redis->delete('list'); $this->assertEquals($this->redis->lGetRange('list', 0, -1), array()); } // public function testsave() { // $this->assertTrue($this->redis->save() === TRUE); // don't really know how else to test this... // } // public function testbgSave() { // // let's try to fill the DB and then bgSave twice. We expect the second one to fail. // for($i = 0; $i < 10e+4; $i++) { // $s = md5($i); // $this->redis->set($s, $s); // } // $this->assertTrue($this->redis->bgSave() === TRUE); // the first one should work. // $this->assertTrue($this->redis->bgSave() === FALSE); // the second one should fail (still working on the first one) // } // // public function testlastSave() { // while(!$this->redis->save()) { // sleep(1); // } // $t_php = microtime(TRUE); // $t_redis = $this->redis->lastSave(); // // $this->assertTrue($t_php - $t_redis < 10000); // check that it's approximately what we've measured in PHP. // } // // public function testflushDb() { // $this->redis->set('x', 'y'); // $this->assertTrue($this->redis->flushDb()); // $this->assertTrue($this->redis->getKeys('*') === array()); // } // // public function testflushAll() { // $this->redis->set('x', 'y'); // $this->assertTrue($this->redis->flushAll()); // $this->assertTrue($this->redis->getKeys('*') === array()); // } public function testdbSize() { $this->assertTrue($this->redis->flushDB()); $this->redis->set('x', 'y'); $this->assertTrue($this->redis->dbSize() === 1); } public function testttl() { $this->redis->set('x', 'y'); $this->redis->setTimeout('x', 5); for($i = 5; $i > 0; $i--) { $this->assertEquals($i, $this->redis->ttl('x')); sleep(1); } // A key with no TTL $this->redis->del('x'); $this->redis->set('x', 'bar'); $this->assertEquals($this->redis->ttl('x'), -1); // A key that doesn't exist (> 2.8 will return -2) if(version_compare($this->version, "2.8.0", "gte")) { $this->redis->del('x'); $this->assertEquals($this->redis->ttl('x'), -2); } } public function testPersist() { $this->redis->set('x', 'y'); $this->redis->setTimeout('x', 100); $this->assertTrue(TRUE === $this->redis->persist('x')); // true if there is a timeout $this->assertTrue(-1 === $this->redis->ttl('x')); // -1: timeout has been removed. $this->assertTrue(FALSE === $this->redis->persist('x')); // false if there is no timeout $this->redis->delete('x'); $this->assertTrue(FALSE === $this->redis->persist('x')); // false if the key doesn’t exist. } public function testClient() { /* CLIENT SETNAME */ $this->assertTrue($this->redis->client('setname', 'phpredis_unit_tests')); /* CLIENT LIST */ $arr_clients = $this->redis->client('list'); $this->assertTrue(is_array($arr_clients)); // Figure out which ip:port is us! $str_addr = NULL; foreach($arr_clients as $arr_client) { if($arr_client['name'] == 'phpredis_unit_tests') { $str_addr = $arr_client['addr']; } } // We should have found our connection $this->assertFalse(empty($str_addr)); /* CLIENT GETNAME */ $this->assertTrue($this->redis->client('getname'), 'phpredis_unit_tests'); /* CLIENT KILL -- phpredis will reconnect, so we can do this */ $this->assertTrue($this->redis->client('kill', $str_addr)); } public function testSlowlog() { // We don't really know what's going to be in the slowlog, but make sure // the command returns proper types when called in various ways $this->assertTrue(is_array($this->redis->slowlog('get'))); $this->assertTrue(is_array($this->redis->slowlog('get', 10))); $this->assertTrue(is_int($this->redis->slowlog('len'))); $this->assertTrue($this->redis->slowlog('reset')); $this->assertFalse($this->redis->slowlog('notvalid')); } public function testWait() { // Closest we can check based on redis commmit history if(version_compare($this->version, '2.9.11', 'lt')) { $this->markTestSkipped(); return; } // We could have slaves here, so determine that $arr_slaves = $this->redis->info(); $i_slaves = $arr_slaves['connected_slaves']; // Send a couple commands $this->redis->set('wait-foo', 'over9000'); $this->redis->set('wait-bar', 'revo9000'); // Make sure we get the right replication count $this->assertEquals($this->redis->wait($i_slaves, 100), $i_slaves); // Pass more slaves than are connected $this->redis->set('wait-foo','over9000'); $this->redis->set('wait-bar','revo9000'); $this->assertTrue($this->redis->wait($i_slaves+1, 100) < $i_slaves+1); // Make sure when we pass with bad arguments we just get back false $this->assertFalse($this->redis->wait(-1, -1)); $this->assertFalse($this->redis->wait(-1, 20)); } public function testinfo() { $info = $this->redis->info(); $keys = array( "redis_version", "arch_bits", "uptime_in_seconds", "uptime_in_days", "connected_clients", "connected_slaves", "used_memory", "total_connections_received", "total_commands_processed", "role"); if (version_compare($this->version, "2.5.0", "lt")) { array_push($keys, "changes_since_last_save", "bgsave_in_progress", "last_save_time" ); } else { array_push($keys, "rdb_changes_since_last_save", "rdb_bgsave_in_progress", "rdb_last_save_time" ); } foreach($keys as $k) { $this->assertTrue(in_array($k, array_keys($info))); } } public function testInfoCommandStats() { // INFO COMMANDSTATS is new in 2.6.0 if (version_compare($this->version, "2.5.0", "lt")) { $this->markTestSkipped(); } $info = $this->redis->info("COMMANDSTATS"); $this->assertTrue(is_array($info)); if (is_array($info)) { foreach($info as $k => $value) { $this->assertTrue(strpos($k, 'cmdstat_') !== false); } } } public function testSelect() { $this->assertFalse($this->redis->select(-1)); $this->assertTrue($this->redis->select(0)); } public function testMset() { $this->redis->delete('x', 'y', 'z'); // remove x y z $this->assertTrue($this->redis->mset(array('x' => 'a', 'y' => 'b', 'z' => 'c'))); // set x y z $this->assertEquals($this->redis->mget(array('x', 'y', 'z')), array('a', 'b', 'c')); // check x y z $this->redis->delete('x'); // delete just x $this->assertTrue($this->redis->mset(array('x' => 'a', 'y' => 'b', 'z' => 'c'))); // set x y z $this->assertEquals($this->redis->mget(array('x', 'y', 'z')), array('a', 'b', 'c')); // check x y z $this->assertFalse($this->redis->mset(array())); // set ø → FALSE /* * Integer keys */ // No prefix $set_array = Array(-1 => 'neg1', -2 => 'neg2', -3 => 'neg3', 1 => 'one', 2 => 'two', '3' => 'three'); $this->redis->delete(array_keys($set_array)); $this->assertTrue($this->redis->mset($set_array)); $this->assertEquals($this->redis->mget(array_keys($set_array)), array_values($set_array)); $this->redis->delete(array_keys($set_array)); // With a prefix $this->redis->setOption(Redis::OPT_PREFIX, 'pfx:'); $this->redis->delete(array_keys($set_array)); $this->assertTrue($this->redis->mset($set_array)); $this->assertEquals($this->redis->mget(array_keys($set_array)), array_values($set_array)); $this->redis->delete(array_keys($set_array)); $this->redis->setOption(Redis::OPT_PREFIX, ''); } public function testMsetNX() { $this->redis->delete('x', 'y', 'z'); // remove x y z $this->assertTrue(TRUE === $this->redis->msetnx(array('x' => 'a', 'y' => 'b', 'z' => 'c'))); // set x y z $this->assertEquals($this->redis->mget(array('x', 'y', 'z')), array('a', 'b', 'c')); // check x y z $this->redis->delete('x'); // delete just x $this->assertTrue(FALSE === $this->redis->msetnx(array('x' => 'A', 'y' => 'B', 'z' => 'C'))); // set x y z $this->assertEquals($this->redis->mget(array('x', 'y', 'z')), array(FALSE, 'b', 'c')); // check x y z $this->assertFalse($this->redis->msetnx(array())); // set ø → FALSE } public function testRpopLpush() { // standard case. $this->redis->delete('x', 'y'); $this->redis->lpush('x', 'abc'); $this->redis->lpush('x', 'def'); // x = [def, abc] $this->redis->lpush('y', '123'); $this->redis->lpush('y', '456'); // y = [456, 123] $this->assertEquals($this->redis->rpoplpush('x', 'y'), 'abc'); // we RPOP x, yielding abc. $this->assertEquals($this->redis->lgetRange('x', 0, -1), array('def')); // only def remains in x. $this->assertEquals($this->redis->lgetRange('y', 0, -1), array('abc', '456', '123')); // abc has been lpushed to y. // with an empty source, expecting no change. $this->redis->delete('x', 'y'); $this->assertTrue(FALSE === $this->redis->rpoplpush('x', 'y')); $this->assertTrue(array() === $this->redis->lgetRange('x', 0, -1)); $this->assertTrue(array() === $this->redis->lgetRange('y', 0, -1)); } public function testBRpopLpush() { // standard case. $this->redis->delete('x', 'y'); $this->redis->lpush('x', 'abc'); $this->redis->lpush('x', 'def'); // x = [def, abc] $this->redis->lpush('y', '123'); $this->redis->lpush('y', '456'); // y = [456, 123] $this->assertEquals($this->redis->brpoplpush('x', 'y', 1), 'abc'); // we RPOP x, yielding abc. $this->assertEquals($this->redis->lgetRange('x', 0, -1), array('def')); // only def remains in x. $this->assertEquals($this->redis->lgetRange('y', 0, -1), array('abc', '456', '123')); // abc has been lpushed to y. // with an empty source, expecting no change. $this->redis->delete('x', 'y'); $this->assertTrue(FALSE === $this->redis->brpoplpush('x', 'y', 1)); $this->assertTrue(array() === $this->redis->lgetRange('x', 0, -1)); $this->assertTrue(array() === $this->redis->lgetRange('y', 0, -1)); } public function testZAddFirstArg() { $zsetName = 100; // Make sure int keys work $this->redis->delete($zsetName); $this->assertEquals(1, $this->redis->zAdd($zsetName, 0, 'val0')); $this->assertEquals(1, $this->redis->zAdd($zsetName, 1, 'val1')); $this->assertTrue(array('val0', 'val1') === $this->redis->zRange($zsetName, 0, -1)); } public function testZX() { $this->redis->delete('key'); $this->assertTrue(array() === $this->redis->zRange('key', 0, -1)); $this->assertTrue(array() === $this->redis->zRange('key', 0, -1, true)); $this->assertTrue(1 === $this->redis->zAdd('key', 0, 'val0')); $this->assertTrue(1 === $this->redis->zAdd('key', 2, 'val2')); $this->assertTrue(1 === $this->redis->zAdd('key', 1, 'val1')); $this->assertTrue(1 === $this->redis->zAdd('key', 3, 'val3')); $this->assertTrue(2 === $this->redis->zAdd('key', 4, 'val4', 5, 'val5')); // multiple parameters $this->assertTrue(array('val0', 'val1', 'val2', 'val3', 'val4', 'val5') === $this->redis->zRange('key', 0, -1)); // withscores $ret = $this->redis->zRange('key', 0, -1, true); $this->assertTrue(count($ret) == 6); $this->assertTrue($ret['val0'] == 0); $this->assertTrue($ret['val1'] == 1); $this->assertTrue($ret['val2'] == 2); $this->assertTrue($ret['val3'] == 3); $this->assertTrue($ret['val4'] == 4); $this->assertTrue($ret['val5'] == 5); $this->assertTrue(0 === $this->redis->zDelete('key', 'valX')); $this->assertTrue(1 === $this->redis->zDelete('key', 'val3')); $this->assertTrue(1 === $this->redis->zDelete('key', 'val4')); $this->assertTrue(1 === $this->redis->zDelete('key', 'val5')); $this->assertTrue(array('val0', 'val1', 'val2') === $this->redis->zRange('key', 0, -1)); // zGetReverseRange $this->assertTrue(1 === $this->redis->zAdd('key', 3, 'val3')); $this->assertTrue(1 === $this->redis->zAdd('key', 3, 'aal3')); $zero_to_three = $this->redis->zRangeByScore('key', 0, 3); $this->assertTrue(array('val0', 'val1', 'val2', 'aal3', 'val3') === $zero_to_three || array('val0', 'val1', 'val2', 'val3', 'aal3') === $zero_to_three); $three_to_zero = $this->redis->zRevRangeByScore('key', 3, 0); $this->assertTrue(array_reverse(array('val0', 'val1', 'val2', 'aal3', 'val3')) === $three_to_zero || array_reverse(array('val0', 'val1', 'val2', 'val3', 'aal3')) === $three_to_zero); $this->assertTrue(5 === $this->redis->zCount('key', 0, 3)); // withscores $this->redis->zRemove('key', 'aal3'); $zero_to_three = $this->redis->zRangeByScore('key', 0, 3, array('withscores' => TRUE)); $this->assertTrue(array('val0' => 0, 'val1' => 1, 'val2' => 2, 'val3' => 3) == $zero_to_three); $this->assertTrue(4 === $this->redis->zCount('key', 0, 3)); // limit $this->assertTrue(array('val0') === $this->redis->zRangeByScore('key', 0, 3, array('limit' => array(0, 1)))); $this->assertTrue(array('val0', 'val1') === $this->redis->zRangeByScore('key', 0, 3, array('limit' => array(0, 2)))); $this->assertTrue(array('val1', 'val2') === $this->redis->zRangeByScore('key', 0, 3, array('limit' => array(1, 2)))); $this->assertTrue(array('val0', 'val1') === $this->redis->zRangeByScore('key', 0, 1, array('limit' => array(0, 100)))); $this->assertTrue(array('val3') === $this->redis->zRevRangeByScore('key', 3, 0, array('limit' => array(0, 1)))); $this->assertTrue(array('val3', 'val2') === $this->redis->zRevRangeByScore('key', 3, 0, array('limit' => array(0, 2)))); $this->assertTrue(array('val2', 'val1') === $this->redis->zRevRangeByScore('key', 3, 0, array('limit' => array(1, 2)))); $this->assertTrue(array('val1', 'val0') === $this->redis->zRevRangeByScore('key', 1, 0, array('limit' => array(0, 100)))); $this->assertTrue(4 === $this->redis->zSize('key')); $this->assertTrue(1.0 === $this->redis->zScore('key', 'val1')); $this->assertFalse($this->redis->zScore('key', 'val')); $this->assertFalse($this->redis->zScore(3, 2)); // with () and +inf, -inf $this->redis->delete('zset'); $this->redis->zAdd('zset', 1, 'foo'); $this->redis->zAdd('zset', 2, 'bar'); $this->redis->zAdd('zset', 3, 'biz'); $this->redis->zAdd('zset', 4, 'foz'); $this->assertTrue(array('foo' => 1, 'bar' => 2, 'biz' => 3, 'foz' => 4) == $this->redis->zRangeByScore('zset', '-inf', '+inf', array('withscores' => TRUE))); $this->assertTrue(array('foo' => 1, 'bar' => 2) == $this->redis->zRangeByScore('zset', 1, 2, array('withscores' => TRUE))); $this->assertTrue(array('bar' => 2) == $this->redis->zRangeByScore('zset', '(1', 2, array('withscores' => TRUE))); $this->assertTrue(array() == $this->redis->zRangeByScore('zset', '(1', '(2', array('withscores' => TRUE))); $this->assertTrue(4 == $this->redis->zCount('zset', '-inf', '+inf')); $this->assertTrue(2 == $this->redis->zCount('zset', 1, 2)); $this->assertTrue(1 == $this->redis->zCount('zset', '(1', 2)); $this->assertTrue(0 == $this->redis->zCount('zset', '(1', '(2')); // zincrby $this->redis->delete('key'); $this->assertTrue(1.0 === $this->redis->zIncrBy('key', 1, 'val1')); $this->assertTrue(1.0 === $this->redis->zScore('key', 'val1')); $this->assertTrue(2.5 === $this->redis->zIncrBy('key', 1.5, 'val1')); $this->assertTrue(2.5 === $this->redis->zScore('key', 'val1')); //zUnion $this->redis->delete('key1'); $this->redis->delete('key2'); $this->redis->delete('key3'); $this->redis->delete('keyU'); $this->redis->zAdd('key1', 0, 'val0'); $this->redis->zAdd('key1', 1, 'val1'); $this->redis->zAdd('key2', 2, 'val2'); $this->redis->zAdd('key2', 3, 'val3'); $this->redis->zAdd('key3', 4, 'val4'); $this->redis->zAdd('key3', 5, 'val5'); $this->assertTrue(4 === $this->redis->zUnion('keyU', array('key1', 'key3'))); $this->assertTrue(array('val0', 'val1', 'val4', 'val5') === $this->redis->zRange('keyU', 0, -1)); // Union on non existing keys $this->redis->delete('keyU'); $this->assertTrue(0 === $this->redis->zUnion('keyU', array('X', 'Y'))); $this->assertTrue(array() === $this->redis->zRange('keyU', 0, -1)); // !Exist U Exist → copy of existing zset. $this->redis->delete('keyU', 'X'); $this->assertTrue(2 === $this->redis->zUnion('keyU', array('key1', 'X'))); // test weighted zUnion $this->redis->delete('keyZ'); $this->assertTrue(4 === $this->redis->zUnion('keyZ', array('key1', 'key2'), array(1, 1))); $this->assertTrue(array('val0', 'val1', 'val2', 'val3') === $this->redis->zRange('keyZ', 0, -1)); $this->redis->zDeleteRangeByScore('keyZ', 0, 10); $this->assertTrue(4 === $this->redis->zUnion('keyZ', array('key1', 'key2'), array(5, 1))); $this->assertTrue(array('val0', 'val2', 'val3', 'val1') === $this->redis->zRange('keyZ', 0, -1)); $this->redis->delete('key1'); $this->redis->delete('key2'); $this->redis->delete('key3'); //test zUnion with weights and aggegration function $this->redis->zadd('key1', 1, 'duplicate'); $this->redis->zadd('key2', 2, 'duplicate'); $this->redis->zUnion('keyU', array('key1','key2'), array(1,1), 'MIN'); $this->assertTrue($this->redis->zScore('keyU', 'duplicate')===1.0); $this->redis->delete('keyU'); //now test zUnion *without* weights but with aggregrate function $this->redis->zUnion('keyU', array('key1','key2'), null, 'MIN'); $this->assertTrue($this->redis->zScore('keyU', 'duplicate')===1.0); $this->redis->delete('keyU', 'key1', 'key2'); // test integer and float weights (GitHub issue #109). $this->redis->del('key1', 'key2', 'key3'); $this->redis->zadd('key1', 1, 'one'); $this->redis->zadd('key1', 2, 'two'); $this->redis->zadd('key2', 1, 'one'); $this->redis->zadd('key2', 2, 'two'); $this->redis->zadd('key2', 3, 'three'); $this->assertTrue($this->redis->zunion('key3', array('key1', 'key2'), array(2, 3.0)) === 3); $this->redis->delete('key1'); $this->redis->delete('key2'); $this->redis->delete('key3'); // Test 'inf', '-inf', and '+inf' weights (GitHub issue #336) $this->redis->zadd('key1', 1, 'one', 2, 'two', 3, 'three'); $this->redis->zadd('key2', 3, 'three', 4, 'four', 5, 'five'); // Make sure phpredis handles these weights $this->assertTrue($this->redis->zunion('key3', array('key1','key2'), array(1, 'inf')) === 5); $this->assertTrue($this->redis->zunion('key3', array('key1','key2'), array(1, '-inf')) === 5); $this->assertTrue($this->redis->zunion('key3', array('key1','key2'), array(1, '+inf')) === 5); // Now, confirm that they're being sent, and that it works $arr_weights = Array('inf','-inf','+inf'); foreach($arr_weights as $str_weight) { $r = $this->redis->zunionstore('key3', array('key1','key2'), array(1,$str_weight)); $this->assertTrue($r===5); $r = $this->redis->zrangebyscore('key3', '(-inf', '(inf',array('withscores'=>true)); $this->assertTrue(count($r)===2); $this->assertTrue(isset($r['one'])); $this->assertTrue(isset($r['two'])); } $this->redis->del('key1','key2','key3'); $this->redis->zadd('key1', 2000.1, 'one'); $this->redis->zadd('key1', 3000.1, 'two'); $this->redis->zadd('key1', 4000.1, 'three'); $ret = $this->redis->zRange('key1', 0, -1, TRUE); $this->assertTrue(count($ret) === 3); $retValues = array_keys($ret); $this->assertTrue(array('one', 'two', 'three') === $retValues); // + 0 converts from string to float OR integer $this->assertTrue(is_float($ret['one'] + 0)); $this->assertTrue(is_float($ret['two'] + 0)); $this->assertTrue(is_float($ret['three'] + 0)); $this->redis->delete('key1'); // ZREMRANGEBYRANK $this->redis->zAdd('key1', 1, 'one'); $this->redis->zAdd('key1', 2, 'two'); $this->redis->zAdd('key1', 3, 'three'); $this->assertTrue(2 === $this->redis->zremrangebyrank('key1', 0, 1)); $this->assertTrue(array('three' => 3) == $this->redis->zRange('key1', 0, -1, TRUE)); $this->redis->delete('key1'); // zInter $this->redis->zAdd('key1', 0, 'val0'); $this->redis->zAdd('key1', 1, 'val1'); $this->redis->zAdd('key1', 3, 'val3'); $this->redis->zAdd('key2', 2, 'val1'); $this->redis->zAdd('key2', 3, 'val3'); $this->redis->zAdd('key3', 4, 'val3'); $this->redis->zAdd('key3', 5, 'val5'); $this->redis->delete('keyI'); $this->assertTrue(2 === $this->redis->zInter('keyI', array('key1', 'key2'))); $this->assertTrue(array('val1', 'val3') === $this->redis->zRange('keyI', 0, -1)); // Union on non existing keys $this->assertTrue(0 === $this->redis->zInter('keyX', array('X', 'Y'))); $this->assertTrue(array() === $this->redis->zRange('keyX', 0, -1)); // !Exist U Exist $this->assertTrue(0 === $this->redis->zInter('keyY', array('key1', 'X'))); $this->assertTrue(array() === $this->redis->zRange('keyY', 0, -1)); // test weighted zInter $this->redis->delete('key1'); $this->redis->delete('key2'); $this->redis->delete('key3'); $this->redis->zAdd('key1', 0, 'val0'); $this->redis->zAdd('key1', 1, 'val1'); $this->redis->zAdd('key1', 3, 'val3'); $this->redis->zAdd('key2', 2, 'val1'); $this->redis->zAdd('key2', 1, 'val3'); $this->redis->zAdd('key3', 7, 'val1'); $this->redis->zAdd('key3', 3, 'val3'); $this->redis->delete('keyI'); $this->assertTrue(2 === $this->redis->zInter('keyI', array('key1', 'key2'), array(1, 1))); $this->assertTrue(array('val1', 'val3') === $this->redis->zRange('keyI', 0, -1)); $this->redis->delete('keyI'); $this->assertTrue( 2 === $this->redis->zInter('keyI', array('key1', 'key2', 'key3'), array(1, 5, 1), 'min')); $this->assertTrue(array('val1', 'val3') === $this->redis->zRange('keyI', 0, -1)); $this->redis->delete('keyI'); $this->assertTrue( 2 === $this->redis->zInter('keyI', array('key1', 'key2', 'key3'), array(1, 5, 1), 'max')); $this->assertTrue(array('val3', 'val1') === $this->redis->zRange('keyI', 0, -1)); $this->redis->delete('keyI'); $this->assertTrue(2 === $this->redis->zInter('keyI', array('key1', 'key2', 'key3'), null, 'max')); $this->assertTrue($this->redis->zScore('keyI', 'val1') === floatval(7)); // zrank, zrevrank $this->redis->delete('z'); $this->redis->zadd('z', 1, 'one'); $this->redis->zadd('z', 2, 'two'); $this->redis->zadd('z', 5, 'five'); $this->assertTrue(0 === $this->redis->zRank('z', 'one')); $this->assertTrue(1 === $this->redis->zRank('z', 'two')); $this->assertTrue(2 === $this->redis->zRank('z', 'five')); $this->assertTrue(2 === $this->redis->zRevRank('z', 'one')); $this->assertTrue(1 === $this->redis->zRevRank('z', 'two')); $this->assertTrue(0 === $this->redis->zRevRank('z', 'five')); } public function testZRangeByLex() { /* Only out since 2.8.9 */ if (version_compare($this->version, '2.8.9', 'lt')) { $this->markTestSkipped(); return; } $arr_vals = Array('a','b','c','d','e','f','g'); $this->redis->del('zlex'); foreach($arr_vals as $str_val) { $this->redis->zadd('zlex', 0, $str_val); } /* These tests were taken off of redis.io out of sheer laziness :) */ $arr_ret = $this->redis->zRangeByLex('zlex', '-', '[c'); $this->assertTrue($arr_ret === Array('a','b','c')); $arr_ret = $this->redis->zRangeByLex('zlex', '-', '(c'); $this->assertTrue($arr_ret === Array('a','b')); $arr_ret = $this->redis->zRangeByLex('zlex', '[aaa', '(g'); $this->assertTrue($arr_ret === Array('b','c','d','e','f')); /* Test with a limit and count */ $arr_ret = $this->redis->zRangeBylex('zlex', '-', '[c', 1, 2); $this->assertTrue($arr_ret === Array('b','c')); /* Test some invalid calls */ $this->assertFalse($this->redis->zRangeByLex('zlex','b','[s')); $this->assertFalse($this->redis->zRangeByLex('zlex','(a', '')); $this->assertFalse($this->redis->zRangeByLex('zlex','(a','[b',1)); } public function testHashes() { $this->redis->delete('h', 'key'); $this->assertTrue(0 === $this->redis->hLen('h')); $this->assertTrue(1 === $this->redis->hSet('h', 'a', 'a-value')); $this->assertTrue(1 === $this->redis->hLen('h')); $this->assertTrue(1 === $this->redis->hSet('h', 'b', 'b-value')); $this->assertTrue(2 === $this->redis->hLen('h')); $this->assertTrue('a-value' === $this->redis->hGet('h', 'a')); // simple get $this->assertTrue('b-value' === $this->redis->hGet('h', 'b')); // simple get $this->assertTrue(0 === $this->redis->hSet('h', 'a', 'another-value')); // replacement $this->assertTrue('another-value' === $this->redis->hGet('h', 'a')); // get the new value $this->assertTrue('b-value' === $this->redis->hGet('h', 'b')); // simple get $this->assertTrue(FALSE === $this->redis->hGet('h', 'c')); // unknown hash member $this->assertTrue(FALSE === $this->redis->hGet('key', 'c')); // unknownkey // hDel $this->assertTrue(1 === $this->redis->hDel('h', 'a')); // 1 on success $this->assertTrue(0 === $this->redis->hDel('h', 'a')); // 0 on failure $this->redis->delete('h'); $this->redis->hSet('h', 'x', 'a'); $this->redis->hSet('h', 'y', 'b'); $this->assertTrue(2 === $this->redis->hDel('h', 'x', 'y')); // variadic // hsetnx $this->redis->delete('h'); $this->assertTrue(TRUE === $this->redis->hSetNx('h', 'x', 'a')); $this->assertTrue(TRUE === $this->redis->hSetNx('h', 'y', 'b')); $this->assertTrue(FALSE === $this->redis->hSetNx('h', 'x', '?')); $this->assertTrue(FALSE === $this->redis->hSetNx('h', 'y', '?')); $this->assertTrue('a' === $this->redis->hGet('h', 'x')); $this->assertTrue('b' === $this->redis->hGet('h', 'y')); // keys $keys = $this->redis->hKeys('h'); $this->assertTrue($keys === array('x', 'y') || $keys === array('y', 'x')); // values $values = $this->redis->hVals('h'); $this->assertTrue($values === array('a', 'b') || $values === array('b', 'a')); // keys + values $all = $this->redis->hGetAll('h'); $this->assertTrue($all === array('x' => 'a', 'y' => 'b') || $all === array('y' => 'b', 'x' => 'a')); // hExists $this->assertTrue(TRUE === $this->redis->hExists('h', 'x')); $this->assertTrue(TRUE === $this->redis->hExists('h', 'y')); $this->assertTrue(FALSE === $this->redis->hExists('h', 'w')); $this->redis->delete('h'); $this->assertTrue(FALSE === $this->redis->hExists('h', 'x')); // hIncrBy $this->redis->delete('h'); $this->assertTrue(2 === $this->redis->hIncrBy('h', 'x', 2)); $this->assertTrue(3 === $this->redis->hIncrBy('h', 'x', 1)); $this->assertTrue(2 === $this->redis->hIncrBy('h', 'x', -1)); $this->assertTrue(FALSE === $this->redis->hIncrBy('h', 'x', "not-a-number")); $this->assertTrue("2" === $this->redis->hGet('h', 'x')); $this->redis->hSet('h', 'y', 'not-a-number'); $this->assertTrue(FALSE === $this->redis->hIncrBy('h', 'y', 1)); if (version_compare($this->version, "2.5.0", "ge")) { // hIncrByFloat $this->redis->delete('h'); $this->assertTrue(1.5 === $this->redis->hIncrByFloat('h','x', 1.5)); $this->assertTrue(3.0 === $this->redis->hincrByFloat('h','x', 1.5)); $this->assertTrue(1.5 === $this->redis->hincrByFloat('h','x', -1.5)); $this->redis->hset('h','y','not-a-number'); $this->assertTrue(FALSE === $this->redis->hIncrByFloat('h', 'y', 1.5)); } // hmset $this->redis->delete('h'); $this->assertTrue(TRUE === $this->redis->hMset('h', array('x' => 123, 'y' => 456, 'z' => 'abc'))); $this->assertTrue('123' === $this->redis->hGet('h', 'x')); $this->assertTrue('456' === $this->redis->hGet('h', 'y')); $this->assertTrue('abc' === $this->redis->hGet('h', 'z')); $this->assertTrue(FALSE === $this->redis->hGet('h', 't')); // hmget $this->assertTrue(array('x' => '123', 'y' => '456') === $this->redis->hMget('h', array('x', 'y'))); $this->assertTrue(array('z' => 'abc') === $this->redis->hMget('h', array('z'))); $this->assertTrue(array('x' => '123', 't' => FALSE, 'y' => '456') === $this->redis->hMget('h', array('x', 't', 'y'))); $this->assertFalse(array(123 => 'x') === $this->redis->hMget('h', array(123))); $this->assertTrue(array(123 => FALSE) === $this->redis->hMget('h', array(123))); // Test with an array populated with things we can't use as keys $this->assertTrue($this->redis->hmget('h', Array(false,NULL,false)) === FALSE); // Test with some invalid keys mixed in (which should just be ignored) $this->assertTrue(array('x'=>'123','y'=>'456','z'=>'abc') === $this->redis->hMget('h',Array('x',null,'y','','z',false))); // hmget/hmset with numeric fields $this->redis->del('h'); $this->assertTrue(TRUE === $this->redis->hMset('h', array(123 => 'x', 'y' => 456))); $this->assertTrue('x' === $this->redis->hGet('h', 123)); $this->assertTrue('x' === $this->redis->hGet('h', '123')); $this->assertTrue('456' === $this->redis->hGet('h', 'y')); $this->assertTrue(array(123 => 'x', 'y' => '456') === $this->redis->hMget('h', array('123', 'y'))); // check non-string types. $this->redis->delete('h1'); $this->assertTrue(TRUE === $this->redis->hMSet('h1', array('x' => 0, 'y' => array(), 'z' => new stdclass(), 't' => NULL))); $h1 = $this->redis->hGetAll('h1'); $this->assertTrue('0' === $h1['x']); $this->assertTrue('Array' === $h1['y']); $this->assertTrue('Object' === $h1['z']); $this->assertTrue('' === $h1['t']); } public function testSetRange() { $this->redis->delete('key'); $this->redis->set('key', 'hello world'); $this->redis->setRange('key', 6, 'redis'); $this->assertTrue('hello redis' === $this->redis->get('key')); $this->redis->setRange('key', 6, 'you'); // don't cut off the end $this->assertTrue('hello youis' === $this->redis->get('key')); $this->redis->set('key', 'hello world'); // $this->assertTrue(11 === $this->redis->setRange('key', -6, 'redis')); // works with negative offsets too! (disabled because not all versions support this) // $this->assertTrue('hello redis' === $this->redis->get('key')); // fill with zeros if needed $this->redis->delete('key'); $this->redis->setRange('key', 6, 'foo'); $this->assertTrue("\x00\x00\x00\x00\x00\x00foo" === $this->redis->get('key')); } public function testObject() { /* Version 3.0.0 (represented as >= 2.9.0 in redis info) and moving * forward uses "embstr" instead of "raw" for small string values */ if (version_compare($this->version, "2.9.0", "lt")) { $str_small_encoding = "raw"; } else { $str_small_encoding = "embstr"; } $this->redis->del('key'); $this->assertTrue($this->redis->object('encoding', 'key') === FALSE); $this->assertTrue($this->redis->object('refcount', 'key') === FALSE); $this->assertTrue($this->redis->object('idletime', 'key') === FALSE); $this->redis->set('key', 'value'); $this->assertTrue($this->redis->object('encoding', 'key') === $str_small_encoding); $this->assertTrue($this->redis->object('refcount', 'key') === 1); $this->assertTrue($this->redis->object('idletime', 'key') === 0); $this->redis->del('key'); $this->redis->lpush('key', 'value'); $this->assertTrue($this->redis->object('encoding', 'key') === "ziplist"); $this->assertTrue($this->redis->object('refcount', 'key') === 1); $this->assertTrue($this->redis->object('idletime', 'key') === 0); $this->redis->del('key'); $this->redis->sadd('key', 'value'); $this->assertTrue($this->redis->object('encoding', 'key') === "hashtable"); $this->assertTrue($this->redis->object('refcount', 'key') === 1); $this->assertTrue($this->redis->object('idletime', 'key') === 0); $this->redis->del('key'); $this->redis->sadd('key', 42); $this->redis->sadd('key', 1729); $this->assertTrue($this->redis->object('encoding', 'key') === "intset"); $this->assertTrue($this->redis->object('refcount', 'key') === 1); $this->assertTrue($this->redis->object('idletime', 'key') === 0); $this->redis->del('key'); $this->redis->lpush('key', str_repeat('A', pow(10,6))); // 1M elements, too big for a ziplist. $this->assertTrue($this->redis->object('encoding', 'key') === "linkedlist"); $this->assertTrue($this->redis->object('refcount', 'key') === 1); $this->assertTrue($this->redis->object('idletime', 'key') === 0); } public function testMultiExec() { $this->sequence(Redis::MULTI); $this->differentType(Redis::MULTI); // with prefix as well $this->redis->setOption(Redis::OPT_PREFIX, "test:"); $this->sequence(Redis::MULTI); $this->differentType(Redis::MULTI); $this->redis->setOption(Redis::OPT_PREFIX, ""); $this->redis->set('x', '42'); $this->assertTrue(TRUE === $this->redis->watch('x')); $ret = $this->redis->multi() ->get('x') ->exec(); // successful transaction $this->assertTrue($ret === array('42')); // failed transaction $this->redis->watch('x'); $r = $this->newInstance(); // new instance, modifying `x'. $r->incr('x'); $ret = $this->redis->multi() ->get('x') ->exec(); $this->assertTrue($ret === FALSE); // failed because another client changed our watched key between WATCH and EXEC. // watch and unwatch $this->redis->watch('x'); $r->incr('x'); // other instance $this->redis->unwatch(); // cancel transaction watch $ret = $this->redis->multi() ->get('x') ->exec(); $this->assertTrue($ret === array('44')); // succeeded since we've cancel the WATCH command. } public function testPipeline() { $this->sequence(Redis::PIPELINE); $this->differentType(Redis::PIPELINE); // with prefix as well $this->redis->setOption(Redis::OPT_PREFIX, "test:"); $this->sequence(Redis::PIPELINE); $this->differentType(Redis::PIPELINE); $this->redis->setOption(Redis::OPT_PREFIX, ""); } protected function sequence($mode) { $ret = $this->redis->multi($mode) ->set('x', 42) ->info() ->type('x') ->get('x') ->exec(); $this->assertTrue(is_array($ret)); $i = 0; $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue(is_array($ret[$i++])); $this->assertTrue($ret[$i++] === Redis::REDIS_STRING); $this->assertTrue($ret[$i] === '42' || $ret[$i] === 42); $serializer = $this->redis->getOption(Redis::OPT_SERIALIZER); $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); // testing incr, which doesn't work with the serializer $ret = $this->redis->multi($mode) ->delete('key1') ->set('key1', 'value1') ->get('key1') ->getSet('key1', 'value2') ->get('key1') ->set('key2', 4) ->incr('key2') ->get('key2') ->decr('key2') ->get('key2') ->renameKey('key2', 'key3') ->get('key3') ->renameNx('key3', 'key1') ->renameKey('key3', 'key2') ->incr('key2', 5) ->get('key2') ->decr('key2', 5) ->get('key2') ->exec(); $this->assertTrue(is_array($ret)); $i = 0; $this->assertTrue(is_long($ret[$i++])); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 'value1'); $this->assertTrue($ret[$i++] == 'value1'); $this->assertTrue($ret[$i++] == 'value2'); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 5); $this->assertTrue($ret[$i++] == 5); $this->assertTrue($ret[$i++] == 4); $this->assertTrue($ret[$i++] == 4); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 4); $this->assertTrue($ret[$i++] == FALSE); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 9); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 4); $this->assertTrue(count($ret) == $i); $this->redis->setOption(Redis::OPT_SERIALIZER, $serializer); $ret = $this->redis->multi($mode) ->delete('key1') ->delete('key2') ->set('key1', 'val1') ->setnx('key1', 'valX') ->setnx('key2', 'valX') ->exists('key1') ->exists('key3') ->ping() ->exec(); $this->assertTrue(is_array($ret)); $this->assertTrue($ret[0] == TRUE); $this->assertTrue($ret[1] == TRUE); $this->assertTrue($ret[2] == TRUE); $this->assertTrue($ret[3] == FALSE); $this->assertTrue($ret[4] == TRUE); $this->assertTrue($ret[5] == TRUE); $this->assertTrue($ret[6] == FALSE); $this->assertTrue($ret[7] == '+PONG'); $ret = $this->redis->multi($mode) ->randomKey() ->exec(); $ret = $this->redis->multi($mode) ->exec(); $this->assertTrue($ret == array()); // ttl, mget, mset, msetnx, expire, expireAt $this->redis->delete('key'); $ret = $this->redis->multi($mode) ->ttl('key') ->mget(array('key1', 'key2', 'key3')) ->mset(array('key3' => 'value3', 'key4' => 'value4')) ->set('key', 'value') ->expire('key', 5) ->ttl('key') ->expireAt('key', '0000') ->exec(); $this->assertTrue(is_array($ret)); $i = 0; $ttl = $ret[$i++]; $this->assertTrue($ttl === -1 || $ttl === -2); $this->assertTrue($ret[$i++] === array('val1', 'valX', FALSE)); // mget $this->assertTrue($ret[$i++] === TRUE); // mset $this->assertTrue($ret[$i++] === TRUE); // set $this->assertTrue($ret[$i++] === TRUE); // expire $this->assertTrue($ret[$i++] === 5); // ttl $this->assertTrue($ret[$i++] === TRUE); // expireAt $this->assertTrue(count($ret) == $i); $ret = $this->redis->multi($mode) ->set('lkey', 'x') ->set('lDest', 'y') ->delete('lkey', 'lDest') ->rpush('lkey', 'lvalue') ->lpush('lkey', 'lvalue') ->lpush('lkey', 'lvalue') ->lpush('lkey', 'lvalue') ->lpush('lkey', 'lvalue') ->lpush('lkey', 'lvalue') ->rpoplpush('lkey', 'lDest') ->lGetRange('lDest', 0, -1) ->lpop('lkey') ->llen('lkey') ->lRemove('lkey', 'lvalue', 3) ->llen('lkey') ->lget('lkey', 0) ->lGetRange('lkey', 0, -1) ->lSet('lkey', 1, "newValue") // check errors on key not exists ->lGetRange('lkey', 0, -1) ->llen('lkey') ->exec(); $this->assertTrue(is_array($ret)); $i = 0; $this->assertTrue($ret[$i++] === TRUE); // SET $this->assertTrue($ret[$i++] === TRUE); // SET $this->assertTrue($ret[$i++] === 2); // deleting 2 keys $this->assertTrue($ret[$i++] === 1); // rpush, now 1 element $this->assertTrue($ret[$i++] === 2); // lpush, now 2 elements $this->assertTrue($ret[$i++] === 3); // lpush, now 3 elements $this->assertTrue($ret[$i++] === 4); // lpush, now 4 elements $this->assertTrue($ret[$i++] === 5); // lpush, now 5 elements $this->assertTrue($ret[$i++] === 6); // lpush, now 6 elements $this->assertTrue($ret[$i++] === 'lvalue'); // rpoplpush returns the element: "lvalue" $this->assertTrue($ret[$i++] === array('lvalue')); // lDest contains only that one element. $this->assertTrue($ret[$i++] === 'lvalue'); // removing a second element from lkey, now 4 elements left ↓ $this->assertTrue($ret[$i++] === 4); // 4 elements left, after 2 pops. $this->assertTrue($ret[$i++] === 3); // removing 3 elements, now 1 left. $this->assertTrue($ret[$i++] === 1); // 1 element left $this->assertTrue($ret[$i++] === "lvalue"); // this is the current head. $this->assertTrue($ret[$i++] === array("lvalue")); // this is the current list. $this->assertTrue($ret[$i++] === FALSE); // updating a non-existent element fails. $this->assertTrue($ret[$i++] === array("lvalue")); // this is the current list. $this->assertTrue($ret[$i++] === 1); // 1 element left $this->assertTrue(count($ret) == $i); $ret = $this->redis->multi(Redis::PIPELINE) ->delete('lkey', 'lDest') ->rpush('lkey', 'lvalue') ->lpush('lkey', 'lvalue') ->lpush('lkey', 'lvalue') ->rpoplpush('lkey', 'lDest') ->lGetRange('lDest', 0, -1) ->lpop('lkey') ->exec(); $this->assertTrue(is_array($ret)); $i = 0; $this->assertTrue($ret[$i++] <= 2); // deleted 0, 1, or 2 items $this->assertTrue($ret[$i++] === 1); // 1 element in the list $this->assertTrue($ret[$i++] === 2); // 2 elements in the list $this->assertTrue($ret[$i++] === 3); // 3 elements in the list $this->assertTrue($ret[$i++] === 'lvalue'); // rpoplpush returns the element: "lvalue" $this->assertTrue($ret[$i++] === array('lvalue')); // rpoplpush returns the element: "lvalue" $this->assertTrue($ret[$i++] === 'lvalue'); // pop returns the front element: "lvalue" $this->assertTrue(count($ret) == $i); // general command $ret = $this->redis->multi($mode) ->select(3) ->set('keyAAA', 'value') ->set('keyAAB', 'value') ->dbSize() ->lastsave() ->exec(); $this->redis->select(0); // back to normal $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue($ret[$i++] === TRUE); // select $this->assertTrue($ret[$i++] === TRUE); // set $this->assertTrue($ret[$i++] === TRUE); // set $this->assertTrue(is_long($ret[$i++])); // dbsize $this->assertTrue(is_long($ret[$i++])); // lastsave $this->assertTrue(count($ret) === $i); $serializer = $this->redis->getOption(Redis::OPT_SERIALIZER); $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); // testing incr, which doesn't work with the serializer $ret = $this->redis->multi($mode) ->delete('key1') ->set('key1', 'value1') ->get('key1') ->getSet('key1', 'value2') ->get('key1') ->set('key2', 4) ->incr('key2') ->get('key2') ->decr('key2') ->get('key2') ->renameKey('key2', 'key3') ->get('key3') ->renameNx('key3', 'key1') ->renameKey('key3', 'key2') ->incr('key2', 5) ->get('key2') ->decr('key2', 5) ->get('key2') ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i]) && $ret[$i] <= 1); $i++; $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 'value1'); $this->assertTrue($ret[$i++] == 'value1'); $this->assertTrue($ret[$i++] == 'value2'); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 5); $this->assertTrue($ret[$i++] == 5); $this->assertTrue($ret[$i++] == 4); $this->assertTrue($ret[$i++] == 4); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 4); $this->assertTrue($ret[$i++] == FALSE); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 9); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 4); $this->redis->setOption(Redis::OPT_SERIALIZER, $serializer); $ret = $this->redis->multi($mode) ->delete('key1') ->delete('key2') ->set('key1', 'val1') ->setnx('key1', 'valX') ->setnx('key2', 'valX') ->exists('key1') ->exists('key3') ->ping() ->exec(); $this->assertTrue(is_array($ret)); $this->assertTrue($ret[0] == TRUE); $this->assertTrue($ret[1] == TRUE); $this->assertTrue($ret[2] == TRUE); $this->assertTrue($ret[3] == FALSE); $this->assertTrue($ret[4] == TRUE); $this->assertTrue($ret[5] == TRUE); $this->assertTrue($ret[6] == FALSE); $this->assertTrue($ret[7] == '+PONG'); $ret = $this->redis->multi($mode) ->randomKey() ->exec(); $this->assertTrue(is_array($ret) && count($ret) === 1); $this->assertTrue(is_string($ret[0])); // ttl, mget, mset, msetnx, expire, expireAt $ret = $this->redis->multi($mode) ->ttl('key') ->mget(array('key1', 'key2', 'key3')) ->mset(array('key3' => 'value3', 'key4' => 'value4')) ->set('key', 'value') ->expire('key', 5) ->ttl('key') ->expireAt('key', '0000') ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i++])); $this->assertTrue(is_array($ret[$i]) && count($ret[$i]) === 3); // mget $i++; $this->assertTrue($ret[$i++] === TRUE); // mset always returns TRUE $this->assertTrue($ret[$i++] === TRUE); // set always returns TRUE $this->assertTrue($ret[$i++] === TRUE); // expire always returns TRUE $this->assertTrue($ret[$i++] === 5); // TTL was just set. $this->assertTrue($ret[$i++] === TRUE); // expireAt returns TRUE for an existing key $this->assertTrue(count($ret) === $i); // lists $ret = $this->redis->multi($mode) ->delete('lkey', 'lDest') ->rpush('lkey', 'lvalue') ->lpush('lkey', 'lvalue') ->lpush('lkey', 'lvalue') ->lpush('lkey', 'lvalue') ->lpush('lkey', 'lvalue') ->lpush('lkey', 'lvalue') ->rpoplpush('lkey', 'lDest') ->lGetRange('lDest', 0, -1) ->lpop('lkey') ->llen('lkey') ->lRemove('lkey', 'lvalue', 3) ->llen('lkey') ->lget('lkey', 0) ->lGetRange('lkey', 0, -1) ->lSet('lkey', 1, "newValue") // check errors on missing key ->lGetRange('lkey', 0, -1) ->llen('lkey') ->exec(); $this->assertTrue(is_array($ret)); $i = 0; $this->assertTrue($ret[$i] >= 0 && $ret[$i] <= 2); // delete $i++; $this->assertTrue($ret[$i++] === 1); // 1 value $this->assertTrue($ret[$i++] === 2); // 2 values $this->assertTrue($ret[$i++] === 3); // 3 values $this->assertTrue($ret[$i++] === 4); // 4 values $this->assertTrue($ret[$i++] === 5); // 5 values $this->assertTrue($ret[$i++] === 6); // 6 values $this->assertTrue($ret[$i++] === 'lvalue'); $this->assertTrue($ret[$i++] === array('lvalue')); // 1 value only in lDest $this->assertTrue($ret[$i++] === 'lvalue'); // now 4 values left $this->assertTrue($ret[$i++] === 4); $this->assertTrue($ret[$i++] === 3); // removing 3 elements. $this->assertTrue($ret[$i++] === 1); // length is now 1 $this->assertTrue($ret[$i++] === 'lvalue'); // this is the head $this->assertTrue($ret[$i++] === array('lvalue')); // 1 value only in lkey $this->assertTrue($ret[$i++] === FALSE); // can't set list[1] if we only have a single value in it. $this->assertTrue($ret[$i++] === array('lvalue')); // the previous error didn't touch anything. $this->assertTrue($ret[$i++] === 1); // the previous error didn't change the length $this->assertTrue(count($ret) === $i); // sets $ret = $this->redis->multi($mode) ->delete('skey1', 'skey2', 'skeydest', 'skeyUnion', 'sDiffDest') ->sadd('skey1', 'sValue1') ->sadd('skey1', 'sValue2') ->sadd('skey1', 'sValue3') ->sadd('skey1', 'sValue4') ->sadd('skey2', 'sValue1') ->sadd('skey2', 'sValue2') ->sSize('skey1') ->sRemove('skey1', 'sValue2') ->sSize('skey1') ->sMove('skey1', 'skey2', 'sValue4') ->sSize('skey2') ->sContains('skey2', 'sValue4') ->sMembers('skey1') ->sMembers('skey2') ->sInter('skey1', 'skey2') ->sInterStore('skeydest', 'skey1', 'skey2') ->sMembers('skeydest') ->sUnion('skey2', 'skeydest') ->sUnionStore('skeyUnion', 'skey2', 'skeydest') ->sMembers('skeyUnion') ->sDiff('skey1', 'skey2') ->sDiffStore('sDiffDest', 'skey1', 'skey2') ->sMembers('sDiffDest') ->sPop('skey2') ->sSize('skey2') ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i]) && $ret[$i] >= 0 && $ret[$i] <= 5); $i++; // deleted at most 5 values. $this->assertTrue($ret[$i++] === 1); // skey1 now has 1 element. $this->assertTrue($ret[$i++] === 1); // skey1 now has 2 elements. $this->assertTrue($ret[$i++] === 1); // skey1 now has 3 elements. $this->assertTrue($ret[$i++] === 1); // skey1 now has 4 elements. $this->assertTrue($ret[$i++] === 1); // skey2 now has 1 element. $this->assertTrue($ret[$i++] === 1); // skey2 now has 2 elements. $this->assertTrue($ret[$i++] === 4); $this->assertTrue($ret[$i++] === 1); // we did remove that value. $this->assertTrue($ret[$i++] === 3); // now 3 values only. $this->assertTrue($ret[$i++] === TRUE); // the move did succeed. $this->assertTrue($ret[$i++] === 3); // sKey2 now has 3 values. $this->assertTrue($ret[$i++] === TRUE); // sKey2 does contain sValue4. foreach(array('sValue1', 'sValue3') as $k) { // sKey1 contains sValue1 and sValue3. $this->assertTrue(in_array($k, $ret[$i])); } $this->assertTrue(count($ret[$i++]) === 2); foreach(array('sValue1', 'sValue2', 'sValue4') as $k) { // sKey2 contains sValue1, sValue2, and sValue4. $this->assertTrue(in_array($k, $ret[$i])); } $this->assertTrue(count($ret[$i++]) === 3); $this->assertTrue($ret[$i++] === array('sValue1')); // intersection $this->assertTrue($ret[$i++] === 1); // intersection + store → 1 value in the destination set. $this->assertTrue($ret[$i++] === array('sValue1')); // sinterstore destination contents foreach(array('sValue1', 'sValue2', 'sValue4') as $k) { // (skeydest U sKey2) contains sValue1, sValue2, and sValue4. $this->assertTrue(in_array($k, $ret[$i])); } $this->assertTrue(count($ret[$i++]) === 3); // union size $this->assertTrue($ret[$i++] === 3); // unionstore size foreach(array('sValue1', 'sValue2', 'sValue4') as $k) { // (skeyUnion) contains sValue1, sValue2, and sValue4. $this->assertTrue(in_array($k, $ret[$i])); } $this->assertTrue(count($ret[$i++]) === 3); // skeyUnion size $this->assertTrue($ret[$i++] === array('sValue3')); // diff skey1, skey2 : only sValue3 is not shared. $this->assertTrue($ret[$i++] === 1); // sdiffstore size == 1 $this->assertTrue($ret[$i++] === array('sValue3')); // contents of sDiffDest $this->assertTrue(in_array($ret[$i++], array('sValue1', 'sValue2', 'sValue4'))); // we removed an element from sKey2 $this->assertTrue($ret[$i++] === 2); // sKey2 now has 2 elements only. $this->assertTrue(count($ret) === $i); // sorted sets $ret = $this->redis->multi($mode) ->delete('zkey1', 'zkey2', 'zkey5', 'zInter', 'zUnion') ->zadd('zkey1', 1, 'zValue1') ->zadd('zkey1', 5, 'zValue5') ->zadd('zkey1', 2, 'zValue2') ->zRange('zkey1', 0, -1) ->zDelete('zkey1', 'zValue2') ->zRange('zkey1', 0, -1) ->zadd('zkey1', 11, 'zValue11') ->zadd('zkey1', 12, 'zValue12') ->zadd('zkey1', 13, 'zValue13') ->zadd('zkey1', 14, 'zValue14') ->zadd('zkey1', 15, 'zValue15') ->zDeleteRangeByScore('zkey1', 11, 13) ->zrange('zkey1', 0, -1) ->zReverseRange('zkey1', 0, -1) ->zRangeByScore('zkey1', 1, 6) ->zCard('zkey1') ->zScore('zkey1', 'zValue15') ->zadd('zkey2', 5, 'zValue5') ->zadd('zkey2', 2, 'zValue2') ->zInter('zInter', array('zkey1', 'zkey2')) ->zRange('zkey1', 0, -1) ->zRange('zkey2', 0, -1) ->zRange('zInter', 0, -1) ->zUnion('zUnion', array('zkey1', 'zkey2')) ->zRange('zUnion', 0, -1) ->zadd('zkey5', 5, 'zValue5') ->zIncrBy('zkey5', 3, 'zValue5') // fix this ->zScore('zkey5', 'zValue5') ->zScore('zkey5', 'unknown') ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i]) && $ret[$i] >= 0 && $ret[$i] <= 5); $i++; // deleting at most 5 keys $this->assertTrue($ret[$i++] === 1); $this->assertTrue($ret[$i++] === 1); $this->assertTrue($ret[$i++] === 1); $this->assertTrue($ret[$i++] === array('zValue1', 'zValue2', 'zValue5')); $this->assertTrue($ret[$i++] === 1); $this->assertTrue($ret[$i++] === array('zValue1', 'zValue5')); $this->assertTrue($ret[$i++] === 1); // adding zValue11 $this->assertTrue($ret[$i++] === 1); // adding zValue12 $this->assertTrue($ret[$i++] === 1); // adding zValue13 $this->assertTrue($ret[$i++] === 1); // adding zValue14 $this->assertTrue($ret[$i++] === 1); // adding zValue15 $this->assertTrue($ret[$i++] === 3); // deleted zValue11, zValue12, zValue13 $this->assertTrue($ret[$i++] === array('zValue1', 'zValue5', 'zValue14', 'zValue15')); $this->assertTrue($ret[$i++] === array('zValue15', 'zValue14', 'zValue5', 'zValue1')); $this->assertTrue($ret[$i++] === array('zValue1', 'zValue5')); $this->assertTrue($ret[$i++] === 4); // 4 elements $this->assertTrue($ret[$i++] === 15.0); $this->assertTrue($ret[$i++] === 1); // added value $this->assertTrue($ret[$i++] === 1); // added value $this->assertTrue($ret[$i++] === 1); // zinter only has 1 value $this->assertTrue($ret[$i++] === array('zValue1', 'zValue5', 'zValue14', 'zValue15')); // zkey1 contents $this->assertTrue($ret[$i++] === array('zValue2', 'zValue5')); // zkey2 contents $this->assertTrue($ret[$i++] === array('zValue5')); // zinter contents $this->assertTrue($ret[$i++] === 5); // zUnion has 5 values (1,2,5,14,15) $this->assertTrue($ret[$i++] === array('zValue1', 'zValue2', 'zValue5', 'zValue14', 'zValue15')); // zunion contents $this->assertTrue($ret[$i++] === 1); // added value to zkey5, with score 5 $this->assertTrue($ret[$i++] === 8.0); // incremented score by 3 → it is now 8. $this->assertTrue($ret[$i++] === 8.0); // current score is 8. $this->assertTrue($ret[$i++] === FALSE); // score for unknown element. $this->assertTrue(count($ret) === $i); // hash $ret = $this->redis->multi($mode) ->delete('hkey1') ->hset('hkey1', 'key1', 'value1') ->hset('hkey1', 'key2', 'value2') ->hset('hkey1', 'key3', 'value3') ->hmget('hkey1', array('key1', 'key2', 'key3')) ->hget('hkey1', 'key1') ->hlen('hkey1') ->hdel('hkey1', 'key2') ->hdel('hkey1', 'key2') ->hexists('hkey1', 'key2') ->hkeys('hkey1') ->hvals('hkey1') ->hgetall('hkey1') ->hset('hkey1', 'valn', 1) ->hset('hkey1', 'val-fail', 'non-string') ->hget('hkey1', 'val-fail') ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue($ret[$i++] <= 1); // delete $this->assertTrue($ret[$i++] === 1); // added 1 element $this->assertTrue($ret[$i++] === 1); // added 1 element $this->assertTrue($ret[$i++] === 1); // added 1 element $this->assertTrue($ret[$i++] === array('key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3')); // hmget, 3 elements $this->assertTrue($ret[$i++] === 'value1'); // hget $this->assertTrue($ret[$i++] === 3); // hlen $this->assertTrue($ret[$i++] === 1); // hdel succeeded $this->assertTrue($ret[$i++] === 0); // hdel failed $this->assertTrue($ret[$i++] === FALSE); // hexists didn't find the deleted key $this->assertTrue($ret[$i] === array('key1', 'key3') || $ret[$i] === array('key3', 'key1')); $i++; // hkeys $this->assertTrue($ret[$i] === array('value1', 'value3') || $ret[$i] === array('value3', 'value1')); $i++; // hvals $this->assertTrue($ret[$i] === array('key1' => 'value1', 'key3' => 'value3') || $ret[$i] === array('key3' => 'value3', 'key1' => 'value1')); $i++; // hgetall $this->assertTrue($ret[$i++] === 1); // added 1 element $this->assertTrue($ret[$i++] === 1); // added the element, so 1. $this->assertTrue($ret[$i++] === 'non-string'); // hset succeeded $this->assertTrue(count($ret) === $i); $ret = $this->redis->multi($mode) // default to MULTI, not PIPELINE. ->delete('test') ->set('test', 'xyz') ->get('test') ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue($ret[$i++] <= 1); // delete $this->assertTrue($ret[$i++] === TRUE); // added 1 element $this->assertTrue($ret[$i++] === 'xyz'); $this->assertTrue(count($ret) === $i); // GitHub issue 78 $this->redis->del('test'); for($i = 1; $i <= 5; $i++) $this->redis->zadd('test', $i, (string)$i); $result = $this->redis->multi($mode) ->zscore('test', "1") ->zscore('test', "6") ->zscore('test', "8") ->zscore('test', "2") ->exec(); $this->assertTrue($result === array(1.0, FALSE, FALSE, 2.0)); } protected function differentType($mode) { // string $key = 'string'; $ret = $this->redis->multi($mode) ->delete($key) ->set($key, 'value') // lists I/F ->rPush($key, 'lvalue') ->lPush($key, 'lvalue') ->lLen($key) ->lPop($key) ->lGetRange($key, 0, -1) ->lTrim($key, 0, 1) ->lGet($key, 0) ->lSet($key, 0, "newValue") ->lRemove($key, 'lvalue', 1) ->lPop($key) ->rPop($key) ->rPoplPush($key, __FUNCTION__ . 'lkey1') // sets I/F ->sAdd($key, 'sValue1') ->sRemove($key, 'sValue1') ->sPop($key) ->sMove($key, __FUNCTION__ . 'skey1', 'sValue1') ->sSize($key) ->sContains($key, 'sValue1') ->sInter($key, __FUNCTION__ . 'skey2') ->sUnion($key, __FUNCTION__ . 'skey4') ->sDiff($key, __FUNCTION__ . 'skey7') ->sMembers($key) ->sRandMember($key) // sorted sets I/F ->zAdd($key, 1, 'zValue1') ->zDelete($key, 'zValue1') ->zIncrBy($key, 1, 'zValue1') ->zRank($key, 'zValue1') ->zRevRank($key, 'zValue1') ->zRange($key, 0, -1) ->zReverseRange($key, 0, -1) ->zRangeByScore($key, 1, 2) ->zCount($key, 0, -1) ->zCard($key) ->zScore($key, 'zValue1') ->zDeleteRangeByRank($key, 1, 2) ->zDeleteRangeByScore($key, 1, 2) // hash I/F ->hSet($key, 'key1', 'value1') ->hGet($key, 'key1') ->hMGet($key, array('key1')) ->hMSet($key, array('key1' => 'value1')) ->hIncrBy($key, 'key2', 1) ->hExists($key, 'key2') ->hDel($key, 'key2') ->hLen($key) ->hKeys($key) ->hVals($key) ->hGetAll($key) ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i++])); // delete $this->assertTrue($ret[$i++] === TRUE); // set $this->assertTrue($ret[$i++] === FALSE); // rpush $this->assertTrue($ret[$i++] === FALSE); // lpush $this->assertTrue($ret[$i++] === FALSE); // llen $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // lgetrange $this->assertTrue($ret[$i++] === FALSE); // ltrim $this->assertTrue($ret[$i++] === FALSE); // lget $this->assertTrue($ret[$i++] === FALSE); // lset $this->assertTrue($ret[$i++] === FALSE); // lremove $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // rpop $this->assertTrue($ret[$i++] === FALSE); // rpoplush $this->assertTrue($ret[$i++] === FALSE); // sadd $this->assertTrue($ret[$i++] === FALSE); // sremove $this->assertTrue($ret[$i++] === FALSE); // spop $this->assertTrue($ret[$i++] === FALSE); // smove $this->assertTrue($ret[$i++] === FALSE); // ssize $this->assertTrue($ret[$i++] === FALSE); // scontains $this->assertTrue($ret[$i++] === FALSE); // sinter $this->assertTrue($ret[$i++] === FALSE); // sunion $this->assertTrue($ret[$i++] === FALSE); // sdiff $this->assertTrue($ret[$i++] === FALSE); // smembers $this->assertTrue($ret[$i++] === FALSE); // srandmember $this->assertTrue($ret[$i++] === FALSE); // zadd $this->assertTrue($ret[$i++] === FALSE); // zdelete $this->assertTrue($ret[$i++] === FALSE); // zincrby $this->assertTrue($ret[$i++] === FALSE); // zrank $this->assertTrue($ret[$i++] === FALSE); // zrevrank $this->assertTrue($ret[$i++] === FALSE); // zrange $this->assertTrue($ret[$i++] === FALSE); // zreverserange $this->assertTrue($ret[$i++] === FALSE); // zrangebyscore $this->assertTrue($ret[$i++] === FALSE); // zcount $this->assertTrue($ret[$i++] === FALSE); // zcard $this->assertTrue($ret[$i++] === FALSE); // zscore $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyrank $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyscore $this->assertTrue($ret[$i++] === FALSE); // hset $this->assertTrue($ret[$i++] === FALSE); // hget $this->assertTrue($ret[$i++] === FALSE); // hmget $this->assertTrue($ret[$i++] === FALSE); // hmset $this->assertTrue($ret[$i++] === FALSE); // hincrby $this->assertTrue($ret[$i++] === FALSE); // hexists $this->assertTrue($ret[$i++] === FALSE); // hdel $this->assertTrue($ret[$i++] === FALSE); // hlen $this->assertTrue($ret[$i++] === FALSE); // hkeys $this->assertTrue($ret[$i++] === FALSE); // hvals $this->assertTrue($ret[$i++] === FALSE); // hgetall $this->assertEquals($i, count($ret)); // list $key = 'list'; $ret = $this->redis->multi($mode) ->delete($key) ->lpush($key, 'lvalue') // string I/F ->get($key) ->getset($key, 'value2') ->append($key, 'append') ->substr($key, 0, 8) ->mget(array($key)) ->incr($key) ->incrBy($key, 1) ->decr($key) ->decrBy($key, 1) // sets I/F ->sAdd($key, 'sValue1') ->sRemove($key, 'sValue1') ->sPop($key) ->sMove($key, __FUNCTION__ . 'skey1', 'sValue1') ->sSize($key) ->sContains($key, 'sValue1') ->sInter($key, __FUNCTION__ . 'skey2') ->sUnion($key, __FUNCTION__ . 'skey4') ->sDiff($key, __FUNCTION__ . 'skey7') ->sMembers($key) ->sRandMember($key) // sorted sets I/F ->zAdd($key, 1, 'zValue1') ->zDelete($key, 'zValue1') ->zIncrBy($key, 1, 'zValue1') ->zRank($key, 'zValue1') ->zRevRank($key, 'zValue1') ->zRange($key, 0, -1) ->zReverseRange($key, 0, -1) ->zRangeByScore($key, 1, 2) ->zCount($key, 0, -1) ->zCard($key) ->zScore($key, 'zValue1') ->zDeleteRangeByRank($key, 1, 2) ->zDeleteRangeByScore($key, 1, 2) // hash I/F ->hSet($key, 'key1', 'value1') ->hGet($key, 'key1') ->hMGet($key, array('key1')) ->hMSet($key, array('key1' => 'value1')) ->hIncrBy($key, 'key2', 1) ->hExists($key, 'key2') ->hDel($key, 'key2') ->hLen($key) ->hKeys($key) ->hVals($key) ->hGetAll($key) ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i++])); // delete $this->assertTrue($ret[$i++] === 1); // lpush $this->assertTrue($ret[$i++] === FALSE); // get $this->assertTrue($ret[$i++] === FALSE); // getset $this->assertTrue($ret[$i++] === FALSE); // append $this->assertTrue($ret[$i++] === FALSE); // substr $this->assertTrue(is_array($ret[$i]) && count($ret[$i]) === 1 && $ret[$i][0] === FALSE); // mget $i++; $this->assertTrue($ret[$i++] === FALSE); // incr $this->assertTrue($ret[$i++] === FALSE); // incrBy $this->assertTrue($ret[$i++] === FALSE); // decr $this->assertTrue($ret[$i++] === FALSE); // decrBy $this->assertTrue($ret[$i++] === FALSE); // sadd $this->assertTrue($ret[$i++] === FALSE); // sremove $this->assertTrue($ret[$i++] === FALSE); // spop $this->assertTrue($ret[$i++] === FALSE); // smove $this->assertTrue($ret[$i++] === FALSE); // ssize $this->assertTrue($ret[$i++] === FALSE); // scontains $this->assertTrue($ret[$i++] === FALSE); // sinter $this->assertTrue($ret[$i++] === FALSE); // sunion $this->assertTrue($ret[$i++] === FALSE); // sdiff $this->assertTrue($ret[$i++] === FALSE); // smembers $this->assertTrue($ret[$i++] === FALSE); // srandmember $this->assertTrue($ret[$i++] === FALSE); // zadd $this->assertTrue($ret[$i++] === FALSE); // zdelete $this->assertTrue($ret[$i++] === FALSE); // zincrby $this->assertTrue($ret[$i++] === FALSE); // zrank $this->assertTrue($ret[$i++] === FALSE); // zrevrank $this->assertTrue($ret[$i++] === FALSE); // zrange $this->assertTrue($ret[$i++] === FALSE); // zreverserange $this->assertTrue($ret[$i++] === FALSE); // zrangebyscore $this->assertTrue($ret[$i++] === FALSE); // zcount $this->assertTrue($ret[$i++] === FALSE); // zcard $this->assertTrue($ret[$i++] === FALSE); // zscore $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyrank $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyscore $this->assertTrue($ret[$i++] === FALSE); // hset $this->assertTrue($ret[$i++] === FALSE); // hget $this->assertTrue($ret[$i++] === FALSE); // hmget $this->assertTrue($ret[$i++] === FALSE); // hmset $this->assertTrue($ret[$i++] === FALSE); // hincrby $this->assertTrue($ret[$i++] === FALSE); // hexists $this->assertTrue($ret[$i++] === FALSE); // hdel $this->assertTrue($ret[$i++] === FALSE); // hlen $this->assertTrue($ret[$i++] === FALSE); // hkeys $this->assertTrue($ret[$i++] === FALSE); // hvals $this->assertTrue($ret[$i++] === FALSE); // hgetall $this->assertEquals($i, count($ret)); // set $key = 'set'; $ret = $this->redis->multi($mode) ->delete($key) ->sAdd($key, 'sValue') // string I/F ->get($key) ->getset($key, 'value2') ->append($key, 'append') ->substr($key, 0, 8) ->mget(array($key)) ->incr($key) ->incrBy($key, 1) ->decr($key) ->decrBy($key, 1) // lists I/F ->rPush($key, 'lvalue') ->lPush($key, 'lvalue') ->lLen($key) ->lPop($key) ->lGetRange($key, 0, -1) ->lTrim($key, 0, 1) ->lGet($key, 0) ->lSet($key, 0, "newValue") ->lRemove($key, 'lvalue', 1) ->lPop($key) ->rPop($key) ->rPoplPush($key, __FUNCTION__ . 'lkey1') // sorted sets I/F ->zAdd($key, 1, 'zValue1') ->zDelete($key, 'zValue1') ->zIncrBy($key, 1, 'zValue1') ->zRank($key, 'zValue1') ->zRevRank($key, 'zValue1') ->zRange($key, 0, -1) ->zReverseRange($key, 0, -1) ->zRangeByScore($key, 1, 2) ->zCount($key, 0, -1) ->zCard($key) ->zScore($key, 'zValue1') ->zDeleteRangeByRank($key, 1, 2) ->zDeleteRangeByScore($key, 1, 2) // hash I/F ->hSet($key, 'key1', 'value1') ->hGet($key, 'key1') ->hMGet($key, array('key1')) ->hMSet($key, array('key1' => 'value1')) ->hIncrBy($key, 'key2', 1) ->hExists($key, 'key2') ->hDel($key, 'key2') ->hLen($key) ->hKeys($key) ->hVals($key) ->hGetAll($key) ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i++])); // delete $this->assertTrue($ret[$i++] === 1); // zadd $this->assertTrue($ret[$i++] === FALSE); // get $this->assertTrue($ret[$i++] === FALSE); // getset $this->assertTrue($ret[$i++] === FALSE); // append $this->assertTrue($ret[$i++] === FALSE); // substr $this->assertTrue(is_array($ret[$i]) && count($ret[$i]) === 1 && $ret[$i][0] === FALSE); // mget $i++; $this->assertTrue($ret[$i++] === FALSE); // incr $this->assertTrue($ret[$i++] === FALSE); // incrBy $this->assertTrue($ret[$i++] === FALSE); // decr $this->assertTrue($ret[$i++] === FALSE); // decrBy $this->assertTrue($ret[$i++] === FALSE); // rpush $this->assertTrue($ret[$i++] === FALSE); // lpush $this->assertTrue($ret[$i++] === FALSE); // llen $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // lgetrange $this->assertTrue($ret[$i++] === FALSE); // ltrim $this->assertTrue($ret[$i++] === FALSE); // lget $this->assertTrue($ret[$i++] === FALSE); // lset $this->assertTrue($ret[$i++] === FALSE); // lremove $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // rpop $this->assertTrue($ret[$i++] === FALSE); // rpoplush $this->assertTrue($ret[$i++] === FALSE); // zadd $this->assertTrue($ret[$i++] === FALSE); // zdelete $this->assertTrue($ret[$i++] === FALSE); // zincrby $this->assertTrue($ret[$i++] === FALSE); // zrank $this->assertTrue($ret[$i++] === FALSE); // zrevrank $this->assertTrue($ret[$i++] === FALSE); // zrange $this->assertTrue($ret[$i++] === FALSE); // zreverserange $this->assertTrue($ret[$i++] === FALSE); // zrangebyscore $this->assertTrue($ret[$i++] === FALSE); // zcount $this->assertTrue($ret[$i++] === FALSE); // zcard $this->assertTrue($ret[$i++] === FALSE); // zscore $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyrank $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyscore $this->assertTrue($ret[$i++] === FALSE); // hset $this->assertTrue($ret[$i++] === FALSE); // hget $this->assertTrue($ret[$i++] === FALSE); // hmget $this->assertTrue($ret[$i++] === FALSE); // hmset $this->assertTrue($ret[$i++] === FALSE); // hincrby $this->assertTrue($ret[$i++] === FALSE); // hexists $this->assertTrue($ret[$i++] === FALSE); // hdel $this->assertTrue($ret[$i++] === FALSE); // hlen $this->assertTrue($ret[$i++] === FALSE); // hkeys $this->assertTrue($ret[$i++] === FALSE); // hvals $this->assertTrue($ret[$i++] === FALSE); // hgetall $this->assertEquals($i, count($ret)); // sorted set $key = 'sortedset'; $ret = $this->redis->multi($mode) ->delete($key) ->zAdd($key, 0, 'zValue') // string I/F ->get($key) ->getset($key, 'value2') ->append($key, 'append') ->substr($key, 0, 8) ->mget(array($key)) ->incr($key) ->incrBy($key, 1) ->decr($key) ->decrBy($key, 1) // lists I/F ->rPush($key, 'lvalue') ->lPush($key, 'lvalue') ->lLen($key) ->lPop($key) ->lGetRange($key, 0, -1) ->lTrim($key, 0, 1) ->lGet($key, 0) ->lSet($key, 0, "newValue") ->lRemove($key, 'lvalue', 1) ->lPop($key) ->rPop($key) ->rPoplPush($key, __FUNCTION__ . 'lkey1') // sets I/F ->sAdd($key, 'sValue1') ->sRemove($key, 'sValue1') ->sPop($key) ->sMove($key, __FUNCTION__ . 'skey1', 'sValue1') ->sSize($key) ->sContains($key, 'sValue1') ->sInter($key, __FUNCTION__ . 'skey2') ->sUnion($key, __FUNCTION__ . 'skey4') ->sDiff($key, __FUNCTION__ . 'skey7') ->sMembers($key) ->sRandMember($key) // hash I/F ->hSet($key, 'key1', 'value1') ->hGet($key, 'key1') ->hMGet($key, array('key1')) ->hMSet($key, array('key1' => 'value1')) ->hIncrBy($key, 'key2', 1) ->hExists($key, 'key2') ->hDel($key, 'key2') ->hLen($key) ->hKeys($key) ->hVals($key) ->hGetAll($key) ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i++])); // delete $this->assertTrue($ret[$i++] === 1); // zadd $this->assertTrue($ret[$i++] === FALSE); // get $this->assertTrue($ret[$i++] === FALSE); // getset $this->assertTrue($ret[$i++] === FALSE); // append $this->assertTrue($ret[$i++] === FALSE); // substr $this->assertTrue(is_array($ret[$i]) && count($ret[$i]) === 1 && $ret[$i][0] === FALSE); // mget $i++; $this->assertTrue($ret[$i++] === FALSE); // incr $this->assertTrue($ret[$i++] === FALSE); // incrBy $this->assertTrue($ret[$i++] === FALSE); // decr $this->assertTrue($ret[$i++] === FALSE); // decrBy $this->assertTrue($ret[$i++] === FALSE); // rpush $this->assertTrue($ret[$i++] === FALSE); // lpush $this->assertTrue($ret[$i++] === FALSE); // llen $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // lgetrange $this->assertTrue($ret[$i++] === FALSE); // ltrim $this->assertTrue($ret[$i++] === FALSE); // lget $this->assertTrue($ret[$i++] === FALSE); // lset $this->assertTrue($ret[$i++] === FALSE); // lremove $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // rpop $this->assertTrue($ret[$i++] === FALSE); // rpoplush $this->assertTrue($ret[$i++] === FALSE); // sadd $this->assertTrue($ret[$i++] === FALSE); // sremove $this->assertTrue($ret[$i++] === FALSE); // spop $this->assertTrue($ret[$i++] === FALSE); // smove $this->assertTrue($ret[$i++] === FALSE); // ssize $this->assertTrue($ret[$i++] === FALSE); // scontains $this->assertTrue($ret[$i++] === FALSE); // sinter $this->assertTrue($ret[$i++] === FALSE); // sunion $this->assertTrue($ret[$i++] === FALSE); // sdiff $this->assertTrue($ret[$i++] === FALSE); // smembers $this->assertTrue($ret[$i++] === FALSE); // srandmember $this->assertTrue($ret[$i++] === FALSE); // hset $this->assertTrue($ret[$i++] === FALSE); // hget $this->assertTrue($ret[$i++] === FALSE); // hmget $this->assertTrue($ret[$i++] === FALSE); // hmset $this->assertTrue($ret[$i++] === FALSE); // hincrby $this->assertTrue($ret[$i++] === FALSE); // hexists $this->assertTrue($ret[$i++] === FALSE); // hdel $this->assertTrue($ret[$i++] === FALSE); // hlen $this->assertTrue($ret[$i++] === FALSE); // hkeys $this->assertTrue($ret[$i++] === FALSE); // hvals $this->assertTrue($ret[$i++] === FALSE); // hgetall $this->assertEquals($i, count($ret)); // hash $key = 'hash'; $ret = $this->redis->multi($mode) ->delete($key) ->hset($key, 'key1', 'hValue') // string I/F ->get($key) ->getset($key, 'value2') ->append($key, 'append') ->substr($key, 0, 8) ->mget(array($key)) ->incr($key) ->incrBy($key, 1) ->decr($key) ->decrBy($key, 1) // lists I/F ->rPush($key, 'lvalue') ->lPush($key, 'lvalue') ->lLen($key) ->lPop($key) ->lGetRange($key, 0, -1) ->lTrim($key, 0, 1) ->lGet($key, 0) ->lSet($key, 0, "newValue") ->lRemove($key, 'lvalue', 1) ->lPop($key) ->rPop($key) ->rPoplPush($key, __FUNCTION__ . 'lkey1') // sets I/F ->sAdd($key, 'sValue1') ->sRemove($key, 'sValue1') ->sPop($key) ->sMove($key, __FUNCTION__ . 'skey1', 'sValue1') ->sSize($key) ->sContains($key, 'sValue1') ->sInter($key, __FUNCTION__ . 'skey2') ->sUnion($key, __FUNCTION__ . 'skey4') ->sDiff($key, __FUNCTION__ . 'skey7') ->sMembers($key) ->sRandMember($key) // sorted sets I/F ->zAdd($key, 1, 'zValue1') ->zDelete($key, 'zValue1') ->zIncrBy($key, 1, 'zValue1') ->zRank($key, 'zValue1') ->zRevRank($key, 'zValue1') ->zRange($key, 0, -1) ->zReverseRange($key, 0, -1) ->zRangeByScore($key, 1, 2) ->zCount($key, 0, -1) ->zCard($key) ->zScore($key, 'zValue1') ->zDeleteRangeByRank($key, 1, 2) ->zDeleteRangeByScore($key, 1, 2) ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i++])); // delete $this->assertTrue($ret[$i++] === 1); // hset $this->assertTrue($ret[$i++] === FALSE); // get $this->assertTrue($ret[$i++] === FALSE); // getset $this->assertTrue($ret[$i++] === FALSE); // append $this->assertTrue($ret[$i++] === FALSE); // substr $this->assertTrue(is_array($ret[$i]) && count($ret[$i]) === 1 && $ret[$i][0] === FALSE); // mget $i++; $this->assertTrue($ret[$i++] === FALSE); // incr $this->assertTrue($ret[$i++] === FALSE); // incrBy $this->assertTrue($ret[$i++] === FALSE); // decr $this->assertTrue($ret[$i++] === FALSE); // decrBy $this->assertTrue($ret[$i++] === FALSE); // rpush $this->assertTrue($ret[$i++] === FALSE); // lpush $this->assertTrue($ret[$i++] === FALSE); // llen $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // lgetrange $this->assertTrue($ret[$i++] === FALSE); // ltrim $this->assertTrue($ret[$i++] === FALSE); // lget $this->assertTrue($ret[$i++] === FALSE); // lset $this->assertTrue($ret[$i++] === FALSE); // lremove $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // rpop $this->assertTrue($ret[$i++] === FALSE); // rpoplush $this->assertTrue($ret[$i++] === FALSE); // sadd $this->assertTrue($ret[$i++] === FALSE); // sremove $this->assertTrue($ret[$i++] === FALSE); // spop $this->assertTrue($ret[$i++] === FALSE); // smove $this->assertTrue($ret[$i++] === FALSE); // ssize $this->assertTrue($ret[$i++] === FALSE); // scontains $this->assertTrue($ret[$i++] === FALSE); // sinter $this->assertTrue($ret[$i++] === FALSE); // sunion $this->assertTrue($ret[$i++] === FALSE); // sdiff $this->assertTrue($ret[$i++] === FALSE); // smembers $this->assertTrue($ret[$i++] === FALSE); // srandmember $this->assertTrue($ret[$i++] === FALSE); // zadd $this->assertTrue($ret[$i++] === FALSE); // zdelete $this->assertTrue($ret[$i++] === FALSE); // zincrby $this->assertTrue($ret[$i++] === FALSE); // zrank $this->assertTrue($ret[$i++] === FALSE); // zrevrank $this->assertTrue($ret[$i++] === FALSE); // zrange $this->assertTrue($ret[$i++] === FALSE); // zreverserange $this->assertTrue($ret[$i++] === FALSE); // zrangebyscore $this->assertTrue($ret[$i++] === FALSE); // zcount $this->assertTrue($ret[$i++] === FALSE); // zcard $this->assertTrue($ret[$i++] === FALSE); // zscore $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyrank $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyscore $this->assertEquals($i, count($ret)); } public function testDifferentTypeString() { $key = 'string'; $this->redis->del($key); $this->assertEquals(TRUE, $this->redis->set($key, 'value')); // lists I/F $this->assertEquals(FALSE, $this->redis->rPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lLen($key)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->lGetRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->lTrim($key, 0, 1)); $this->assertEquals(FALSE, $this->redis->lGet($key, 0)); $this->assertEquals(FALSE, $this->redis->lSet($key, 0, "newValue")); $this->assertEquals(FALSE, $this->redis->lRemove($key, 'lvalue', 1)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->rPop($key)); $this->assertEquals(FALSE, $this->redis->rPoplPush($key, __FUNCTION__ . 'lkey1')); // sets I/F $this->assertEquals(FALSE, $this->redis->sAdd($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sRemove($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sPop($key)); $this->assertEquals(FALSE, $this->redis->sMove($key, __FUNCTION__ . 'skey1', 'sValue1')); $this->assertEquals(FALSE, $this->redis->sSize($key)); $this->assertEquals(FALSE, $this->redis->sContains($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sInter($key, __FUNCTION__ . 'skey2')); $this->assertEquals(FALSE, $this->redis->sUnion($key, __FUNCTION__ . 'skey4')); $this->assertEquals(FALSE, $this->redis->sDiff($key, __FUNCTION__ . 'skey7')); $this->assertEquals(FALSE, $this->redis->sMembers($key)); $this->assertEquals(FALSE, $this->redis->sRandMember($key)); // sorted sets I/F $this->assertEquals(FALSE, $this->redis->zAdd($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zDelete($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zIncrBy($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRevRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zReverseRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zRangeByScore($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zCount($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zCard($key)); $this->assertEquals(FALSE, $this->redis->zScore($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zDeleteRangeByRank($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zDeleteRangeByScore($key, 1, 2)); // hash I/F $this->assertEquals(FALSE, $this->redis->hSet($key, 'key1', 'value1')); $this->assertEquals(FALSE, $this->redis->hGet($key, 'key1')); $this->assertEquals(FALSE, $this->redis->hMGet($key, array('key1'))); $this->assertEquals(FALSE, $this->redis->hMSet($key, array('key1' => 'value1'))); $this->assertEquals(FALSE, $this->redis->hIncrBy($key, 'key2', 1)); $this->assertEquals(FALSE, $this->redis->hExists($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hDel($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hLen($key)); $this->assertEquals(FALSE, $this->redis->hKeys($key)); $this->assertEquals(FALSE, $this->redis->hVals($key)); $this->assertEquals(FALSE, $this->redis->hGetAll($key)); } public function testDifferentTypeList() { $key = 'list'; $this->redis->del($key); $this->assertEquals(1, $this->redis->lPush($key, 'value')); // string I/F $this->assertEquals(FALSE, $this->redis->get($key)); $this->assertEquals(FALSE, $this->redis->getset($key, 'value2')); $this->assertEquals(FALSE, $this->redis->append($key, 'append')); $this->assertEquals(FALSE, $this->redis->substr($key, 0, 8)); $this->assertEquals(array(FALSE), $this->redis->mget(array($key))); $this->assertEquals(FALSE, $this->redis->incr($key)); $this->assertEquals(FALSE, $this->redis->incrBy($key, 1)); $this->assertEquals(FALSE, $this->redis->decr($key)); $this->assertEquals(FALSE, $this->redis->decrBy($key, 1)); // sets I/F $this->assertEquals(FALSE, $this->redis->sAdd($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sRemove($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sPop($key)); $this->assertEquals(FALSE, $this->redis->sMove($key, __FUNCTION__ . 'skey1', 'sValue1')); $this->assertEquals(FALSE, $this->redis->sSize($key)); $this->assertEquals(FALSE, $this->redis->sContains($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sInter($key, __FUNCTION__ . 'skey2')); $this->assertEquals(FALSE, $this->redis->sUnion($key, __FUNCTION__ . 'skey4')); $this->assertEquals(FALSE, $this->redis->sDiff($key, __FUNCTION__ . 'skey7')); $this->assertEquals(FALSE, $this->redis->sMembers($key)); $this->assertEquals(FALSE, $this->redis->sRandMember($key)); // sorted sets I/F $this->assertEquals(FALSE, $this->redis->zAdd($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zDelete($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zIncrBy($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRevRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zReverseRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zRangeByScore($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zCount($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zCard($key)); $this->assertEquals(FALSE, $this->redis->zScore($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zDeleteRangeByRank($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zDeleteRangeByScore($key, 1, 2)); // hash I/F $this->assertEquals(FALSE, $this->redis->hSet($key, 'key1', 'value1')); $this->assertEquals(FALSE, $this->redis->hGet($key, 'key1')); $this->assertEquals(FALSE, $this->redis->hMGet($key, array('key1'))); $this->assertEquals(FALSE, $this->redis->hMSet($key, array('key1' => 'value1'))); $this->assertEquals(FALSE, $this->redis->hIncrBy($key, 'key2', 1)); $this->assertEquals(FALSE, $this->redis->hExists($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hDel($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hLen($key)); $this->assertEquals(FALSE, $this->redis->hKeys($key)); $this->assertEquals(FALSE, $this->redis->hVals($key)); $this->assertEquals(FALSE, $this->redis->hGetAll($key)); } public function testDifferentTypeSet() { $key = 'set'; $this->redis->del($key); $this->assertEquals(1, $this->redis->sAdd($key, 'value')); // string I/F $this->assertEquals(FALSE, $this->redis->get($key)); $this->assertEquals(FALSE, $this->redis->getset($key, 'value2')); $this->assertEquals(FALSE, $this->redis->append($key, 'append')); $this->assertEquals(FALSE, $this->redis->substr($key, 0, 8)); $this->assertEquals(array(FALSE), $this->redis->mget(array($key))); $this->assertEquals(FALSE, $this->redis->incr($key)); $this->assertEquals(FALSE, $this->redis->incrBy($key, 1)); $this->assertEquals(FALSE, $this->redis->decr($key)); $this->assertEquals(FALSE, $this->redis->decrBy($key, 1)); // lists I/F $this->assertEquals(FALSE, $this->redis->rPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lLen($key)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->lGetRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->lTrim($key, 0, 1)); $this->assertEquals(FALSE, $this->redis->lGet($key, 0)); $this->assertEquals(FALSE, $this->redis->lSet($key, 0, "newValue")); $this->assertEquals(FALSE, $this->redis->lRemove($key, 'lvalue', 1)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->rPop($key)); $this->assertEquals(FALSE, $this->redis->rPoplPush($key, __FUNCTION__ . 'lkey1')); // sorted sets I/F $this->assertEquals(FALSE, $this->redis->zAdd($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zDelete($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zIncrBy($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRevRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zReverseRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zRangeByScore($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zCount($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zCard($key)); $this->assertEquals(FALSE, $this->redis->zScore($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zDeleteRangeByRank($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zDeleteRangeByScore($key, 1, 2)); // hash I/F $this->assertEquals(FALSE, $this->redis->hSet($key, 'key1', 'value1')); $this->assertEquals(FALSE, $this->redis->hGet($key, 'key1')); $this->assertEquals(FALSE, $this->redis->hMGet($key, array('key1'))); $this->assertEquals(FALSE, $this->redis->hMSet($key, array('key1' => 'value1'))); $this->assertEquals(FALSE, $this->redis->hIncrBy($key, 'key2', 1)); $this->assertEquals(FALSE, $this->redis->hExists($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hDel($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hLen($key)); $this->assertEquals(FALSE, $this->redis->hKeys($key)); $this->assertEquals(FALSE, $this->redis->hVals($key)); $this->assertEquals(FALSE, $this->redis->hGetAll($key)); } public function testDifferentTypeSortedSet() { $key = 'sortedset'; $this->redis->del($key); $this->assertEquals(1, $this->redis->zAdd($key, 0, 'value')); // string I/F $this->assertEquals(FALSE, $this->redis->get($key)); $this->assertEquals(FALSE, $this->redis->getset($key, 'value2')); $this->assertEquals(FALSE, $this->redis->append($key, 'append')); $this->assertEquals(FALSE, $this->redis->substr($key, 0, 8)); $this->assertEquals(array(FALSE), $this->redis->mget(array($key))); $this->assertEquals(FALSE, $this->redis->incr($key)); $this->assertEquals(FALSE, $this->redis->incrBy($key, 1)); $this->assertEquals(FALSE, $this->redis->decr($key)); $this->assertEquals(FALSE, $this->redis->decrBy($key, 1)); // lists I/F $this->assertEquals(FALSE, $this->redis->rPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lLen($key)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->lGetRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->lTrim($key, 0, 1)); $this->assertEquals(FALSE, $this->redis->lGet($key, 0)); $this->assertEquals(FALSE, $this->redis->lSet($key, 0, "newValue")); $this->assertEquals(FALSE, $this->redis->lRemove($key, 'lvalue', 1)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->rPop($key)); $this->assertEquals(FALSE, $this->redis->rPoplPush($key, __FUNCTION__ . 'lkey1')); // sets I/F $this->assertEquals(FALSE, $this->redis->sAdd($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sRemove($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sPop($key)); $this->assertEquals(FALSE, $this->redis->sMove($key, __FUNCTION__ . 'skey1', 'sValue1')); $this->assertEquals(FALSE, $this->redis->sSize($key)); $this->assertEquals(FALSE, $this->redis->sContains($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sInter($key, __FUNCTION__ . 'skey2')); $this->assertEquals(FALSE, $this->redis->sUnion($key, __FUNCTION__ . 'skey4')); $this->assertEquals(FALSE, $this->redis->sDiff($key, __FUNCTION__ . 'skey7')); $this->assertEquals(FALSE, $this->redis->sMembers($key)); $this->assertEquals(FALSE, $this->redis->sRandMember($key)); // hash I/F $this->assertEquals(FALSE, $this->redis->hSet($key, 'key1', 'value1')); $this->assertEquals(FALSE, $this->redis->hGet($key, 'key1')); $this->assertEquals(FALSE, $this->redis->hMGet($key, array('key1'))); $this->assertEquals(FALSE, $this->redis->hMSet($key, array('key1' => 'value1'))); $this->assertEquals(FALSE, $this->redis->hIncrBy($key, 'key2', 1)); $this->assertEquals(FALSE, $this->redis->hExists($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hDel($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hLen($key)); $this->assertEquals(FALSE, $this->redis->hKeys($key)); $this->assertEquals(FALSE, $this->redis->hVals($key)); $this->assertEquals(FALSE, $this->redis->hGetAll($key)); } public function testDifferentTypeHash() { $key = 'hash'; $this->redis->del($key); $this->assertEquals(1, $this->redis->hSet($key, 'key', 'value')); // string I/F $this->assertEquals(FALSE, $this->redis->get($key)); $this->assertEquals(FALSE, $this->redis->getset($key, 'value2')); $this->assertEquals(FALSE, $this->redis->append($key, 'append')); $this->assertEquals(FALSE, $this->redis->substr($key, 0, 8)); $this->assertEquals(array(FALSE), $this->redis->mget(array($key))); $this->assertEquals(FALSE, $this->redis->incr($key)); $this->assertEquals(FALSE, $this->redis->incrBy($key, 1)); $this->assertEquals(FALSE, $this->redis->decr($key)); $this->assertEquals(FALSE, $this->redis->decrBy($key, 1)); // lists I/F $this->assertEquals(FALSE, $this->redis->rPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lLen($key)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->lGetRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->lTrim($key, 0, 1)); $this->assertEquals(FALSE, $this->redis->lGet($key, 0)); $this->assertEquals(FALSE, $this->redis->lSet($key, 0, "newValue")); $this->assertEquals(FALSE, $this->redis->lRemove($key, 'lvalue', 1)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->rPop($key)); $this->assertEquals(FALSE, $this->redis->rPoplPush($key, __FUNCTION__ . 'lkey1')); // sets I/F $this->assertEquals(FALSE, $this->redis->sAdd($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sRemove($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sPop($key)); $this->assertEquals(FALSE, $this->redis->sMove($key, __FUNCTION__ . 'skey1', 'sValue1')); $this->assertEquals(FALSE, $this->redis->sSize($key)); $this->assertEquals(FALSE, $this->redis->sContains($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sInter($key, __FUNCTION__ . 'skey2')); $this->assertEquals(FALSE, $this->redis->sUnion($key, __FUNCTION__ . 'skey4')); $this->assertEquals(FALSE, $this->redis->sDiff($key, __FUNCTION__ . 'skey7')); $this->assertEquals(FALSE, $this->redis->sMembers($key)); $this->assertEquals(FALSE, $this->redis->sRandMember($key)); // sorted sets I/F $this->assertEquals(FALSE, $this->redis->zAdd($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zDelete($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zIncrBy($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRevRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zReverseRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zRangeByScore($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zCount($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zCard($key)); $this->assertEquals(FALSE, $this->redis->zScore($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zDeleteRangeByRank($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zDeleteRangeByScore($key, 1, 2)); } public function testSerializerPHP() { $this->checkSerializer(Redis::SERIALIZER_PHP); // with prefix $this->redis->setOption(Redis::OPT_PREFIX, "test:"); $this->checkSerializer(Redis::SERIALIZER_PHP); $this->redis->setOption(Redis::OPT_PREFIX, ""); } public function testSerializerIGBinary() { if(defined('Redis::SERIALIZER_IGBINARY')) { $this->checkSerializer(Redis::SERIALIZER_IGBINARY); // with prefix $this->redis->setOption(Redis::OPT_PREFIX, "test:"); $this->checkSerializer(Redis::SERIALIZER_IGBINARY); $this->redis->setOption(Redis::OPT_PREFIX, ""); } } private function checkSerializer($mode) { $this->redis->delete('key'); $this->assertTrue($this->redis->getOption(Redis::OPT_SERIALIZER) === Redis::SERIALIZER_NONE); // default $this->assertTrue($this->redis->setOption(Redis::OPT_SERIALIZER, $mode) === TRUE); // set ok $this->assertTrue($this->redis->getOption(Redis::OPT_SERIALIZER) === $mode); // get ok // lPush, rPush $a = array('hello world', 42, TRUE, array('<tag>' => 1729)); $this->redis->delete('key'); $this->redis->lPush('key', $a[0]); $this->redis->rPush('key', $a[1]); $this->redis->rPush('key', $a[2]); $this->redis->rPush('key', $a[3]); // lGetRange $this->assertTrue($a === $this->redis->lGetRange('key', 0, -1)); // lGet $this->assertTrue($a[0] === $this->redis->lGet('key', 0)); $this->assertTrue($a[1] === $this->redis->lGet('key', 1)); $this->assertTrue($a[2] === $this->redis->lGet('key', 2)); $this->assertTrue($a[3] === $this->redis->lGet('key', 3)); // lRemove $this->assertTrue($this->redis->lRemove('key', $a[3]) === 1); $this->assertTrue(array_slice($a, 0, 3) === $this->redis->lGetRange('key', 0, -1)); // lSet $a[0] = array('k' => 'v'); // update $this->assertTrue(TRUE === $this->redis->lSet('key', 0, $a[0])); $this->assertTrue($a[0] === $this->redis->lGet('key', 0)); // lInsert $this->assertTrue($this->redis->lInsert('key', Redis::BEFORE, $a[0], array(1,2,3)) === 4); $this->assertTrue($this->redis->lInsert('key', Redis::AFTER, $a[0], array(4,5,6)) === 5); $a = array(array(1,2,3), $a[0], array(4,5,6), $a[1], $a[2]); $this->assertTrue($a === $this->redis->lGetRange('key', 0, -1)); // sAdd $this->redis->delete('key'); $s = array(1,'a', array(1,2,3), array('k' => 'v')); $this->assertTrue(1 === $this->redis->sAdd('key', $s[0])); $this->assertTrue(1 === $this->redis->sAdd('key', $s[1])); $this->assertTrue(1 === $this->redis->sAdd('key', $s[2])); $this->assertTrue(1 === $this->redis->sAdd('key', $s[3])); // variadic sAdd $this->redis->delete('k'); $this->assertTrue(3 === $this->redis->sAdd('k', 'a', 'b', 'c')); $this->assertTrue(1 === $this->redis->sAdd('k', 'a', 'b', 'c', 'd')); // sRemove $this->assertTrue(1 === $this->redis->sRemove('key', $s[3])); $this->assertTrue(0 === $this->redis->sRemove('key', $s[3])); // variadic $this->redis->delete('k'); $this->redis->sAdd('k', 'a', 'b', 'c', 'd'); $this->assertTrue(2 === $this->redis->sRem('k', 'a', 'd')); $this->assertTrue(2 === $this->redis->sRem('k', 'b', 'c', 'e')); $this->assertTrue(FALSE === $this->redis->exists('k')); // sContains $this->assertTrue(TRUE === $this->redis->sContains('key', $s[0])); $this->assertTrue(TRUE === $this->redis->sContains('key', $s[1])); $this->assertTrue(TRUE === $this->redis->sContains('key', $s[2])); $this->assertTrue(FALSE === $this->redis->sContains('key', $s[3])); unset($s[3]); // sMove $this->redis->delete('tmp'); $this->redis->sMove('key', 'tmp', $s[0]); $this->assertTrue(FALSE === $this->redis->sContains('key', $s[0])); $this->assertTrue(TRUE === $this->redis->sContains('tmp', $s[0])); unset($s[0]); // sorted sets $z = array('z0', array('k' => 'v'), FALSE, NULL); $this->redis->delete('key'); // zAdd $this->assertTrue(1 === $this->redis->zAdd('key', 0, $z[0])); $this->assertTrue(1 === $this->redis->zAdd('key', 1, $z[1])); $this->assertTrue(1 === $this->redis->zAdd('key', 2, $z[2])); $this->assertTrue(1 === $this->redis->zAdd('key', 3, $z[3])); // zDelete $this->assertTrue(1 === $this->redis->zDelete('key', $z[3])); $this->assertTrue(0 === $this->redis->zDelete('key', $z[3])); unset($z[3]); // check that zDelete doesn't crash with a missing parameter (GitHub issue #102): $this->assertTrue(FALSE === @$this->redis->zDelete('key')); // variadic $this->redis->delete('k'); $this->redis->zAdd('k', 0, 'a'); $this->redis->zAdd('k', 1, 'b'); $this->redis->zAdd('k', 2, 'c'); $this->assertTrue(2 === $this->redis->zDelete('k', 'a', 'c')); $this->assertTrue(1.0 === $this->redis->zScore('k', 'b')); $this->assertTrue($this->redis->zRange('k', 0, -1, true) == array('b' => 1.0)); // zRange $this->assertTrue($z === $this->redis->zRange('key', 0, -1)); // zScore $this->assertTrue(0.0 === $this->redis->zScore('key', $z[0])); $this->assertTrue(1.0 === $this->redis->zScore('key', $z[1])); $this->assertTrue(2.0 === $this->redis->zScore('key', $z[2])); // zRank $this->assertTrue(0 === $this->redis->zRank('key', $z[0])); $this->assertTrue(1 === $this->redis->zRank('key', $z[1])); $this->assertTrue(2 === $this->redis->zRank('key', $z[2])); // zRevRank $this->assertTrue(2 === $this->redis->zRevRank('key', $z[0])); $this->assertTrue(1 === $this->redis->zRevRank('key', $z[1])); $this->assertTrue(0 === $this->redis->zRevRank('key', $z[2])); // zIncrBy $this->assertTrue(3.0 === $this->redis->zIncrBy('key', 1.0, $z[2])); $this->assertTrue(3.0 === $this->redis->zScore('key', $z[2])); $this->assertTrue(5.0 === $this->redis->zIncrBy('key', 2.0, $z[2])); $this->assertTrue(5.0 === $this->redis->zScore('key', $z[2])); $this->assertTrue(2.0 === $this->redis->zIncrBy('key', -3.0, $z[2])); $this->assertTrue(2.0 === $this->redis->zScore('key', $z[2])); // mset $a = array('k0' => 1, 'k1' => 42, 'k2' => NULL, 'k3' => FALSE, 'k4' => array('a' => 'b')); $this->assertTrue(TRUE === $this->redis->mset($a)); foreach($a as $k => $v) { $this->assertTrue($this->redis->get($k) === $v); } $a = array('k0' => 1, 'k1' => 42, 'k2' => NULL, 'k3' => FALSE, 'k4' => array('a' => 'b')); // hSet $this->redis->delete('key'); foreach($a as $k => $v) { $this->assertTrue(1 === $this->redis->hSet('key', $k, $v)); } // hGet foreach($a as $k => $v) { $this->assertTrue($v === $this->redis->hGet('key', $k)); } // hGetAll $this->assertTrue($a === $this->redis->hGetAll('key')); $this->assertTrue(TRUE === $this->redis->hExists('key', 'k0')); $this->assertTrue(TRUE === $this->redis->hExists('key', 'k1')); $this->assertTrue(TRUE === $this->redis->hExists('key', 'k2')); $this->assertTrue(TRUE === $this->redis->hExists('key', 'k3')); $this->assertTrue(TRUE === $this->redis->hExists('key', 'k4')); // hMSet $this->redis->delete('key'); $this->redis->hMSet('key', $a); foreach($a as $k => $v) { $this->assertTrue($v === $this->redis->hGet('key', $k)); } // hMget $hmget = $this->redis->hMget('key', array_keys($a)); foreach($hmget as $k => $v) { $this->assertTrue($v === $a[$k]); } // getMultiple $this->redis->set('a', NULL); $this->redis->set('b', FALSE); $this->redis->set('c', 42); $this->redis->set('d', array('x' => 'y')); $this->assertTrue(array(NULL, FALSE, 42, array('x' => 'y')) === $this->redis->getMultiple(array('a', 'b', 'c', 'd'))); // pipeline $this->sequence(Redis::PIPELINE); // multi-exec $this->sequence(Redis::MULTI); // keys $this->assertTrue(is_array($this->redis->keys('*'))); // issue #62, hgetall $this->redis->del('hash1'); $this->redis->hSet('hash1','data', 'test 1'); $this->redis->hSet('hash1','session_id', 'test 2'); $data = $this->redis->hGetAll('hash1'); $this->assertTrue($data['data'] === 'test 1'); $this->assertTrue($data['session_id'] === 'test 2'); // issue #145, serializer with objects. $this->redis->set('x', array(new stdClass, new stdClass)); $x = $this->redis->get('x'); $this->assertTrue(is_array($x)); $this->assertTrue(is_object($x[0]) && get_class($x[0]) === 'stdClass'); $this->assertTrue(is_object($x[1]) && get_class($x[1]) === 'stdClass'); // revert $this->assertTrue($this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE) === TRUE); // set ok $this->assertTrue($this->redis->getOption(Redis::OPT_SERIALIZER) === Redis::SERIALIZER_NONE); // get ok } public function testDebug() { $this->redis->set('foo', 0); $arr_info = $this->redis->debug('foo'); $this->assertTrue(isset($arr_info['encoding']) && $arr_info['encoding']=='int'); } public function testDumpRestore() { if (version_compare($this->version, "2.5.0", "lt")) { $this->markTestSkipped(); } $this->redis->del('foo'); $this->redis->del('bar'); $this->redis->set('foo', 'this-is-foo'); $this->redis->set('bar', 'this-is-bar'); $d_foo = $this->redis->dump('foo'); $d_bar = $this->redis->dump('bar'); $this->redis->del('foo'); $this->redis->del('bar'); // Assert returns from restore $this->assertTrue($this->redis->restore('foo', 0, $d_bar)); $this->assertTrue($this->redis->restore('bar', 0, $d_foo)); // Now check that the keys have switched $this->assertTrue($this->redis->get('foo') === 'this-is-bar'); $this->assertTrue($this->redis->get('bar') === 'this-is-foo'); $this->redis->del('foo'); $this->redis->del('bar'); } public function testGetLastError() { // We shouldn't have any errors now $this->assertTrue($this->redis->getLastError() === NULL); // Throw some invalid lua at redis $this->redis->eval("not-a-lua-script"); // Now we should have an error $evalError = $this->redis->getLastError(); $this->assertTrue(strlen($evalError) > 0); // test getLastError with a regular command $this->redis->set('x', 'a'); $this->assertFalse($this->redis->incr('x')); $incrError = $this->redis->getLastError(); $this->assertTrue($incrError !== $evalError); // error has changed $this->assertTrue(strlen($incrError) > 0); // clear error $this->redis->clearLastError(); $this->assertTrue($this->redis->getLastError() === NULL); } // Helper function to compare nested results -- from the php.net array_diff page, I believe private function array_diff_recursive($aArray1, $aArray2) { $aReturn = array(); foreach ($aArray1 as $mKey => $mValue) { if (array_key_exists($mKey, $aArray2)) { if (is_array($mValue)) { $aRecursiveDiff = $this->array_diff_recursive($mValue, $aArray2[$mKey]); if (count($aRecursiveDiff)) { $aReturn[$mKey] = $aRecursiveDiff; } } else { if ($mValue != $aArray2[$mKey]) { $aReturn[$mKey] = $mValue; } } } else { $aReturn[$mKey] = $mValue; } } return $aReturn; } public function testScript() { if (version_compare($this->version, "2.5.0", "lt")) { $this->markTestSkipped(); } // Flush any scripts we have $this->assertTrue($this->redis->script('flush')); // Silly scripts to test against $s1_src = 'return 1'; $s1_sha = sha1($s1_src); $s2_src = 'return 2'; $s2_sha = sha1($s2_src); $s3_src = 'return 3'; $s3_sha = sha1($s3_src); // None should exist $result = $this->redis->script('exists', $s1_sha, $s2_sha, $s3_sha); $this->assertTrue(is_array($result) && count($result) == 3); $this->assertTrue(is_array($result) && count(array_filter($result)) == 0); // Load them up $this->assertTrue($this->redis->script('load', $s1_src) == $s1_sha); $this->assertTrue($this->redis->script('load', $s2_src) == $s2_sha); $this->assertTrue($this->redis->script('load', $s3_src) == $s3_sha); // They should all exist $result = $this->redis->script('exists', $s1_sha, $s2_sha, $s3_sha); $this->assertTrue(is_array($result) && count(array_filter($result)) == 3); } public function testEval() { if (version_compare($this->version, "2.5.0", "lt")) { $this->markTestSkipped(); } // Basic single line response tests $this->assertTrue(1 == $this->redis->eval('return 1')); $this->assertTrue(1.55 == $this->redis->eval("return '1.55'")); $this->assertTrue("hello, world" == $this->redis->eval("return 'hello, world'")); /* * Keys to be incorporated into lua results */ // Make a list $this->redis->del('mylist'); $this->redis->rpush('mylist', 'a'); $this->redis->rpush('mylist', 'b'); $this->redis->rpush('mylist', 'c'); // Make a set $this->redis->del('myset'); $this->redis->sadd('myset', 'd'); $this->redis->sadd('myset', 'e'); $this->redis->sadd('myset', 'f'); // Basic keys $this->redis->set('key1', 'hello, world'); $this->redis->set('key2', 'hello again!'); // Use a script to return our list, and verify its response $list = $this->redis->eval("return redis.call('lrange', 'mylist', 0, -1)"); $this->assertTrue($list === Array('a','b','c')); // Use a script to return our set $set = $this->redis->eval("return redis.call('smembers', 'myset')"); $this->assertTrue($set == Array('d','e','f')); // Test an empty MULTI BULK response $this->redis->del('not-any-kind-of-list'); $empty_resp = $this->redis->eval("return redis.call('lrange', 'not-any-kind-of-list', 0, -1)"); $this->assertTrue(is_array($empty_resp) && empty($empty_resp)); // Now test a nested reply $nested_script = " return { 1,2,3, { redis.call('get', 'key1'), redis.call('get', 'key2'), redis.call('lrange', 'not-any-kind-of-list', 0, -1), { redis.call('smembers','myset'), redis.call('lrange', 'mylist', 0, -1) } } } "; $expected = Array( 1, 2, 3, Array( 'hello, world', 'hello again!', Array(), Array( Array('d','e','f'), Array('a','b','c') ) ) ); // Now run our script, and check our values against each other $eval_result = $this->redis->eval($nested_script); $this->assertTrue(is_array($eval_result) && count($this->array_diff_recursive($eval_result, $expected)) == 0); /* * Nested reply wihin a multi/pipeline block */ $num_scripts = 10; foreach(Array(Redis::PIPELINE, Redis::MULTI) as $mode) { $this->redis->multi($mode); for($i=0;$i<$num_scripts;$i++) { $this->redis->eval($nested_script); } $replies = $this->redis->exec(); foreach($replies as $reply) { $this->assertTrue(is_array($reply) && count($this->array_diff_recursive($reply, $expected)) == 0); } } /* * KEYS/ARGV */ $args_script = "return {KEYS[1],KEYS[2],KEYS[3],ARGV[1],ARGV[2],ARGV[3]}"; $args_args = Array('k1','k2','k3','v1','v2','v3'); $args_result = $this->redis->eval($args_script, $args_args, 3); $this->assertTrue($args_result === $args_args); // turn on key prefixing $this->redis->setOption(Redis::OPT_PREFIX, 'prefix:'); $args_result = $this->redis->eval($args_script, $args_args, 3); // Make sure our first three are prefixed for($i=0;$i<count($args_result);$i++) { if($i<3) { // Should be prefixed $this->assertTrue($args_result[$i] == 'prefix:' . $args_args[$i]); } else { // Should not be prefixed $this->assertTrue($args_result[$i] == $args_args[$i]); } } } public function testEvalSHA() { if (version_compare($this->version, "2.5.0", "lt")) { $this->markTestSkipped(); } // Flush any loaded scripts $this->redis->script('flush'); // Non existant script (but proper sha1), and a random (not) sha1 string $this->assertFalse($this->redis->evalsha(sha1(uniqid()))); $this->assertFalse($this->redis->evalsha('some-random-data')); // Load a script $cb = uniqid(); // To ensure the script is new $scr = "local cb='$cb' return 1"; $sha = sha1($scr); // Run it when it doesn't exist, run it with eval, and then run it with sha1 $this->assertTrue(false === $this->redis->evalsha($scr)); $this->assertTrue(1 === $this->redis->eval($scr)); $this->assertTrue(1 === $this->redis->evalsha($sha)); } public function testSerialize() { $vals = Array(1, 1.5, 'one', Array('here','is','an','array')); // Test with no serialization at all $this->assertTrue($this->redis->_serialize('test') === 'test'); $this->assertTrue($this->redis->_serialize(1) === '1'); $this->assertTrue($this->redis->_serialize(Array()) === 'Array'); $this->assertTrue($this->redis->_serialize(new stdClass) === 'Object'); $arr_serializers = Array(Redis::SERIALIZER_PHP); if(defined('Redis::SERIALIZER_IGBINARY')) { $arr_serializers[] = Redis::SERIALIZER_IGBINARY; } foreach($arr_serializers as $mode) { $arr_enc = Array(); $arr_dec = Array(); foreach($vals as $k => $v) { $enc = $this->redis->_serialize($v); $dec = $this->redis->_unserialize($enc); // They should be the same $this->assertTrue($enc == $dec); } } } public function testUnserialize() { $vals = Array( 1,1.5,'one',Array('this','is','an','array') ); $serializers = Array(Redis::SERIALIZER_PHP); if(defined('Redis::SERIALIZER_IGBINARY')) { $serializers[] = Redis::SERIALIZER_IGBINARY; } foreach($serializers as $mode) { $vals_enc = Array(); // Pass them through redis so they're serialized foreach($vals as $key => $val) { $this->redis->setOption(Redis::OPT_SERIALIZER, $mode); $key = "key" . ++$key; $this->redis->del($key); $this->redis->set($key, $val); // Clear serializer, get serialized value $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); $vals_enc[] = $this->redis->get($key); } // Run through our array comparing values for($i=0;$i<count($vals);$i++) { // reset serializer $this->redis->setOption(Redis::OPT_SERIALIZER, $mode); $this->assertTrue($vals[$i] == $this->redis->_unserialize($vals_enc[$i])); $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); } } } public function testPrefix() { // no prefix $this->redis->setOption(Redis::OPT_PREFIX, ''); $this->assertTrue('key' == $this->redis->_prefix('key')); // with a prefix $this->redis->setOption(Redis::OPT_PREFIX, 'some-prefix:'); $this->assertTrue('some-prefix:key' == $this->redis->_prefix('key')); // Clear prefix $this->redis->setOption(Redis::OPT_PREFIX, ''); } public function testReconnectSelect() { $key = 'reconnect-select'; $value = 'Has been set!'; $original_cfg = $this->redis->config('GET', 'timeout'); // Make sure the default DB doesn't have the key. $this->redis->select(0); $this->redis->delete($key); // Set the key on a different DB. $this->redis->select(5); $this->redis->set($key, $value); // Time out after 1 second. $this->redis->config('SET', 'timeout', '1'); // Wait for the timeout. With Redis 2.4, we have to wait up to 10 s // for the server to close the connection, regardless of the timeout // setting. sleep(11); // Make sure we're still using the same DB. $this->assertEquals($value, $this->redis->get($key)); // Revert the setting. $this->redis->config('SET', 'timeout', $original_cfg['timeout']); } public function testTime() { if (version_compare($this->version, "2.5.0", "lt")) { $this->markTestSkipped(); } $time_arr = $this->redis->time(); $this->assertTrue(is_array($time_arr) && count($time_arr) == 2 && strval(intval($time_arr[0])) === strval($time_arr[0]) && strval(intval($time_arr[1])) === strval($time_arr[1])); } public function testReadTimeoutOption() { $this->assertTrue(defined('Redis::OPT_READ_TIMEOUT')); $this->redis->setOption(Redis::OPT_READ_TIMEOUT, "12.3"); $this->assertEquals(12.3, $this->redis->getOption(Redis::OPT_READ_TIMEOUT)); } public function testIntrospection() { // Simple introspection tests $this->assertTrue($this->redis->getHost() === self::HOST); $this->assertTrue($this->redis->getPort() === self::PORT); $this->assertTrue($this->redis->getAuth() === self::AUTH); } /** * Scan and variants */ protected function get_keyspace_count($str_db) { $arr_info = $this->redis->info(); $arr_info = $arr_info[$str_db]; $arr_info = explode(',', $arr_info); $arr_info = explode('=', $arr_info[0]); return $arr_info[1]; } public function testScan() { if(version_compare($this->version, "2.8.0", "lt")) { $this->markTestSkipped(); return; } // Key count $i_key_count = $this->get_keyspace_count('db0'); // Have scan retry $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); // Scan them all $it = NULL; while($arr_keys = $this->redis->scan($it)) { $i_key_count -= count($arr_keys); } // Should have iterated all keys $this->assertEquals(0, $i_key_count); // Unique keys, for pattern matching $str_uniq = uniqid() . '-' . uniqid(); for($i=0;$i<10;$i++) { $this->redis->set($str_uniq . "::$i", "bar::$i"); } // Scan just these keys using a pattern match $it = NULL; while($arr_keys = $this->redis->scan($it, "*$str_uniq*")) { $i -= count($arr_keys); } $this->assertEquals(0, $i); } public function testHScan() { if(version_compare($this->version, "2.8.0", "lt")) { $this->markTestSkipped(); return; } // Never get empty sets $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); $this->redis->del('hash'); $i_foo_mems = 0; for($i=0;$i<100;$i++) { if($i>3) { $this->redis->hset('hash', "member:$i", "value:$i"); } else { $this->redis->hset('hash', "foomember:$i", "value:$i"); $i_foo_mems++; } } // Scan all of them $it = NULL; while($arr_keys = $this->redis->hscan('hash', $it)) { $i -= count($arr_keys); } $this->assertEquals(0, $i); // Scan just *foomem* (should be 4) $it = NULL; while($arr_keys = $this->redis->hscan('hash', $it, '*foomember*')) { $i_foo_mems -= count($arr_keys); foreach($arr_keys as $str_mem => $str_val) { $this->assertTrue(strpos($str_mem, 'member')!==FALSE); $this->assertTrue(strpos($str_val, 'value')!==FALSE); } } $this->assertEquals(0, $i_foo_mems); } public function testSScan() { if(version_compare($this->version, "2.8.0", "lt")) { $this->markTestSkipped(); return; } $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); $this->redis->del('set'); for($i=0;$i<100;$i++) { $this->redis->sadd('set', "member:$i"); } // Scan all of them $it = NULL; while($arr_keys = $this->redis->sscan('set', $it)) { $i -= count($arr_keys); foreach($arr_keys as $str_mem) { $this->assertTrue(strpos($str_mem,'member')!==FALSE); } } $this->assertEquals(0, $i); // Scan just ones with zero in them (0, 10, 20, 30, 40, 50, 60, 70, 80, 90) $it = NULL; $i_w_zero = 0; while($arr_keys = $this->redis->sscan('set', $it, '*0*')) { $i_w_zero += count($arr_keys); } $this->assertEquals(10, $i_w_zero); } public function testZScan() { if(version_compare($this->version, "2.8.0", "lt")) { $this->markTestSkipped(); return; } $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); $this->redis->del('zset'); $i_tot_score = 0; $i_p_score = 0; $i_p_count = 0; for($i=0;$i<2000;$i++) { if($i<10) { $this->redis->zadd('zset', $i, "pmem:$i"); $i_p_score += $i; $i_p_count += 1; } else { $this->redis->zadd('zset', $i, "mem:$i"); } $i_tot_score += $i; } // Scan them all $it = NULL; while($arr_keys = $this->redis->zscan('zset', $it)) { foreach($arr_keys as $str_mem => $f_score) { $i_tot_score -= $f_score; $i--; } } $this->assertEquals(0, $i); $this->assertEquals(0.0, $i_tot_score); // Just scan "pmem" members $it = NULL; $i_p_score_old = $i_p_score; $i_p_count_old = $i_p_count; while($arr_keys = $this->redis->zscan('zset', $it, "*pmem*")) { foreach($arr_keys as $str_mem => $f_score) { $i_p_score -= $f_score; $i_p_count -= 1; } } $this->assertEquals(0.0, $i_p_score); $this->assertEquals(0, $i_p_count); // Turn off retrying and we should get some empty results $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_NORETRY); $i_skips = 0; $i_p_score = $i_p_score_old; $i_p_count = $i_p_count_old; $it = NULL; while(($arr_keys = $this->redis->zscan('zset', $it, "*pmem*")) !== FALSE) { if(count($arr_keys) == 0) $i_skips++; foreach($arr_keys as $str_mem => $f_score) { $i_p_score -= $f_score; $i_p_count -= 1; } } // We should still get all the keys, just with several empty results $this->assertTrue($i_skips > 0); $this->assertEquals(0.0, $i_p_score); $this->assertEquals(0, $i_p_count); } // // HyperLogLog (PF) commands // protected function createPFKey($str_key, $i_count) { $arr_mems = Array(); for($i=0;$i<$i_count;$i++) { $arr_mems[] = uniqid() . '-' . $i; } // Estimation by Redis $this->redis->pfadd($str_key, $i_count); } public function testPFCommands() { // Isn't available until 2.8.9 if(version_compare($this->version, "2.8.9", "lt")) { $this->markTestSkipped(); return; } $str_uniq = uniqid(); $arr_mems = Array(); for($i=0;$i<1000;$i++) { if($i%2 == 0) { $arr_mems[] = $str_uniq . '-' . $i; } else { $arr_mems[] = $i; } } // How many keys to create $i_keys = 10; // Iterate prefixing/serialization options foreach(Array(Redis::SERIALIZER_NONE, Redis::SERIALIZER_PHP) as $str_ser) { foreach(Array('', 'hl-key-prefix:') as $str_prefix) { $arr_keys = Array(); // Now add for each key for($i=0;$i<$i_keys;$i++) { $str_key = "key:$i"; $arr_keys[] = $str_key; // Clean up this key $this->redis->del($str_key); // Add to our cardinality set, and confirm we got a valid response $this->assertTrue($this->redis->pfadd($str_key, $arr_mems)); // Grab estimated cardinality $i_card = $this->redis->pfcount($str_key); $this->assertTrue(is_int($i_card)); // Count should be close $this->assertLess(abs($i_card-count($arr_mems)), count($arr_mems) * .1); // The PFCOUNT on this key should be the same as the above returned response $this->assertEquals($this->redis->pfcount($str_key), $i_card); } // Make sure we can pass an array of keys into pfCount $i_card = $this->redis->pfcount($arr_keys); $this->assertTrue(is_int($i_card)); // Clean up merge key $this->redis->del('pf-merge-key'); // Merge the counters $this->assertTrue($this->redis->pfmerge('pf-merge-key', $arr_keys)); // Validate our merged count $i_redis_card = $this->redis->pfcount('pf-merge-key'); // Merged cardinality should still be roughly 1000 $this->assertLess(abs($i_redis_card-count($arr_mems)), count($arr_mems) * .1); // Clean up merge key $this->redis->del('pf-merge-key'); } } } } $str_test = isset($argv[1]) ? $argv[1] : NULL; exit(TestSuite::run("Redis_Test", $str_test)); ?> # PhpRedis The phpredis extension provides an API for communicating with the [Redis](http://redis.io/) key-value store. It is released under the [PHP License, version 3.01](http://www.php.net/license/3_01.txt). This code has been developed and maintained by Owlient from November 2009 to March 2011. You can send comments, patches, questions [here on github](https://github.com/nicolasff/phpredis/issues), to n.favrefelix@gmail.com ([@yowgi](http://twitter.com/yowgi)), or to michael.grunder@gmail.com ([@grumi78](http://twitter.com/grumi78)). # Table of contents ----- 1. [Installing/Configuring](#installingconfiguring) * [Installation](#installation) * [Installation on OSX](#installation-on-osx) * [Building on Windows](#building-on-windows) * [PHP Session handler](#php-session-handler) * [Distributed Redis Array](#distributed-redis-array) 1. [Classes and methods](#classes-and-methods) * [Usage](#usage) * [Connection](#connection) * [Server](#server) * [Keys and strings](#keys-and-strings) * [Hashes](#hashes) * [Lists](#lists) * [Sets](#sets) * [Sorted sets](#sorted-sets) * [Pub/sub](#pubsub) * [Transactions](#transactions) * [Scripting](#scripting) * [Introspection](#introspection) ----- # Installing/Configuring ----- Everything you should need to install PhpRedis on your system. ## Installation ~~~ phpize ./configure [--enable-redis-igbinary] make && make install ~~~ If you would like phpredis to serialize your data using the igbinary library, run configure with `--enable-redis-igbinary`. `make install` copies `redis.so` to an appropriate location, but you still need to enable the module in the PHP config file. To do so, either edit your php.ini or add a redis.ini file in `/etc/php5/conf.d` with the following contents: `extension=redis.so`. You can generate a debian package for PHP5, accessible from Apache 2 by running `./mkdeb-apache2.sh` or with `dpkg-buildpackage` or `svn-buildpackage`. This extension exports a single class, [Redis](#class-redis) (and [RedisException](#class-redisexception) used in case of errors). Check out https://github.com/ukko/phpredis-phpdoc for a PHP stub that you can use in your IDE for code completion. ## Installation on OSX If the install fails on OSX, type the following commands in your shell before trying again: ~~~ MACOSX_DEPLOYMENT_TARGET=10.6 CFLAGS="-arch i386 -arch x86_64 -g -Os -pipe -no-cpp-precomp" CCFLAGS="-arch i386 -arch x86_64 -g -Os -pipe" CXXFLAGS="-arch i386 -arch x86_64 -g -Os -pipe" LDFLAGS="-arch i386 -arch x86_64 -bind_at_load" export CFLAGS CXXFLAGS LDFLAGS CCFLAGS MACOSX_DEPLOYMENT_TARGET ~~~ If that still fails and you are running Zend Server CE, try this right before "make": `./configure CFLAGS="-arch i386"`. Taken from [Compiling phpredis on Zend Server CE/OSX ](http://www.tumblr.com/tagged/phpredis). See also: [Install Redis & PHP Extension PHPRedis with Macports](http://www.lecloud.net/post/3378834922/install-redis-php-extension-phpredis-with-macports). You can install install it using Homebrew: - [Get homebrew-php](https://github.com/josegonzalez/homebrew-php) - `brew install php55-redis` (or php53-redis, php54-redis) ## PHP Session handler phpredis can be used to store PHP sessions. To do this, configure `session.save_handler` and `session.save_path` in your php.ini to tell phpredis where to store the sessions: ~~~ session.save_handler = redis session.save_path = "tcp://host1:6379?weight=1, tcp://host2:6379?weight=2&timeout=2.5, tcp://host3:6379?weight=2" ~~~ `session.save_path` can have a simple `host:port` format too, but you need to provide the `tcp://` scheme if you want to use the parameters. The following parameters are available: * weight (integer): the weight of a host is used in comparison with the others in order to customize the session distribution on several hosts. If host A has twice the weight of host B, it will get twice the amount of sessions. In the example, *host1* stores 20% of all the sessions (1/(1+2+2)) while *host2* and *host3* each store 40% (2/1+2+2). The target host is determined once and for all at the start of the session, and doesn't change. The default weight is 1. * timeout (float): the connection timeout to a redis host, expressed in seconds. If the host is unreachable in that amount of time, the session storage will be unavailable for the client. The default timeout is very high (86400 seconds). * persistent (integer, should be 1 or 0): defines if a persistent connection should be used. **(experimental setting)** * prefix (string, defaults to "PHPREDIS_SESSION:"): used as a prefix to the Redis key in which the session is stored. The key is composed of the prefix followed by the session ID. * auth (string, empty by default): used to authenticate with the server prior to sending commands. * database (integer): selects a different database. Sessions have a lifetime expressed in seconds and stored in the INI variable "session.gc_maxlifetime". You can change it with [`ini_set()`](http://php.net/ini_set). The session handler requires a version of Redis with the `SETEX` command (at least 2.0). phpredis can also connect to a unix domain socket: `session.save_path = "unix:///var/run/redis/redis.sock?persistent=1&weight=1&database=0`. ## Building on Windows See [instructions from @char101](https://github.com/nicolasff/phpredis/issues/213#issuecomment-11361242) on how to build phpredis on Windows. ## Distributed Redis Array See [dedicated page](https://github.com/nicolasff/phpredis/blob/master/arrays.markdown#readme). # Classes and methods ----- ## Usage 1. [Class Redis](#class-redis) 1. [Class RedisException](#class-redisexception) 1. [Predefined constants](#predefined-constants) ### Class Redis ----- _**Description**_: Creates a Redis client ##### *Example* ~~~ $redis = new Redis(); ~~~ ### Class RedisException ----- phpredis throws a [RedisException](#class-redisexception) object if it can't reach the Redis server. That can happen in case of connectivity issues, if the Redis service is down, or if the redis host is overloaded. In any other problematic case that does not involve an unreachable server (such as a key not existing, an invalid command, etc), phpredis will return `FALSE`. ### Predefined constants ----- _**Description**_: Available Redis Constants Redis data types, as returned by [type](#type) ~~~ Redis::REDIS_STRING - String Redis::REDIS_SET - Set Redis::REDIS_LIST - List Redis::REDIS_ZSET - Sorted set Redis::REDIS_HASH - Hash Redis::REDIS_NOT_FOUND - Not found / other ~~~ @TODO: OPT_SERIALIZER, AFTER, BEFORE,... ## Connection 1. [connect, open](#connect-open) - Connect to a server 1. [pconnect, popen](#pconnect-popen) - Connect to a server (persistent) 1. [auth](#auth) - Authenticate to the server 1. [select](#select) - Change the selected database for the current connection 1. [close](#close) - Close the connection 1. [setOption](#setoption) - Set client option 1. [getOption](#getoption) - Get client option 1. [ping](#ping) - Ping the server 1. [echo](#echo) - Echo the given string ### connect, open ----- _**Description**_: Connects to a Redis instance. ##### *Parameters* *host*: string. can be a host, or the path to a unix domain socket *port*: int, optional *timeout*: float, value in seconds (optional, default is 0 meaning unlimited) *reserved*: should be NULL if retry_interval is specified *retry_interval*: int, value in milliseconds (optional) ##### *Return value* *BOOL*: `TRUE` on success, `FALSE` on error. ##### *Example* ~~~ $redis->connect('127.0.0.1', 6379); $redis->connect('127.0.0.1'); // port 6379 by default $redis->connect('127.0.0.1', 6379, 2.5); // 2.5 sec timeout. $redis->connect('/tmp/redis.sock'); // unix domain socket. $redis->connect('127.0.0.1', 6379, 1, NULL, 100); // 1 sec timeout, 100ms delay between reconnection attempts. ~~~ ### pconnect, popen ----- _**Description**_: Connects to a Redis instance or reuse a connection already established with `pconnect`/`popen`. The connection will not be closed on `close` or end of request until the php process ends. So be patient on to many open FD's (specially on redis server side) when using persistent connections on many servers connecting to one redis server. Also more than one persistent connection can be made identified by either host + port + timeout or host + persistent_id or unix socket + timeout. This feature is not available in threaded versions. `pconnect` and `popen` then working like their non persistent equivalents. ##### *Parameters* *host*: string. can be a host, or the path to a unix domain socket *port*: int, optional *timeout*: float, value in seconds (optional, default is 0 meaning unlimited) *persistent_id*: string. identity for the requested persistent connection *retry_interval*: int, value in milliseconds (optional) ##### *Return value* *BOOL*: `TRUE` on success, `FALSE` on error. ##### *Example* ~~~ $redis->pconnect('127.0.0.1', 6379); $redis->pconnect('127.0.0.1'); // port 6379 by default - same connection like before. $redis->pconnect('127.0.0.1', 6379, 2.5); // 2.5 sec timeout and would be another connection than the two before. $redis->pconnect('127.0.0.1', 6379, 2.5, 'x'); // x is sent as persistent_id and would be another connection the the three before. $redis->pconnect('/tmp/redis.sock'); // unix domain socket - would be another connection than the four before. ~~~ ### auth ----- _**Description**_: Authenticate the connection using a password. *Warning*: The password is sent in plain-text over the network. ##### *Parameters* *STRING*: password ##### *Return value* *BOOL*: `TRUE` if the connection is authenticated, `FALSE` otherwise. ##### *Example* ~~~ $redis->auth('foobared'); ~~~ ### select ----- _**Description**_: Change the selected database for the current connection. ##### *Parameters* *INTEGER*: dbindex, the database number to switch to. ##### *Return value* `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* See method for example: [move](#move) ### close ----- _**Description**_: Disconnects from the Redis instance, except when `pconnect` is used. ### setOption ----- _**Description**_: Set client option. ##### *Parameters* *parameter name* *parameter value* ##### *Return value* *BOOL*: `TRUE` on success, `FALSE` on error. ##### *Example* ~~~ $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); // don't serialize data $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); // use built-in serialize/unserialize $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY); // use igBinary serialize/unserialize $redis->setOption(Redis::OPT_PREFIX, 'myAppName:'); // use custom prefix on all keys /* Options for the SCAN family of commands, indicating whether to abstract empty results from the user. If set to SCAN_NORETRY (the default), phpredis will just issue one SCAN command at a time, sometimes returning an empty array of results. If set to SCAN_RETRY, phpredis will retry the scan command until keys come back OR Redis returns an iterator of zero */ $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_NORETRY); $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); ~~~ ### getOption ----- _**Description**_: Get client option. ##### *Parameters* *parameter name* ##### *Return value* Parameter value. ##### *Example* ~~~ $redis->getOption(Redis::OPT_SERIALIZER); // return Redis::SERIALIZER_NONE, Redis::SERIALIZER_PHP, or Redis::SERIALIZER_IGBINARY. ~~~ ### ping ----- _**Description**_: Check the current connection status ##### *Parameters* (none) ##### *Return value* *STRING*: `+PONG` on success. Throws a [RedisException](#class-redisexception) object on connectivity error, as described above. ### echo ----- _**Description**_: Sends a string to Redis, which replies with the same string ##### *Parameters* *STRING*: The message to send. ##### *Return value* *STRING*: the same message. ## Server 1. [bgrewriteaof](#bgrewriteaof) - Asynchronously rewrite the append-only file 1. [bgsave](#bgsave) - Asynchronously save the dataset to disk (in background) 1. [config](#config) - Get or Set the Redis server configuration parameters 1. [dbSize](#dbsize) - Return the number of keys in selected database 1. [flushAll](#flushall) - Remove all keys from all databases 1. [flushDB](#flushdb) - Remove all keys from the current database 1. [info](#info) - Get information and statistics about the server 1. [lastSave](#lastsave) - Get the timestamp of the last disk save 1. [resetStat](#resetstat) - Reset the stats returned by [info](#info) method. 1. [save](#save) - Synchronously save the dataset to disk (wait to complete) 1. [slaveof](#slaveof) - Make the server a slave of another instance, or promote it to master 1. [time](#time) - Return the current server time 1. [slowlog](#slowlog) - Access the Redis slowlog entries ### bgrewriteaof ----- _**Description**_: Start the background rewrite of AOF (Append-Only File) ##### *Parameters* None. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->bgrewriteaof(); ~~~ ### bgsave ----- _**Description**_: Asynchronously save the dataset to disk (in background) ##### *Parameters* None. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. If a save is already running, this command will fail and return `FALSE`. ##### *Example* ~~~ $redis->bgSave(); ~~~ ### config ----- _**Description**_: Get or Set the Redis server configuration parameters. ##### *Parameters* *operation* (string) either `GET` or `SET` *key* string for `SET`, glob-pattern for `GET`. See http://redis.io/commands/config-get for examples. *value* optional string (only for `SET`) ##### *Return value* *Associative array* for `GET`, key -> value *bool* for `SET` ##### *Examples* ~~~ $redis->config("GET", "*max-*-entries*"); $redis->config("SET", "dir", "/var/run/redis/dumps/"); ~~~ ### dbSize ----- _**Description**_: Return the number of keys in selected database. ##### *Parameters* None. ##### *Return value* *INTEGER*: DB size, in number of keys. ##### *Example* ~~~ $count = $redis->dbSize(); echo "Redis has $count keys\n"; ~~~ ### flushAll ----- _**Description**_: Remove all keys from all databases. ##### *Parameters* None. ##### *Return value* *BOOL*: Always `TRUE`. ##### *Example* ~~~ $redis->flushAll(); ~~~ ### flushDB ----- _**Description**_: Remove all keys from the current database. ##### *Parameters* None. ##### *Return value* *BOOL*: Always `TRUE`. ##### *Example* ~~~ $redis->flushDB(); ~~~ ### info ----- _**Description**_: Get information and statistics about the server Returns an associative array that provides information about the server. Passing no arguments to INFO will call the standard REDIS INFO command, which returns information such as the following: * redis_version * arch_bits * uptime_in_seconds * uptime_in_days * connected_clients * connected_slaves * used_memory * changes_since_last_save * bgsave_in_progress * last_save_time * total_connections_received * total_commands_processed * role You can pass a variety of options to INFO ([per the Redis documentation](http://redis.io/commands/info)), which will modify what is returned. ##### *Parameters* *option*: The option to provide redis (e.g. "COMMANDSTATS", "CPU") ##### *Example* ~~~ $redis->info(); /* standard redis INFO command */ $redis->info("COMMANDSTATS"); /* Information on the commands that have been run (>=2.6 only) $redis->info("CPU"); /* just CPU information from Redis INFO */ ~~~ ### lastSave ----- _**Description**_: Returns the timestamp of the last disk save. ##### *Parameters* None. ##### *Return value* *INT*: timestamp. ##### *Example* ~~~ $redis->lastSave(); ~~~ ### resetStat ----- _**Description**_: Reset the stats returned by [info](#info) method. These are the counters that are reset: * Keyspace hits * Keyspace misses * Number of commands processed * Number of connections received * Number of expired keys ##### *Parameters* None. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->resetStat(); ~~~ ### save ----- _**Description**_: Synchronously save the dataset to disk (wait to complete) ##### *Parameters* None. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. If a save is already running, this command will fail and return `FALSE`. ##### *Example* ~~~ $redis->save(); ~~~ ### slaveof ----- _**Description**_: Changes the slave status ##### *Parameters* Either host (string) and port (int), or no parameter to stop being a slave. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->slaveof('10.0.1.7', 6379); /* ... */ $redis->slaveof(); ~~~ ### time ----- _**Description**_: Return the current server time. ##### *Parameters* (none) ##### *Return value* If successfull, the time will come back as an associative array with element zero being the unix timestamp, and element one being microseconds. ##### *Examples* ~~~ $redis->time(); ~~~ ### slowlog ----- _**Description**_: Access the Redis slowlog ##### *Parameters* *Operation* (string): This can be either `GET`, `LEN`, or `RESET` *Length* (integer), optional: If executing a `SLOWLOG GET` command, you can pass an optional length. ##### ##### *Return value* The return value of SLOWLOG will depend on which operation was performed. SLOWLOG GET: Array of slowlog entries, as provided by Redis SLOGLOG LEN: Integer, the length of the slowlog SLOWLOG RESET: Boolean, depending on success ##### ##### *Examples* ~~~ // Get ten slowlog entries $redis->slowlog('get', 10); // Get the default number of slowlog entries $redis->slowlog('get'); // Reset our slowlog $redis->slowlog('reset'); // Retrieve slowlog length $redis->slowlog('len'); ~~~ ## Keys and Strings ### Strings ----- * [append](#append) - Append a value to a key * [bitcount](#bitcount) - Count set bits in a string * [bitop](#bitop) - Perform bitwise operations between strings * [decr, decrBy](#decr-decrby) - Decrement the value of a key * [get](#get) - Get the value of a key * [getBit](#getbit) - Returns the bit value at offset in the string value stored at key * [getRange](#getrange) - Get a substring of the string stored at a key * [getSet](#getset) - Set the string value of a key and return its old value * [incr, incrBy](#incr-incrby) - Increment the value of a key * [incrByFloat](#incrbyfloat) - Increment the float value of a key by the given amount * [mGet, getMultiple](#mget-getmultiple) - Get the values of all the given keys * [mSet, mSetNX](#mset-msetnx) - Set multiple keys to multiple values * [set](#set) - Set the string value of a key * [setBit](#setbit) - Sets or clears the bit at offset in the string value stored at key * [setex, psetex](#setex-psetex) - Set the value and expiration of a key * [setnx](#setnx) - Set the value of a key, only if the key does not exist * [setRange](#setrange) - Overwrite part of a string at key starting at the specified offset * [strlen](#strlen) - Get the length of the value stored in a key ### Keys ----- * [del, delete](#del-delete) - Delete a key * [dump](#dump) - Return a serialized version of the value stored at the specified key. * [exists](#exists) - Determine if a key exists * [expire, setTimeout, pexpire](#expire-settimeout-pexpire) - Set a key's time to live in seconds * [expireAt, pexpireAt](#expireat-pexpireat) - Set the expiration for a key as a UNIX timestamp * [keys, getKeys](#keys-getkeys) - Find all keys matching the given pattern * [scan](#scan) - Scan for keys in the keyspace (Redis >= 2.8.0) * [migrate](#migrate) - Atomically transfer a key from a Redis instance to another one * [move](#move) - Move a key to another database * [object](#object) - Inspect the internals of Redis objects * [persist](#persist) - Remove the expiration from a key * [randomKey](#randomkey) - Return a random key from the keyspace * [rename, renameKey](#rename-renamekey) - Rename a key * [renameNx](#renamenx) - Rename a key, only if the new key does not exist * [type](#type) - Determine the type stored at key * [sort](#sort) - Sort the elements in a list, set or sorted set * [ttl, pttl](#ttl-pttl) - Get the time to live for a key * [restore](#restore) - Create a key using the provided serialized value, previously obtained with [dump](#dump). ----- ### get ----- _**Description**_: Get the value related to the specified key ##### *Parameters* *key* ##### *Return value* *String* or *Bool*: If key didn't exist, `FALSE` is returned. Otherwise, the value related to this key is returned. ##### *Examples* ~~~ $redis->get('key'); ~~~ ### set ----- _**Description**_: Set the string value in argument as value of the key. If you're using Redis >= 2.6.12, you can pass extended options as explained below ##### *Parameters* *Key* *Value* *Timeout or Options Array* (optional). If you pass an integer, phpredis will redirect to SETEX, and will try to use Redis >= 2.6.12 extended options if you pass an array with valid values ##### *Return value* *Bool* `TRUE` if the command is successful. ##### *Examples* ~~~ // Simple key -> value set $redis->set('key', 'value'); // Will redirect, and actually make an SETEX call $redis->set('key','value', 10); // Will set the key, if it doesn't exist, with a ttl of 10 seconds $redis->set('key', 'value', Array('nx', 'ex'=>10)); // Will set a key, if it does exist, with a ttl of 1000 miliseconds $redis->set('key', 'value', Array('xx', 'px'=>1000)); ~~~ ### setex, psetex ----- _**Description**_: Set the string value in argument as value of the key, with a time to live. PSETEX uses a TTL in milliseconds. ##### *Parameters* *Key* *TTL* *Value* ##### *Return value* *Bool* `TRUE` if the command is successful. ##### *Examples* ~~~ $redis->setex('key', 3600, 'value'); // sets key → value, with 1h TTL. $redis->psetex('key', 100, 'value'); // sets key → value, with 0.1 sec TTL. ~~~ ### setnx ----- _**Description**_: Set the string value in argument as value of the key if the key doesn't already exist in the database. ##### *Parameters* *key* *value* ##### *Return value* *Bool* `TRUE` in case of success, `FALSE` in case of failure. ##### *Examples* ~~~ $redis->setnx('key', 'value'); /* return TRUE */ $redis->setnx('key', 'value'); /* return FALSE */ ~~~ ### del, delete ----- _**Description**_: Remove specified keys. ##### *Parameters* An array of keys, or an undefined number of parameters, each a key: *key1* *key2* *key3* ... *keyN* ##### *Return value* *Long* Number of keys deleted. ##### *Examples* ~~~ $redis->set('key1', 'val1'); $redis->set('key2', 'val2'); $redis->set('key3', 'val3'); $redis->set('key4', 'val4'); $redis->delete('key1', 'key2'); /* return 2 */ $redis->delete(array('key3', 'key4')); /* return 2 */ ~~~ ### exists ----- _**Description**_: Verify if the specified key exists. ##### *Parameters* *key* ##### *Return value* *BOOL*: If the key exists, return `TRUE`, otherwise return `FALSE`. ##### *Examples* ~~~ $redis->set('key', 'value'); $redis->exists('key'); /* TRUE */ $redis->exists('NonExistingKey'); /* FALSE */ ~~~ ### incr, incrBy ----- _**Description**_: Increment the number stored at key by one. If the second argument is filled, it will be used as the integer value of the increment. ##### *Parameters* *key* *value*: value that will be added to key (only for incrBy) ##### *Return value* *INT* the new value ##### *Examples* ~~~ $redis->incr('key1'); /* key1 didn't exists, set to 0 before the increment */ /* and now has the value 1 */ $redis->incr('key1'); /* 2 */ $redis->incr('key1'); /* 3 */ $redis->incr('key1'); /* 4 */ $redis->incrBy('key1', 10); /* 14 */ ~~~ ### incrByFloat ----- _**Description**_: Increment the key with floating point precision. ##### *Parameters* *key* *value*: (float) value that will be added to the key ##### *Return value* *FLOAT* the new value ##### *Examples* ~~~ $redis->incrByFloat('key1', 1.5); /* key1 didn't exist, so it will now be 1.5 */ $redis->incrByFloat('key1', 1.5); /* 3 */ $redis->incrByFloat('key1', -1.5); /* 1.5 */ $redis->incrByFloat('key1', 2.5); /* 4 */ ~~~ ### decr, decrBy ----- _**Description**_: Decrement the number stored at key by one. If the second argument is filled, it will be used as the integer value of the decrement. ##### *Parameters* *key* *value*: value that will be substracted to key (only for decrBy) ##### *Return value* *INT* the new value ##### *Examples* ~~~ $redis->decr('key1'); /* key1 didn't exists, set to 0 before the increment */ /* and now has the value -1 */ $redis->decr('key1'); /* -2 */ $redis->decr('key1'); /* -3 */ $redis->decrBy('key1', 10); /* -13 */ ~~~ ### mGet, getMultiple ----- _**Description**_: Get the values of all the specified keys. If one or more keys dont exist, the array will contain `FALSE` at the position of the key. ##### *Parameters* *Array*: Array containing the list of the keys ##### *Return value* *Array*: Array containing the values related to keys in argument ##### *Examples* ~~~ $redis->set('key1', 'value1'); $redis->set('key2', 'value2'); $redis->set('key3', 'value3'); $redis->mGet(array('key1', 'key2', 'key3')); /* array('value1', 'value2', 'value3'); $redis->mGet(array('key0', 'key1', 'key5')); /* array(`FALSE`, 'value2', `FALSE`); ~~~ ### getSet ----- _**Description**_: Sets a value and returns the previous entry at that key. ##### *Parameters* *Key*: key *STRING*: value ##### *Return value* A string, the previous value located at this key. ##### *Example* ~~~ $redis->set('x', '42'); $exValue = $redis->getSet('x', 'lol'); // return '42', replaces x by 'lol' $newValue = $redis->get('x')' // return 'lol' ~~~ ### randomKey ----- _**Description**_: Returns a random key. ##### *Parameters* None. ##### *Return value* *STRING*: an existing key in redis. ##### *Example* ~~~ $key = $redis->randomKey(); $surprise = $redis->get($key); // who knows what's in there. ~~~ ### move ----- _**Description**_: Moves a key to a different database. ##### *Parameters* *Key*: key, the key to move. *INTEGER*: dbindex, the database number to move the key to. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->select(0); // switch to DB 0 $redis->set('x', '42'); // write 42 to x $redis->move('x', 1); // move to DB 1 $redis->select(1); // switch to DB 1 $redis->get('x'); // will return 42 ~~~ ### rename, renameKey ----- _**Description**_: Renames a key. ##### *Parameters* *STRING*: srckey, the key to rename. *STRING*: dstkey, the new name for the key. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->set('x', '42'); $redis->rename('x', 'y'); $redis->get('y'); // → 42 $redis->get('x'); // → `FALSE` ~~~ ### renameNx ----- _**Description**_: Same as rename, but will not replace a key if the destination already exists. This is the same behaviour as setNx. ### expire, setTimeout, pexpire ----- _**Description**_: Sets an expiration date (a timeout) on an item. pexpire requires a TTL in milliseconds. ##### *Parameters* *Key*: key. The key that will disappear. *Integer*: ttl. The key's remaining Time To Live, in seconds. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->set('x', '42'); $redis->setTimeout('x', 3); // x will disappear in 3 seconds. sleep(5); // wait 5 seconds $redis->get('x'); // will return `FALSE`, as 'x' has expired. ~~~ ### expireAt, pexpireAt ----- _**Description**_: Sets an expiration date (a timestamp) on an item. pexpireAt requires a timestamp in milliseconds. ##### *Parameters* *Key*: key. The key that will disappear. *Integer*: Unix timestamp. The key's date of death, in seconds from Epoch time. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->set('x', '42'); $now = time(NULL); // current timestamp $redis->expireAt('x', $now + 3); // x will disappear in 3 seconds. sleep(5); // wait 5 seconds $redis->get('x'); // will return `FALSE`, as 'x' has expired. ~~~ ### keys, getKeys ----- _**Description**_: Returns the keys that match a certain pattern. ##### *Parameters* *STRING*: pattern, using '*' as a wildcard. ##### *Return value* *Array of STRING*: The keys that match a certain pattern. ##### *Example* ~~~ $allKeys = $redis->keys('*'); // all keys will match this. $keyWithUserPrefix = $redis->keys('user*'); ~~~ ### scan ----- _**Description**_: Scan the keyspace for keys ##### *Parameters* *LONG (reference)*: Iterator, initialized to NULL *STRING, Optional*: Pattern to match *LONG, Optional*: Count of keys per iteration (only a suggestion to Redis) ##### *Return value* *Array, boolean*: This function will return an array of keys or FALSE if there are no more keys ##### *Example* ~~~ $it = NULL; /* Initialize our iterator to NULL */ $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); /* retry when we get no keys back */ while($arr_keys = $redis->scan($it)) { foreach($arr_keys as $str_key) { echo "Here is a key: $str_key\n"; } echo "No more keys to scan!\n"; } ~~~ ### object ----- _**Description**_: Describes the object pointed to by a key. ##### *Parameters* The information to retrieve (string) and the key (string). Info can be one of the following: * "encoding" * "refcount" * "idletime" ##### *Return value* *STRING* for "encoding", *LONG* for "refcount" and "idletime", `FALSE` if the key doesn't exist. ##### *Example* ~~~ $redis->object("encoding", "l"); // → ziplist $redis->object("refcount", "l"); // → 1 $redis->object("idletime", "l"); // → 400 (in seconds, with a precision of 10 seconds). ~~~ ### type ----- _**Description**_: Returns the type of data pointed by a given key. ##### *Parameters* *Key*: key ##### *Return value* Depending on the type of the data pointed by the key, this method will return the following value: string: Redis::REDIS_STRING set: Redis::REDIS_SET list: Redis::REDIS_LIST zset: Redis::REDIS_ZSET hash: Redis::REDIS_HASH other: Redis::REDIS_NOT_FOUND ##### *Example* ~~~ $redis->type('key'); ~~~ ### append ----- _**Description**_: Append specified string to the string stored in specified key. ##### *Parameters* *Key* *Value* ##### *Return value* *INTEGER*: Size of the value after the append ##### *Example* ~~~ $redis->set('key', 'value1'); $redis->append('key', 'value2'); /* 12 */ $redis->get('key'); /* 'value1value2' */ ~~~ ### getRange ----- _**Description**_: Return a substring of a larger string *Note*: substr also supported but deprecated in redis. ##### *Parameters* *key* *start* *end* ##### *Return value* *STRING*: the substring ##### *Example* ~~~ $redis->set('key', 'string value'); $redis->getRange('key', 0, 5); /* 'string' */ $redis->getRange('key', -5, -1); /* 'value' */ ~~~ ### setRange ----- _**Description**_: Changes a substring of a larger string. ##### *Parameters* *key* *offset* *value* ##### *Return value* *STRING*: the length of the string after it was modified. ##### *Example* ~~~ $redis->set('key', 'Hello world'); $redis->setRange('key', 6, "redis"); /* returns 11 */ $redis->get('key'); /* "Hello redis" */ ~~~ ### strlen ----- _**Description**_: Get the length of a string value. ##### *Parameters* *key* ##### *Return value* *INTEGER* ##### *Example* ~~~ $redis->set('key', 'value'); $redis->strlen('key'); /* 5 */ ~~~ ### getBit ----- _**Description**_: Return a single bit out of a larger string ##### *Parameters* *key* *offset* ##### *Return value* *LONG*: the bit value (0 or 1) ##### *Example* ~~~ $redis->set('key', "\x7f"); // this is 0111 1111 $redis->getBit('key', 0); /* 0 */ $redis->getBit('key', 1); /* 1 */ ~~~ ### setBit ----- _**Description**_: Changes a single bit of a string. ##### *Parameters* *key* *offset* *value*: bool or int (1 or 0) ##### *Return value* *LONG*: 0 or 1, the value of the bit before it was set. ##### *Example* ~~~ $redis->set('key', "*"); // ord("*") = 42 = 0x2f = "0010 1010" $redis->setBit('key', 5, 1); /* returns 0 */ $redis->setBit('key', 7, 1); /* returns 0 */ $redis->get('key'); /* chr(0x2f) = "/" = b("0010 1111") */ ~~~ ### bitop ----- _**Description**_: Bitwise operation on multiple keys. ##### *Parameters* *operation*: either "AND", "OR", "NOT", "XOR" *ret_key*: return key *key1* *key2...* ##### *Return value* *LONG*: The size of the string stored in the destination key. ### bitcount ----- _**Description**_: Count bits in a string. ##### *Parameters* *key* ##### *Return value* *LONG*: The number of bits set to 1 in the value behind the input key. ### sort ----- _**Description**_: Sort the elements in a list, set or sorted set. ##### *Parameters* *Key*: key *Options*: array(key => value, ...) - optional, with the following keys and values: ~~~ 'by' => 'some_pattern_*', 'limit' => array(0, 1), 'get' => 'some_other_pattern_*' or an array of patterns, 'sort' => 'asc' or 'desc', 'alpha' => TRUE, 'store' => 'external-key' ~~~ ##### *Return value* An array of values, or a number corresponding to the number of elements stored if that was used. ##### *Example* ~~~ $redis->delete('s'); $redis->sadd('s', 5); $redis->sadd('s', 4); $redis->sadd('s', 2); $redis->sadd('s', 1); $redis->sadd('s', 3); var_dump($redis->sort('s')); // 1,2,3,4,5 var_dump($redis->sort('s', array('sort' => 'desc'))); // 5,4,3,2,1 var_dump($redis->sort('s', array('sort' => 'desc', 'store' => 'out'))); // (int)5 ~~~ ### ttl, pttl ----- _**Description**_: Returns the time to live left for a given key in seconds (ttl), or milliseconds (pttl). ##### *Parameters* *Key*: key ##### *Return value* *LONG*: The time to live in seconds. If the key has no ttl, `-1` will be returned, and `-2` if the key doesn't exist. ##### *Example* ~~~ $redis->ttl('key'); ~~~ ### persist ----- _**Description**_: Remove the expiration timer from a key. ##### *Parameters* *Key*: key ##### *Return value* *BOOL*: `TRUE` if a timeout was removed, `FALSE` if the key didn’t exist or didn’t have an expiration timer. ##### *Example* ~~~ $redis->persist('key'); ~~~ ### mset, msetnx ----- _**Description**_: Sets multiple key-value pairs in one atomic command. MSETNX only returns TRUE if all the keys were set (see SETNX). ##### *Parameters* *Pairs*: array(key => value, ...) ##### *Return value* *Bool* `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->mset(array('key0' => 'value0', 'key1' => 'value1')); var_dump($redis->get('key0')); var_dump($redis->get('key1')); ~~~ Output: ~~~ string(6) "value0" string(6) "value1" ~~~ ### dump ----- _**Description**_: Dump a key out of a redis database, the value of which can later be passed into redis using the RESTORE command. The data that comes out of DUMP is a binary representation of the key as Redis stores it. ##### *Parameters* *key* string ##### *Return value* The Redis encoded value of the key, or FALSE if the key doesn't exist ##### *Examples* ~~~ $redis->set('foo', 'bar'); $val = $redis->dump('foo'); // $val will be the Redis encoded key value ~~~ ### restore ----- _**Description**_: Restore a key from the result of a DUMP operation. ##### *Parameters* *key* string. The key name *ttl* integer. How long the key should live (if zero, no expire will be set on the key) *value* string (binary). The Redis encoded key value (from DUMP) ##### *Examples* ~~~ $redis->set('foo', 'bar'); $val = $redis->dump('foo'); $redis->restore('bar', 0, $val); // The key 'bar', will now be equal to the key 'foo' ~~~ ### migrate ----- _**Description**_: Migrates a key to a different Redis instance. ##### *Parameters* *host* string. The destination host *port* integer. The TCP port to connect to. *key* string. The key to migrate. *destination-db* integer. The target DB. *timeout* integer. The maximum amount of time given to this transfer. *copy* boolean, optional. Should we send the COPY flag to redis *replace* boolean, optional. Should we send the REPLACE flag to redis ##### *Examples* ~~~ $redis->migrate('backup', 6379, 'foo', 0, 3600); $redis->migrate('backup', 6379, 'foo', 0, 3600, true, true); /* copy and replace */ $redis->migrate('backup', 6379, 'foo', 0, 3600, false, true); /* just REPLACE flag */ ~~~ ## Hashes * [hDel](#hdel) - Delete one or more hash fields * [hExists](#hexists) - Determine if a hash field exists * [hGet](#hget) - Get the value of a hash field * [hGetAll](#hgetall) - Get all the fields and values in a hash * [hIncrBy](#hincrby) - Increment the integer value of a hash field by the given number * [hIncrByFloat](#hincrbyfloat) - Increment the float value of a hash field by the given amount * [hKeys](#hkeys) - Get all the fields in a hash * [hLen](#hlen) - Get the number of fields in a hash * [hMGet](#hmget) - Get the values of all the given hash fields * [hMSet](#hmset) - Set multiple hash fields to multiple values * [hSet](#hset) - Set the string value of a hash field * [hSetNx](#hsetnx) - Set the value of a hash field, only if the field does not exist * [hVals](#hvals) - Get all the values in a hash * [hScan](#hscan) - Scan a hash key for members ### hSet ----- _**Description**_: Adds a value to the hash stored at key. If this value is already in the hash, `FALSE` is returned. ##### *Parameters* *key* *hashKey* *value* ##### *Return value* *LONG* `1` if value didn't exist and was added successfully, `0` if the value was already present and was replaced, `FALSE` if there was an error. ##### *Example* ~~~ $redis->delete('h') $redis->hSet('h', 'key1', 'hello'); /* 1, 'key1' => 'hello' in the hash at "h" */ $redis->hGet('h', 'key1'); /* returns "hello" */ $redis->hSet('h', 'key1', 'plop'); /* 0, value was replaced. */ $redis->hGet('h', 'key1'); /* returns "plop" */ ~~~ ### hSetNx ----- _**Description**_: Adds a value to the hash stored at key only if this field isn't already in the hash. ##### *Return value* *BOOL* `TRUE` if the field was set, `FALSE` if it was already present. ##### *Example* ~~~ $redis->delete('h') $redis->hSetNx('h', 'key1', 'hello'); /* TRUE, 'key1' => 'hello' in the hash at "h" */ $redis->hSetNx('h', 'key1', 'world'); /* FALSE, 'key1' => 'hello' in the hash at "h". No change since the field wasn't replaced. */ ~~~ ### hGet ----- _**Description**_: Gets a value from the hash stored at key. If the hash table doesn't exist, or the key doesn't exist, `FALSE` is returned. ##### *Parameters* *key* *hashKey* ##### *Return value* *STRING* The value, if the command executed successfully *BOOL* `FALSE` in case of failure ### hLen ----- _**Description**_: Returns the length of a hash, in number of items ##### *Parameters* *key* ##### *Return value* *LONG* the number of items in a hash, `FALSE` if the key doesn't exist or isn't a hash. ##### *Example* ~~~ $redis->delete('h') $redis->hSet('h', 'key1', 'hello'); $redis->hSet('h', 'key2', 'plop'); $redis->hLen('h'); /* returns 2 */ ~~~ ### hDel ----- _**Description**_: Removes a value from the hash stored at key. If the hash table doesn't exist, or the key doesn't exist, `FALSE` is returned. ##### *Parameters* *key* *hashKey* ##### *Return value* *BOOL* `TRUE` in case of success, `FALSE` in case of failure ### hKeys ----- _**Description**_: Returns the keys in a hash, as an array of strings. ##### *Parameters* *Key*: key ##### *Return value* An array of elements, the keys of the hash. This works like PHP's array_keys(). ##### *Example* ~~~ $redis->delete('h'); $redis->hSet('h', 'a', 'x'); $redis->hSet('h', 'b', 'y'); $redis->hSet('h', 'c', 'z'); $redis->hSet('h', 'd', 't'); var_dump($redis->hKeys('h')); ~~~ Output: ~~~ array(4) { [0]=> string(1) "a" [1]=> string(1) "b" [2]=> string(1) "c" [3]=> string(1) "d" } ~~~ The order is random and corresponds to redis' own internal representation of the set structure. ### hVals ----- _**Description**_: Returns the values in a hash, as an array of strings. ##### *Parameters* *Key*: key ##### *Return value* An array of elements, the values of the hash. This works like PHP's array_values(). ##### *Example* ~~~ $redis->delete('h'); $redis->hSet('h', 'a', 'x'); $redis->hSet('h', 'b', 'y'); $redis->hSet('h', 'c', 'z'); $redis->hSet('h', 'd', 't'); var_dump($redis->hVals('h')); ~~~ Output: ~~~ array(4) { [0]=> string(1) "x" [1]=> string(1) "y" [2]=> string(1) "z" [3]=> string(1) "t" } ~~~ The order is random and corresponds to redis' own internal representation of the set structure. ### hGetAll ----- _**Description**_: Returns the whole hash, as an array of strings indexed by strings. ##### *Parameters* *Key*: key ##### *Return value* An array of elements, the contents of the hash. ##### *Example* ~~~ $redis->delete('h'); $redis->hSet('h', 'a', 'x'); $redis->hSet('h', 'b', 'y'); $redis->hSet('h', 'c', 'z'); $redis->hSet('h', 'd', 't'); var_dump($redis->hGetAll('h')); ~~~ Output: ~~~ array(4) { ["a"]=> string(1) "x" ["b"]=> string(1) "y" ["c"]=> string(1) "z" ["d"]=> string(1) "t" } ~~~ The order is random and corresponds to redis' own internal representation of the set structure. ### hExists ----- _**Description**_: Verify if the specified member exists in a key. ##### *Parameters* *key* *memberKey* ##### *Return value* *BOOL*: If the member exists in the hash table, return `TRUE`, otherwise return `FALSE`. ##### *Examples* ~~~ $redis->hSet('h', 'a', 'x'); $redis->hExists('h', 'a'); /* TRUE */ $redis->hExists('h', 'NonExistingKey'); /* FALSE */ ~~~ ### hIncrBy ----- _**Description**_: Increments the value of a member from a hash by a given amount. ##### *Parameters* *key* *member* *value*: (integer) value that will be added to the member's value ##### *Return value* *LONG* the new value ##### *Examples* ~~~ $redis->delete('h'); $redis->hIncrBy('h', 'x', 2); /* returns 2: h[x] = 2 now. */ $redis->hIncrBy('h', 'x', 1); /* h[x] ← 2 + 1. Returns 3 */ ~~~ ### hIncrByFloat ----- _**Description**_: Increments the value of a hash member by the provided float value ##### *Parameters* *key* *member* *value*: (float) value that will be added to the member's value ##### *Return value* *FLOAT* the new value ##### *Examples* ~~~ $redis->delete('h'); $redis->hIncrByFloat('h','x', 1.5); /* returns 1.5: h[x] = 1.5 now */ $redis->hIncrByFLoat('h', 'x', 1.5); /* returns 3.0: h[x] = 3.0 now */ $redis->hIncrByFloat('h', 'x', -3.0); /* returns 0.0: h[x] = 0.0 now */ ~~~ ### hMSet ----- _**Description**_: Fills in a whole hash. Non-string values are converted to string, using the standard `(string)` cast. NULL values are stored as empty strings. ##### *Parameters* *key* *members*: key → value array ##### *Return value* *BOOL* ##### *Examples* ~~~ $redis->delete('user:1'); $redis->hMset('user:1', array('name' => 'Joe', 'salary' => 2000)); $redis->hIncrBy('user:1', 'salary', 100); // Joe earns 100 more now. ~~~ ### hMGet ----- _**Description**_: Retrieve the values associated to the specified fields in the hash. ##### *Parameters* *key* *memberKeys* Array ##### *Return value* *Array* An array of elements, the values of the specified fields in the hash, with the hash keys as array keys. ##### *Examples* ~~~ $redis->delete('h'); $redis->hSet('h', 'field1', 'value1'); $redis->hSet('h', 'field2', 'value2'); $redis->hmGet('h', array('field1', 'field2')); /* returns array('field1' => 'value1', 'field2' => 'value2') */ ~~~ ### hScan ----- _**Description**_: Scan a HASH value for members, with an optional pattern and count ##### *Parameters* *key*: String *iterator*: Long (reference) *pattern*: Optional pattern to match against *count*: How many keys to return in a go (only a sugestion to Redis) ##### *Return value* *Array* An array of members that match our pattern ##### *Examples* ~~~ $it = NULL; /* Don't ever return an empty array until we're done iterating */ $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); while($arr_keys = $redis->hscan('hash', $it)) { foreach($arr_keys as $str_field => $str_value) { echo "$str_field => $str_value\n"; /* Print the hash member and value */ } } ~~~ ## Lists * [blPop, brPop](#blpop-brpop) - Remove and get the first/last element in a list * [brpoplpush](#brpoplpush) - Pop a value from a list, push it to another list and return it * [lIndex, lGet](#lindex-lget) - Get an element from a list by its index * [lInsert](#linsert) - Insert an element before or after another element in a list * [lLen, lSize](#llen-lsize) - Get the length/size of a list * [lPop](#lpop) - Remove and get the first element in a list * [lPush](#lpush) - Prepend one or multiple values to a list * [lPushx](#lpushx) - Prepend a value to a list, only if the list exists * [lRange, lGetRange](#lrange-lgetrange) - Get a range of elements from a list * [lRem, lRemove](#lrem-lremove) - Remove elements from a list * [lSet](#lset) - Set the value of an element in a list by its index * [lTrim, listTrim](#ltrim-listtrim) - Trim a list to the specified range * [rPop](#rpop) - Remove and get the last element in a list * [rpoplpush](#rpoplpush) - Remove the last element in a list, append it to another list and return it (redis >= 1.1) * [rPush](#rpush) - Append one or multiple values to a list * [rPushx](#rpushx) - Append a value to a list, only if the list exists ### blPop, brPop ----- _**Description**_: Is a blocking lPop(rPop) primitive. If at least one of the lists contains at least one element, the element will be popped from the head of the list and returned to the caller. Il all the list identified by the keys passed in arguments are empty, blPop will block during the specified timeout until an element is pushed to one of those lists. This element will be popped. ##### *Parameters* *ARRAY* Array containing the keys of the lists *INTEGER* Timeout Or *STRING* Key1 *STRING* Key2 *STRING* Key3 ... *STRING* Keyn *INTEGER* Timeout ##### *Return value* *ARRAY* array('listName', 'element') ##### *Example* ~~~ /* Non blocking feature */ $redis->lPush('key1', 'A'); $redis->delete('key2'); $redis->blPop('key1', 'key2', 10); /* array('key1', 'A') */ /* OR */ $redis->blPop(array('key1', 'key2'), 10); /* array('key1', 'A') */ $redis->brPop('key1', 'key2', 10); /* array('key1', 'A') */ /* OR */ $redis->brPop(array('key1', 'key2'), 10); /* array('key1', 'A') */ /* Blocking feature */ /* process 1 */ $redis->delete('key1'); $redis->blPop('key1', 10); /* blocking for 10 seconds */ /* process 2 */ $redis->lPush('key1', 'A'); /* process 1 */ /* array('key1', 'A') is returned*/ ~~~ ### brpoplpush ----- _**Description**_: A blocking version of `rpoplpush`, with an integral timeout in the third parameter. ##### *Parameters* *Key*: srckey *Key*: dstkey *Long*: timeout ##### *Return value* *STRING* The element that was moved in case of success, `FALSE` in case of timeout. ### lIndex, lGet ----- _**Description**_: Return the specified element of the list stored at the specified key. 0 the first element, 1 the second ... -1 the last element, -2 the penultimate ... Return `FALSE` in case of a bad index or a key that doesn't point to a list. ##### *Parameters* *key* *index* ##### *Return value* *String* the element at this index *Bool* `FALSE` if the key identifies a non-string data type, or no value corresponds to this index in the list `Key`. ##### *Example* ~~~ $redis->rPush('key1', 'A'); $redis->rPush('key1', 'B'); $redis->rPush('key1', 'C'); /* key1 => [ 'A', 'B', 'C' ] */ $redis->lGet('key1', 0); /* 'A' */ $redis->lGet('key1', -1); /* 'C' */ $redis->lGet('key1', 10); /* `FALSE` */ ~~~ ### lInsert ----- _**Description**_: Insert value in the list before or after the pivot value. The parameter options specify the position of the insert (before or after). If the list didn't exists, or the pivot didn't exists, the value is not inserted. ##### *Parameters* *key* *position* Redis::BEFORE | Redis::AFTER *pivot* *value* ##### *Return value* The number of the elements in the list, -1 if the pivot didn't exists. ##### *Example* ~~~ $redis->delete('key1'); $redis->lInsert('key1', Redis::AFTER, 'A', 'X'); /* 0 */ $redis->lPush('key1', 'A'); $redis->lPush('key1', 'B'); $redis->lPush('key1', 'C'); $redis->lInsert('key1', Redis::BEFORE, 'C', 'X'); /* 4 */ $redis->lRange('key1', 0, -1); /* array('A', 'B', 'X', 'C') */ $redis->lInsert('key1', Redis::AFTER, 'C', 'Y'); /* 5 */ $redis->lRange('key1', 0, -1); /* array('A', 'B', 'X', 'C', 'Y') */ $redis->lInsert('key1', Redis::AFTER, 'W', 'value'); /* -1 */ ~~~ ### lPop ----- _**Description**_: Return and remove the first element of the list. ##### *Parameters* *key* ##### *Return value* *STRING* if command executed successfully *BOOL* `FALSE` in case of failure (empty list) ##### *Example* ~~~ $redis->rPush('key1', 'A'); $redis->rPush('key1', 'B'); $redis->rPush('key1', 'C'); /* key1 => [ 'A', 'B', 'C' ] */ $redis->lPop('key1'); /* key1 => [ 'B', 'C' ] */ ~~~ ### lPush ----- _**Description**_: Adds the string value to the head (left) of the list. Creates the list if the key didn't exist. If the key exists and is not a list, `FALSE` is returned. ##### *Parameters* *key* *value* String, value to push in key ##### *Return value* *LONG* The new length of the list in case of success, `FALSE` in case of Failure. ##### *Examples* ~~~ $redis->delete('key1'); $redis->lPush('key1', 'C'); // returns 1 $redis->lPush('key1', 'B'); // returns 2 $redis->lPush('key1', 'A'); // returns 3 /* key1 now points to the following list: [ 'A', 'B', 'C' ] */ ~~~ ### lPushx ----- _**Description**_: Adds the string value to the head (left) of the list if the list exists. ##### *Parameters* *key* *value* String, value to push in key ##### *Return value* *LONG* The new length of the list in case of success, `FALSE` in case of Failure. ##### *Examples* ~~~ $redis->delete('key1'); $redis->lPushx('key1', 'A'); // returns 0 $redis->lPush('key1', 'A'); // returns 1 $redis->lPushx('key1', 'B'); // returns 2 $redis->lPushx('key1', 'C'); // returns 3 /* key1 now points to the following list: [ 'A', 'B', 'C' ] */ ~~~ ### lRange, lGetRange ----- _**Description**_: Returns the specified elements of the list stored at the specified key in the range [start, end]. start and stop are interpretated as indices: 0 the first element, 1 the second ... -1 the last element, -2 the penultimate ... ##### *Parameters* *key* *start* *end* ##### *Return value* *Array* containing the values in specified range. ##### *Example* ~~~ $redis->rPush('key1', 'A'); $redis->rPush('key1', 'B'); $redis->rPush('key1', 'C'); $redis->lRange('key1', 0, -1); /* array('A', 'B', 'C') */ ~~~ ### lRem, lRemove ----- _**Description**_: Removes the first `count` occurences of the value element from the list. If count is zero, all the matching elements are removed. If count is negative, elements are removed from tail to head. **Note**: The argument order is not the same as in the Redis documentation. This difference is kept for compatibility reasons. ##### *Parameters* *key* *value* *count* ##### *Return value* *LONG* the number of elements to remove *BOOL* `FALSE` if the value identified by key is not a list. ##### *Example* ~~~ $redis->lPush('key1', 'A'); $redis->lPush('key1', 'B'); $redis->lPush('key1', 'C'); $redis->lPush('key1', 'A'); $redis->lPush('key1', 'A'); $redis->lRange('key1', 0, -1); /* array('A', 'A', 'C', 'B', 'A') */ $redis->lRem('key1', 'A', 2); /* 2 */ $redis->lRange('key1', 0, -1); /* array('C', 'B', 'A') */ ~~~ ### lSet ----- _**Description**_: Set the list at index with the new value. ##### *Parameters* *key* *index* *value* ##### *Return value* *BOOL* `TRUE` if the new value is setted. `FALSE` if the index is out of range, or data type identified by key is not a list. ##### *Example* ~~~ $redis->rPush('key1', 'A'); $redis->rPush('key1', 'B'); $redis->rPush('key1', 'C'); /* key1 => [ 'A', 'B', 'C' ] */ $redis->lGet('key1', 0); /* 'A' */ $redis->lSet('key1', 0, 'X'); $redis->lGet('key1', 0); /* 'X' */ ~~~ ### lTrim, listTrim ----- _**Description**_: Trims an existing list so that it will contain only a specified range of elements. ##### *Parameters* *key* *start* *stop* ##### *Return value* *Array* *Bool* return `FALSE` if the key identify a non-list value. ##### *Example* ~~~ $redis->rPush('key1', 'A'); $redis->rPush('key1', 'B'); $redis->rPush('key1', 'C'); $redis->lRange('key1', 0, -1); /* array('A', 'B', 'C') */ $redis->lTrim('key1', 0, 1); $redis->lRange('key1', 0, -1); /* array('A', 'B') */ ~~~ ### rPop ----- _**Description**_: Returns and removes the last element of the list. ##### *Parameters* *key* ##### *Return value* *STRING* if command executed successfully *BOOL* `FALSE` in case of failure (empty list) ##### *Example* ~~~ $redis->rPush('key1', 'A'); $redis->rPush('key1', 'B'); $redis->rPush('key1', 'C'); /* key1 => [ 'A', 'B', 'C' ] */ $redis->rPop('key1'); /* key1 => [ 'A', 'B' ] */ ~~~ ### rpoplpush ----- _**Description**_: Pops a value from the tail of a list, and pushes it to the front of another list. Also return this value. (redis >= 1.1) ##### *Parameters* *Key*: srckey *Key*: dstkey ##### *Return value* *STRING* The element that was moved in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->delete('x', 'y'); $redis->lPush('x', 'abc'); $redis->lPush('x', 'def'); $redis->lPush('y', '123'); $redis->lPush('y', '456'); // move the last of x to the front of y. var_dump($redis->rpoplpush('x', 'y')); var_dump($redis->lRange('x', 0, -1)); var_dump($redis->lRange('y', 0, -1)); ~~~ Output: ~~~ string(3) "abc" array(1) { [0]=> string(3) "def" } array(3) { [0]=> string(3) "abc" [1]=> string(3) "456" [2]=> string(3) "123" } ~~~ ### rPush ----- _**Description**_: Adds the string value to the tail (right) of the list. Creates the list if the key didn't exist. If the key exists and is not a list, `FALSE` is returned. ##### *Parameters* *key* *value* String, value to push in key ##### *Return value* *LONG* The new length of the list in case of success, `FALSE` in case of Failure. ##### *Examples* ~~~ $redis->delete('key1'); $redis->rPush('key1', 'A'); // returns 1 $redis->rPush('key1', 'B'); // returns 2 $redis->rPush('key1', 'C'); // returns 3 /* key1 now points to the following list: [ 'A', 'B', 'C' ] */ ~~~ ### rPushx ----- _**Description**_: Adds the string value to the tail (right) of the list if the ist exists. `FALSE` in case of Failure. ##### *Parameters* *key* *value* String, value to push in key ##### *Return value* *LONG* The new length of the list in case of success, `FALSE` in case of Failure. ##### *Examples* ~~~ $redis->delete('key1'); $redis->rPushx('key1', 'A'); // returns 0 $redis->rPush('key1', 'A'); // returns 1 $redis->rPushx('key1', 'B'); // returns 2 $redis->rPushx('key1', 'C'); // returns 3 /* key1 now points to the following list: [ 'A', 'B', 'C' ] */ ~~~ ### lLen, lSize ----- _**Description**_: Returns the size of a list identified by Key. If the list didn't exist or is empty, the command returns 0. If the data type identified by Key is not a list, the command return `FALSE`. ##### *Parameters* *Key* ##### *Return value* *LONG* The size of the list identified by Key exists. *BOOL* `FALSE` if the data type identified by Key is not list ##### *Example* ~~~ $redis->rPush('key1', 'A'); $redis->rPush('key1', 'B'); $redis->rPush('key1', 'C'); /* key1 => [ 'A', 'B', 'C' ] */ $redis->lSize('key1');/* 3 */ $redis->rPop('key1'); $redis->lSize('key1');/* 2 */ ~~~ ## Sets * [sAdd](#sadd) - Add one or more members to a set * [sCard, sSize](#scard-ssize) - Get the number of members in a set * [sDiff](#sdiff) - Subtract multiple sets * [sDiffStore](#sdiffstore) - Subtract multiple sets and store the resulting set in a key * [sInter](#sinter) - Intersect multiple sets * [sInterStore](#sinterstore) - Intersect multiple sets and store the resulting set in a key * [sIsMember, sContains](#sismember-scontains) - Determine if a given value is a member of a set * [sMembers, sGetMembers](#smembers-sgetmembers) - Get all the members in a set * [sMove](#smove) - Move a member from one set to another * [sPop](#spop) - Remove and return a random member from a set * [sRandMember](#srandmember) - Get one or multiple random members from a set * [sRem, sRemove](#srem-sremove) - Remove one or more members from a set * [sUnion](#sunion) - Add multiple sets * [sUnionStore](#sunionstore) - Add multiple sets and store the resulting set in a key * [sScan](#sscan) - Scan a set for members ### sAdd ----- _**Description**_: Adds a value to the set value stored at key. If this value is already in the set, `FALSE` is returned. ##### *Parameters* *key* *value* ##### *Return value* *LONG* the number of elements added to the set. ##### *Example* ~~~ $redis->sAdd('key1' , 'member1'); /* 1, 'key1' => {'member1'} */ $redis->sAdd('key1' , 'member2', 'member3'); /* 2, 'key1' => {'member1', 'member2', 'member3'}*/ $redis->sAdd('key1' , 'member2'); /* 0, 'key1' => {'member1', 'member2', 'member3'}*/ ~~~ ### sCard, sSize ----- _**Description**_: Returns the cardinality of the set identified by key. ##### *Parameters* *key* ##### *Return value* *LONG* the cardinality of the set identified by key, 0 if the set doesn't exist. ##### *Example* ~~~ $redis->sAdd('key1' , 'member1'); $redis->sAdd('key1' , 'member2'); $redis->sAdd('key1' , 'member3'); /* 'key1' => {'member1', 'member2', 'member3'}*/ $redis->sCard('key1'); /* 3 */ $redis->sCard('keyX'); /* 0 */ ~~~ ### sDiff ----- _**Description**_: Performs the difference between N sets and returns it. ##### *Parameters* *Keys*: key1, key2, ... , keyN: Any number of keys corresponding to sets in redis. ##### *Return value* *Array of strings*: The difference of the first set will all the others. ##### *Example* ~~~ $redis->delete('s0', 's1', 's2'); $redis->sAdd('s0', '1'); $redis->sAdd('s0', '2'); $redis->sAdd('s0', '3'); $redis->sAdd('s0', '4'); $redis->sAdd('s1', '1'); $redis->sAdd('s2', '3'); var_dump($redis->sDiff('s0', 's1', 's2')); ~~~ Return value: all elements of s0 that are neither in s1 nor in s2. ~~~ array(2) { [0]=> string(1) "4" [1]=> string(1) "2" } ~~~ ### sDiffStore ----- _**Description**_: Performs the same action as sDiff, but stores the result in the first key ##### *Parameters* *Key*: dstkey, the key to store the diff into. *Keys*: key1, key2, ... , keyN: Any number of keys corresponding to sets in redis ##### *Return value* *INTEGER*: The cardinality of the resulting set, or `FALSE` in case of a missing key. ##### *Example* ~~~ $redis->delete('s0', 's1', 's2'); $redis->sAdd('s0', '1'); $redis->sAdd('s0', '2'); $redis->sAdd('s0', '3'); $redis->sAdd('s0', '4'); $redis->sAdd('s1', '1'); $redis->sAdd('s2', '3'); var_dump($redis->sDiffStore('dst', 's0', 's1', 's2')); var_dump($redis->sMembers('dst')); ~~~ Return value: the number of elements of s0 that are neither in s1 nor in s2. ~~~ int(2) array(2) { [0]=> string(1) "4" [1]=> string(1) "2" } ~~~ ### sInter ----- _**Description**_: Returns the members of a set resulting from the intersection of all the sets held at the specified keys. If just a single key is specified, then this command produces the members of this set. If one of the keys is missing, `FALSE` is returned. ##### *Parameters* key1, key2, keyN: keys identifying the different sets on which we will apply the intersection. ##### *Return value* Array, contain the result of the intersection between those keys. If the intersection beteen the different sets is empty, the return value will be empty array. ##### *Examples* ~~~ $redis->sAdd('key1', 'val1'); $redis->sAdd('key1', 'val2'); $redis->sAdd('key1', 'val3'); $redis->sAdd('key1', 'val4'); $redis->sAdd('key2', 'val3'); $redis->sAdd('key2', 'val4'); $redis->sAdd('key3', 'val3'); $redis->sAdd('key3', 'val4'); var_dump($redis->sInter('key1', 'key2', 'key3')); ~~~ Output: ~~~ array(2) { [0]=> string(4) "val4" [1]=> string(4) "val3" } ~~~ ### sInterStore ----- _**Description**_: Performs a sInter command and stores the result in a new set. ##### *Parameters* *Key*: dstkey, the key to store the diff into. *Keys*: key1, key2... keyN. key1..keyN are intersected as in sInter. ##### *Return value* *INTEGER*: The cardinality of the resulting set, or `FALSE` in case of a missing key. ##### *Example* ~~~ $redis->sAdd('key1', 'val1'); $redis->sAdd('key1', 'val2'); $redis->sAdd('key1', 'val3'); $redis->sAdd('key1', 'val4'); $redis->sAdd('key2', 'val3'); $redis->sAdd('key2', 'val4'); $redis->sAdd('key3', 'val3'); $redis->sAdd('key3', 'val4'); var_dump($redis->sInterStore('output', 'key1', 'key2', 'key3')); var_dump($redis->sMembers('output')); ~~~ Output: ~~~ int(2) array(2) { [0]=> string(4) "val4" [1]=> string(4) "val3" } ~~~ ### sIsMember, sContains ----- _**Description**_: Checks if `value` is a member of the set stored at the key `key`. ##### *Parameters* *key* *value* ##### *Return value* *BOOL* `TRUE` if `value` is a member of the set at key `key`, `FALSE` otherwise. ##### *Example* ~~~ $redis->sAdd('key1' , 'member1'); $redis->sAdd('key1' , 'member2'); $redis->sAdd('key1' , 'member3'); /* 'key1' => {'member1', 'member2', 'member3'}*/ $redis->sIsMember('key1', 'member1'); /* TRUE */ $redis->sIsMember('key1', 'memberX'); /* FALSE */ ~~~ ### sMembers, sGetMembers ----- _**Description**_: Returns the contents of a set. ##### *Parameters* *Key*: key ##### *Return value* An array of elements, the contents of the set. ##### *Example* ~~~ $redis->delete('s'); $redis->sAdd('s', 'a'); $redis->sAdd('s', 'b'); $redis->sAdd('s', 'a'); $redis->sAdd('s', 'c'); var_dump($redis->sMembers('s')); ~~~ Output: ~~~ array(3) { [0]=> string(1) "c" [1]=> string(1) "a" [2]=> string(1) "b" } ~~~ The order is random and corresponds to redis' own internal representation of the set structure. ### sMove ----- _**Description**_: Moves the specified member from the set at srcKey to the set at dstKey. ##### *Parameters* *srcKey* *dstKey* *member* ##### *Return value* *BOOL* If the operation is successful, return `TRUE`. If the srcKey and/or dstKey didn't exist, and/or the member didn't exist in srcKey, `FALSE` is returned. ##### *Example* ~~~ $redis->sAdd('key1' , 'member11'); $redis->sAdd('key1' , 'member12'); $redis->sAdd('key1' , 'member13'); /* 'key1' => {'member11', 'member12', 'member13'}*/ $redis->sAdd('key2' , 'member21'); $redis->sAdd('key2' , 'member22'); /* 'key2' => {'member21', 'member22'}*/ $redis->sMove('key1', 'key2', 'member13'); /* 'key1' => {'member11', 'member12'} */ /* 'key2' => {'member21', 'member22', 'member13'} */ ~~~ ### sPop ----- _**Description**_: Removes and returns a random element from the set value at Key. ##### *Parameters* *key* ##### *Return value* *String* "popped" value *Bool* `FALSE` if set identified by key is empty or doesn't exist. ##### *Example* ~~~ $redis->sAdd('key1' , 'member1'); $redis->sAdd('key1' , 'member2'); $redis->sAdd('key1' , 'member3'); /* 'key1' => {'member3', 'member1', 'member2'}*/ $redis->sPop('key1'); /* 'member1', 'key1' => {'member3', 'member2'} */ $redis->sPop('key1'); /* 'member3', 'key1' => {'member2'} */ ~~~ ### sRandMember ----- _**Description**_: Returns a random element from the set value at Key, without removing it. ##### *Parameters* *key* *count* (Integer, optional) ##### *Return value* If no count is provided, a random *String* value from the set will be returned. If a count is provided, an array of values from the set will be returned. Read about the different ways to use the count here: [SRANDMEMBER](http://redis.io/commands/srandmember) *Bool* `FALSE` if set identified by key is empty or doesn't exist. ##### *Example* ~~~ $redis->sAdd('key1' , 'member1'); $redis->sAdd('key1' , 'member2'); $redis->sAdd('key1' , 'member3'); /* 'key1' => {'member3', 'member1', 'member2'}*/ // No count $redis->sRandMember('key1'); /* 'member1', 'key1' => {'member3', 'member1', 'member2'} */ $redis->sRandMember('key1'); /* 'member3', 'key1' => {'member3', 'member1', 'member2'} */ // With a count $redis->sRandMember('key1', 3); // Will return an array with all members from the set $redis->sRandMember('key1', 2); // Will an array with 2 members of the set $redis->sRandMember('key1', -100); // Will return an array of 100 elements, picked from our set (with dups) $redis->sRandMember('empty-set', 100); // Will return an empty array $redis->sRandMember('not-a-set', 100); // Will return FALSE ~~~ ### sRem, sRemove ----- _**Description**_: Removes the specified member from the set value stored at key. ##### *Parameters* *key* *member* ##### *Return value* *LONG* The number of elements removed from the set. ##### *Example* ~~~ $redis->sAdd('key1' , 'member1'); $redis->sAdd('key1' , 'member2'); $redis->sAdd('key1' , 'member3'); /* 'key1' => {'member1', 'member2', 'member3'}*/ $redis->sRem('key1', 'member2', 'member3'); /*return 2. 'key1' => {'member1'} */ ~~~ ### sUnion ----- _**Description**_: Performs the union between N sets and returns it. ##### *Parameters* *Keys*: key1, key2, ... , keyN: Any number of keys corresponding to sets in redis. ##### *Return value* *Array of strings*: The union of all these sets. ##### *Example* ~~~ $redis->delete('s0', 's1', 's2'); $redis->sAdd('s0', '1'); $redis->sAdd('s0', '2'); $redis->sAdd('s1', '3'); $redis->sAdd('s1', '1'); $redis->sAdd('s2', '3'); $redis->sAdd('s2', '4'); var_dump($redis->sUnion('s0', 's1', 's2')); ~~~ Return value: all elements that are either in s0 or in s1 or in s2. ~~~ array(4) { [0]=> string(1) "3" [1]=> string(1) "4" [2]=> string(1) "1" [3]=> string(1) "2" } ~~~ ### sUnionStore ----- _**Description**_: Performs the same action as sUnion, but stores the result in the first key ##### *Parameters* *Key*: dstkey, the key to store the diff into. *Keys*: key1, key2, ... , keyN: Any number of keys corresponding to sets in redis. ##### *Return value* *INTEGER*: The cardinality of the resulting set, or `FALSE` in case of a missing key. ##### *Example* ~~~ $redis->delete('s0', 's1', 's2'); $redis->sAdd('s0', '1'); $redis->sAdd('s0', '2'); $redis->sAdd('s1', '3'); $redis->sAdd('s1', '1'); $redis->sAdd('s2', '3'); $redis->sAdd('s2', '4'); var_dump($redis->sUnionStore('dst', 's0', 's1', 's2')); var_dump($redis->sMembers('dst')); ~~~ Return value: the number of elements that are either in s0 or in s1 or in s2. ~~~ int(4) array(4) { [0]=> string(1) "3" [1]=> string(1) "4" [2]=> string(1) "1" [3]=> string(1) "2" } ~~~ ### sScan ----- _**Description**_: Scan a set for members ##### *Parameters* *Key*: The set to search *iterator*: LONG (reference) to the iterator as we go *pattern*: String, optional pattern to match against *count*: How many members to return at a time (Redis might return a different amount) ##### *Return value* *Array, boolean*: PHPRedis will return an array of keys or FALSE when we're done iterating ##### *Example* ~~~ $it = NULL; $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); /* don't return empty results until we're done */ while($arr_mems = $redis->sscan('set', $it, "*pattern*")) { foreach($arr_mems as $str_mem) { echo "Member: $str_mem\n"; } } $it = NULL; $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_NORETRY); /* return after each iteration, even if empty */ while(($arr_mems = $redis->sscan('set', $it, "*pattern*"))!==FALSE) { if(count($arr_mems) > 0) { foreach($arr_mems as $str_mem) { echo "Member found: $str_mem\n"; } } else { echo "No members in this iteration, iterator value: $it\n"; } } ~~~ ## Sorted sets * [zAdd](#zadd) - Add one or more members to a sorted set or update its score if it already exists * [zCard, zSize](#zcard-zsize) - Get the number of members in a sorted set * [zCount](#zcount) - Count the members in a sorted set with scores within the given values * [zIncrBy](#zincrby) - Increment the score of a member in a sorted set * [zInter](#zinter) - Intersect multiple sorted sets and store the resulting sorted set in a new key * [zRange](#zrange) - Return a range of members in a sorted set, by index * [zRangeByScore, zRevRangeByScore](#zrangebyscore-zrevrangebyscore) - Return a range of members in a sorted set, by score * [zRangeByLex](#zrangebylex) - Return a lexigraphical range from members that share the same score * [zRank, zRevRank](#zrank-zrevrank) - Determine the index of a member in a sorted set * [zRem, zDelete](#zrem-zdelete) - Remove one or more members from a sorted set * [zRemRangeByRank, zDeleteRangeByRank](#zremrangebyrank-zdeleterangebyrank) - Remove all members in a sorted set within the given indexes * [zRemRangeByScore, zDeleteRangeByScore](#zremrangebyscore-zdeleterangebyscore) - Remove all members in a sorted set within the given scores * [zRevRange](#zrevrange) - Return a range of members in a sorted set, by index, with scores ordered from high to low * [zScore](#zscore) - Get the score associated with the given member in a sorted set * [zUnion](#zunion) - Add multiple sorted sets and store the resulting sorted set in a new key * [zScan](#zscan) - Scan a sorted set for members ### zAdd ----- _**Description**_: Add one or more members to a sorted set or update its score if it already exists ##### *Parameters* *key* *score* : double *value*: string ##### *Return value* *Long* 1 if the element is added. 0 otherwise. ##### *Example* ~~~ $redis->zAdd('key', 1, 'val1'); $redis->zAdd('key', 0, 'val0'); $redis->zAdd('key', 5, 'val5'); $redis->zRange('key', 0, -1); // array(val0, val1, val5) ~~~ ### zCard, zSize ----- _**Description**_: Returns the cardinality of an ordered set. ##### *Parameters* *key* ##### *Return value* *Long*, the set's cardinality ##### *Example* ~~~ $redis->zAdd('key', 0, 'val0'); $redis->zAdd('key', 2, 'val2'); $redis->zAdd('key', 10, 'val10'); $redis->zSize('key'); /* 3 */ ~~~ ### zCount ----- _**Description**_: Returns the *number* of elements of the sorted set stored at the specified key which have scores in the range [start,end]. Adding a parenthesis before `start` or `end` excludes it from the range. +inf and -inf are also valid limits. ##### *Parameters* *key* *start*: string *end*: string ##### *Return value* *LONG* the size of a corresponding zRangeByScore. ##### *Example* ~~~ $redis->zAdd('key', 0, 'val0'); $redis->zAdd('key', 2, 'val2'); $redis->zAdd('key', 10, 'val10'); $redis->zCount('key', 0, 3); /* 2, corresponding to array('val0', 'val2') */ ~~~ ### zIncrBy ----- _**Description**_: Increments the score of a member from a sorted set by a given amount. ##### *Parameters* *key* *value*: (double) value that will be added to the member's score *member* ##### *Return value* *DOUBLE* the new value ##### *Examples* ~~~ $redis->delete('key'); $redis->zIncrBy('key', 2.5, 'member1'); /* key or member1 didn't exist, so member1's score is to 0 before the increment */ /* and now has the value 2.5 */ $redis->zIncrBy('key', 1, 'member1'); /* 3.5 */ ~~~ ### zInter ----- _**Description**_: Creates an intersection of sorted sets given in second argument. The result of the union will be stored in the sorted set defined by the first argument. The third optionnel argument defines `weights` to apply to the sorted sets in input. In this case, the `weights` will be multiplied by the score of each element in the sorted set before applying the aggregation. The forth argument defines the `AGGREGATE` option which specify how the results of the union are aggregated. ##### *Parameters* *keyOutput* *arrayZSetKeys* *arrayWeights* *aggregateFunction* Either "SUM", "MIN", or "MAX": defines the behaviour to use on duplicate entries during the zInter. ##### *Return value* *LONG* The number of values in the new sorted set. ##### *Example* ~~~ $redis->delete('k1'); $redis->delete('k2'); $redis->delete('k3'); $redis->delete('ko1'); $redis->delete('ko2'); $redis->delete('ko3'); $redis->delete('ko4'); $redis->zAdd('k1', 0, 'val0'); $redis->zAdd('k1', 1, 'val1'); $redis->zAdd('k1', 3, 'val3'); $redis->zAdd('k2', 2, 'val1'); $redis->zAdd('k2', 3, 'val3'); $redis->zInter('ko1', array('k1', 'k2')); /* 2, 'ko1' => array('val1', 'val3') */ $redis->zInter('ko2', array('k1', 'k2'), array(1, 1)); /* 2, 'ko2' => array('val1', 'val3') */ /* Weighted zInter */ $redis->zInter('ko3', array('k1', 'k2'), array(1, 5), 'min'); /* 2, 'ko3' => array('val1', 'val3') */ $redis->zInter('ko4', array('k1', 'k2'), array(1, 5), 'max'); /* 2, 'ko4' => array('val3', 'val1') */ ~~~ ### zRange ----- _**Description**_: Returns a range of elements from the ordered set stored at the specified key, with values in the range [start, end]. Start and stop are interpreted as zero-based indices: 0 the first element, 1 the second ... -1 the last element, -2 the penultimate ... ##### *Parameters* *key* *start*: long *end*: long *withscores*: bool = false ##### *Return value* *Array* containing the values in specified range. ##### *Example* ~~~ $redis->zAdd('key1', 0, 'val0'); $redis->zAdd('key1', 2, 'val2'); $redis->zAdd('key1', 10, 'val10'); $redis->zRange('key1', 0, -1); /* array('val0', 'val2', 'val10') */ // with scores $redis->zRange('key1', 0, -1, true); /* array('val0' => 0, 'val2' => 2, 'val10' => 10) */ ~~~ ### zRangeByScore, zRevRangeByScore ----- _**Description**_: Returns the elements of the sorted set stored at the specified key which have scores in the range [start,end]. Adding a parenthesis before `start` or `end` excludes it from the range. +inf and -inf are also valid limits. zRevRangeByScore returns the same items in reverse order, when the `start` and `end` parameters are swapped. ##### *Parameters* *key* *start*: string *end*: string *options*: array Two options are available: `withscores => TRUE`, and `limit => array($offset, $count)` ##### *Return value* *Array* containing the values in specified range. ##### *Example* ~~~ $redis->zAdd('key', 0, 'val0'); $redis->zAdd('key', 2, 'val2'); $redis->zAdd('key', 10, 'val10'); $redis->zRangeByScore('key', 0, 3); /* array('val0', 'val2') */ $redis->zRangeByScore('key', 0, 3, array('withscores' => TRUE); /* array('val0' => 0, 'val2' => 2) */ $redis->zRangeByScore('key', 0, 3, array('limit' => array(1, 1)); /* array('val2') */ $redis->zRangeByScore('key', 0, 3, array('withscores' => TRUE, 'limit' => array(1, 1)); /* array('val2' => 2) */ ~~~ ### zRangeByLex ----- _**Description**_: Returns a lexigraphical range of members in a sorted set, assuming the members have the same score. The min and max values are required to start with '(' (exclusive), '[' (inclusive), or be exactly the values '-' (negative inf) or '+' (positive inf). The command must be called with either three *or* five arguments or will return FALSE. ##### *Parameters* *key*: The ZSET you wish to run against *min*: The minimum alphanumeric value you wish to get *max*: The maximum alphanumeric value you wish to get *offset*: Optional argument if you wish to start somewhere other than the first element. *limit*: Optional argument if you wish to limit the number of elements returned. ##### *Return value* *Array* containing the values in the specified range. ##### *Example* ~~~ foreach(Array('a','b','c','d','e','f','g') as $c) $redis->zAdd('key',0,$c); $redis->zRangeByLex('key','-','[c') /* Array('a','b','c'); */ $redis->zRangeByLex('key','-','(c') /* Array('a','b') */ $redis->zRangeByLex('key','-','[c',1,2) /* Array('b','c') */ ~~~ ### zRank, zRevRank ----- _**Description**_: Returns the rank of a given member in the specified sorted set, starting at 0 for the item with the smallest score. zRevRank starts at 0 for the item with the *largest* score. ##### *Parameters* *key* *member* ##### *Return value* *Long*, the item's score. ##### *Example* ~~~ $redis->delete('z'); $redis->zAdd('key', 1, 'one'); $redis->zAdd('key', 2, 'two'); $redis->zRank('key', 'one'); /* 0 */ $redis->zRank('key', 'two'); /* 1 */ $redis->zRevRank('key', 'one'); /* 1 */ $redis->zRevRank('key', 'two'); /* 0 */ ~~~ ### zRem, zDelete ----- _**Description**_: Deletes a specified member from the ordered set. ##### *Parameters* *key* *member* ##### *Return value* *LONG* 1 on success, 0 on failure. ##### *Example* ~~~ $redis->zAdd('key', 0, 'val0'); $redis->zAdd('key', 2, 'val2'); $redis->zAdd('key', 10, 'val10'); $redis->zDelete('key', 'val2'); $redis->zRange('key', 0, -1); /* array('val0', 'val10') */ ~~~ ### zRemRangeByRank, zDeleteRangeByRank ----- _**Description**_: Deletes the elements of the sorted set stored at the specified key which have rank in the range [start,end]. ##### *Parameters* *key* *start*: LONG *end*: LONG ##### *Return value* *LONG* The number of values deleted from the sorted set ##### *Example* ~~~ $redis->zAdd('key', 1, 'one'); $redis->zAdd('key', 2, 'two'); $redis->zAdd('key', 3, 'three'); $redis->zRemRangeByRank('key', 0, 1); /* 2 */ $redis->zRange('key', 0, -1, array('withscores' => TRUE)); /* array('three' => 3) */ ~~~ ### zRemRangeByScore, zDeleteRangeByScore ----- _**Description**_: Deletes the elements of the sorted set stored at the specified key which have scores in the range [start,end]. ##### *Parameters* *key* *start*: double or "+inf" or "-inf" string *end*: double or "+inf" or "-inf" string ##### *Return value* *LONG* The number of values deleted from the sorted set ##### *Example* ~~~ $redis->zAdd('key', 0, 'val0'); $redis->zAdd('key', 2, 'val2'); $redis->zAdd('key', 10, 'val10'); $redis->zRemRangeByScore('key', 0, 3); /* 2 */ ~~~ ### zRevRange ----- _**Description**_: Returns the elements of the sorted set stored at the specified key in the range [start, end] in reverse order. start and stop are interpretated as zero-based indices: 0 the first element, 1 the second ... -1 the last element, -2 the penultimate ... ##### *Parameters* *key* *start*: long *end*: long *withscores*: bool = false ##### *Return value* *Array* containing the values in specified range. ##### *Example* ~~~ $redis->zAdd('key', 0, 'val0'); $redis->zAdd('key', 2, 'val2'); $redis->zAdd('key', 10, 'val10'); $redis->zRevRange('key', 0, -1); /* array('val10', 'val2', 'val0') */ // with scores $redis->zRevRange('key', 0, -1, true); /* array('val10' => 10, 'val2' => 2, 'val0' => 0) */ ~~~ ### zScore ----- _**Description**_: Returns the score of a given member in the specified sorted set. ##### *Parameters* *key* *member* ##### *Return value* *Double* ##### *Example* ~~~ $redis->zAdd('key', 2.5, 'val2'); $redis->zScore('key', 'val2'); /* 2.5 */ ~~~ ### zUnion ----- _**Description**_: Creates an union of sorted sets given in second argument. The result of the union will be stored in the sorted set defined by the first argument. The third optionnel argument defines `weights` to apply to the sorted sets in input. In this case, the `weights` will be multiplied by the score of each element in the sorted set before applying the aggregation. The forth argument defines the `AGGREGATE` option which specify how the results of the union are aggregated. ##### *Parameters* *keyOutput* *arrayZSetKeys* *arrayWeights* *aggregateFunction* Either "SUM", "MIN", or "MAX": defines the behaviour to use on duplicate entries during the zUnion. ##### *Return value* *LONG* The number of values in the new sorted set. ##### *Example* ~~~ $redis->delete('k1'); $redis->delete('k2'); $redis->delete('k3'); $redis->delete('ko1'); $redis->delete('ko2'); $redis->delete('ko3'); $redis->zAdd('k1', 0, 'val0'); $redis->zAdd('k1', 1, 'val1'); $redis->zAdd('k2', 2, 'val2'); $redis->zAdd('k2', 3, 'val3'); $redis->zUnion('ko1', array('k1', 'k2')); /* 4, 'ko1' => array('val0', 'val1', 'val2', 'val3') */ /* Weighted zUnion */ $redis->zUnion('ko2', array('k1', 'k2'), array(1, 1)); /* 4, 'ko2' => array('val0', 'val1', 'val2', 'val3') */ $redis->zUnion('ko3', array('k1', 'k2'), array(5, 1)); /* 4, 'ko3' => array('val0', 'val2', 'val3', 'val1') */ ~~~ ### zScan ----- _**Description**_: Scan a sorted set for members, with optional pattern and count ##### *Parameters* *key*: String, the set to scan *iterator*: Long (reference), initialized to NULL *pattern*: String (optional), the pattern to match *count*: How many keys to return per iteration (Redis might return a different number) ##### *Return value* *Array, boolean* PHPRedis will return matching keys from Redis, or FALSE when iteration is complete ##### *Example* ~~~ $it = NULL; $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); while($arr_matches = $redis->zscan('zset', $it, '*pattern*')) { foreach($arr_matches as $str_mem => $f_score) { echo "Key: $str_mem, Score: $f_score\n"; } } ~~~ ## Pub/sub * [psubscribe](#psubscribe) - Subscribe to channels by pattern * [publish](#publish) - Post a message to a channel * [subscribe](#subscribe) - Subscribe to channels * [pubsub](#pubsub) - Introspection into the pub/sub subsystem ### psubscribe ----- _**Description**_: Subscribe to channels by pattern ##### *Parameters* *patterns*: An array of patterns to match *callback*: Either a string or an array with an object and method. The callback will get four arguments ($redis, $pattern, $channel, $message) *return value*: Mixed. Any non-null return value in the callback will be returned to the caller. ##### *Example* ~~~ function psubscribe($redis, $pattern, $chan, $msg) { echo "Pattern: $pattern\n"; echo "Channel: $chan\n"; echo "Payload: $msg\n"; } ~~~ ### publish ----- _**Description**_: Publish messages to channels. Warning: this function will probably change in the future. ##### *Parameters* *channel*: a channel to publish to *messsage*: string ##### *Example* ~~~ $redis->publish('chan-1', 'hello, world!'); // send message. ~~~ ### subscribe ----- _**Description**_: Subscribe to channels. Warning: this function will probably change in the future. ##### *Parameters* *channels*: an array of channels to subscribe to *callback*: either a string or an array($instance, 'method_name'). The callback function receives 3 parameters: the redis instance, the channel name, and the message. *return value*: Mixed. Any non-null return value in the callback will be returned to the caller. ##### *Example* ~~~ function f($redis, $chan, $msg) { switch($chan) { case 'chan-1': ... break; case 'chan-2': ... break; case 'chan-2': ... break; } } $redis->subscribe(array('chan-1', 'chan-2', 'chan-3'), 'f'); // subscribe to 3 chans ~~~ ### pubsub ----- _**Description**_: A command allowing you to get information on the Redis pub/sub system. ##### *Parameters* *keyword*: String, which can be: "channels", "numsub", or "numpat" *argument*: Optional, variant. For the "channels" subcommand, you can pass a string pattern. For "numsub" an array of channel names. ##### *Return value* *CHANNELS*: Returns an array where the members are the matching channels. *NUMSUB*: Returns a key/value array where the keys are channel names and values are their counts. *NUMPAT*: Integer return containing the number active pattern subscriptions ##### *Example* ~~~ $redis->pubsub("channels"); /*All channels */ $redis->pubsub("channels", "*pattern*"); /* Just channels matching your pattern */ $redis->pubsub("numsub", Array("chan1", "chan2")); /*Get subscriber counts for 'chan1' and 'chan2'*/ $redsi->pubsub("numpat"); /* Get the number of pattern subscribers */ ``` ## Transactions 1. [multi, exec, discard](#multi-exec-discard) - Enter and exit transactional mode 2. [watch, unwatch](#watch-unwatch) - Watches a key for modifications by another client. ### multi, exec, discard. ----- _**Description**_: Enter and exit transactional mode. ##### *Parameters* (optional) `Redis::MULTI` or `Redis::PIPELINE`. Defaults to `Redis::MULTI`. A `Redis::MULTI` block of commands runs as a single transaction; a `Redis::PIPELINE` block is simply transmitted faster to the server, but without any guarantee of atomicity. `discard` cancels a transaction. ##### *Return value* `multi()` returns the Redis instance and enters multi-mode. Once in multi-mode, all subsequent method calls return the same object until `exec()` is called. ##### *Example* ~~~ $ret = $redis->multi() ->set('key1', 'val1') ->get('key1') ->set('key2', 'val2') ->get('key2') ->exec(); /* $ret == array( 0 => TRUE, 1 => 'val1', 2 => TRUE, 3 => 'val2'); */ ~~~ ### watch, unwatch ----- _**Description**_: Watches a key for modifications by another client. If the key is modified between `WATCH` and `EXEC`, the MULTI/EXEC transaction will fail (return `FALSE`). `unwatch` cancels all the watching of all keys by this client. ##### *Parameters* *keys*: a list of keys ##### *Example* ~~~ $redis->watch('x'); /* long code here during the execution of which other clients could well modify `x` */ $ret = $redis->multi() ->incr('x') ->exec(); /* $ret = FALSE if x has been modified between the call to WATCH and the call to EXEC. */ ~~~ ## Scripting * [eval](#) - Evaluate a LUA script serverside * [evalSha](#) - Evaluate a LUA script serverside, from the SHA1 hash of the script instead of the script itself * [script](#) - Execute the Redis SCRIPT command to perform various operations on the scripting subsystem * [getLastError](#) - The last error message (if any) * [clearLastError](#) - Clear the last error message * [_prefix](#) - A utility method to prefix the value with the prefix setting for phpredis * [_unserialize](#) - A utility method to unserialize data with whatever serializer is set up * [_serialize](#) - A utility method to serialize data with whatever serializer is set up ### eval ----- _**Description**_: Evaluate a LUA script serverside ##### *Parameters* *script* string. *args* array, optional. *num_keys* int, optional. ##### *Return value* Mixed. What is returned depends on what the LUA script itself returns, which could be a scalar value (int/string), or an array. Arrays that are returned can also contain other arrays, if that's how it was set up in your LUA script. If there is an error executing the LUA script, the getLastError() function can tell you the message that came back from Redis (e.g. compile error). ##### *Examples* ~~~ $redis->eval("return 1"); // Returns an integer: 1 $redis->eval("return {1,2,3}"); // Returns Array(1,2,3) $redis->del('mylist'); $redis->rpush('mylist','a'); $redis->rpush('mylist','b'); $redis->rpush('mylist','c'); // Nested response: Array(1,2,3,Array('a','b','c')); $redis->eval("return {1,2,3,redis.call('lrange','mylist',0,-1)}}"); ~~~ ### evalSha ----- _**Description**_: Evaluate a LUA script serverside, from the SHA1 hash of the script instead of the script itself. In order to run this command Redis will have to have already loaded the script, either by running it or via the SCRIPT LOAD command. ##### *Parameters* *script_sha* string. The sha1 encoded hash of the script you want to run. *args* array, optional. Arguments to pass to the LUA script. *num_keys* int, optional. The number of arguments that should go into the KEYS array, vs. the ARGV array when Redis spins the script ##### *Return value* Mixed. See EVAL ##### *Examples* ~~~ $script = 'return 1'; $sha = $redis->script('load', $script); $redis->evalSha($sha); // Returns 1 ~~~ ### script ----- _**Description**_: Execute the Redis SCRIPT command to perform various operations on the scripting subsystem. ##### *Usage* ~~~ $redis->script('load', $script); $redis->script('flush'); $redis->script('kill'); $redis->script('exists', $script1, [$script2, $script3, ...]); ~~~ ##### *Return value* * SCRIPT LOAD will return the SHA1 hash of the passed script on success, and FALSE on failure. * SCRIPT FLUSH should always return TRUE * SCRIPT KILL will return true if a script was able to be killed and false if not * SCRIPT EXISTS will return an array with TRUE or FALSE for each passed script ### client ----- _**Description**_: Issue the CLIENT command with various arguments. The Redis CLIENT command can be used in four ways. * CLIENT LIST * CLIENT GETNAME * CLIENT SETNAME [name] * CLIENT KILL [ip:port] ##### *Usage* ~~~ $redis->client('list'); // Get a list of clients $redis->client('getname'); // Get the name of the current connection $redis->client('setname', 'somename'); // Set the name of the current connection $redis->client('kill', <ip:port>); // Kill the process at ip:port ~~~ ##### *Return value* This will vary depending on which client command was executed. * CLIENT LIST will return an array of arrays with client information. * CLIENT GETNAME will return the client name or false if none has been set * CLIENT SETNAME will return true if it can be set and false if not * CLIENT KILL will return true if the client can be killed, and false if not Note: phpredis will attempt to reconnect so you can actually kill your own connection but may not notice losing it! ### getLastError ----- _**Description**_: The last error message (if any) ##### *Parameters* *none* ##### *Return value* A string with the last returned script based error message, or NULL if there is no error ##### *Examples* ~~~ $redis->eval('this-is-not-lua'); $err = $redis->getLastError(); // "ERR Error compiling script (new function): user_script:1: '=' expected near '-'" ~~~ ### clearLastError ----- _**Description**_: Clear the last error message ##### *Parameters* *none* ##### *Return value* *BOOL* TRUE ##### *Examples* ~~~ $redis->set('x', 'a'); $redis->incr('x'); $err = $redis->getLastError(); // "ERR value is not an integer or out of range" $redis->clearLastError(); $err = $redis->getLastError(); // NULL ~~~ ### _prefix ----- _**Description**_: A utility method to prefix the value with the prefix setting for phpredis. ##### *Parameters* *value* string. The value you wish to prefix ##### *Return value* If a prefix is set up, the value now prefixed. If there is no prefix, the value will be returned unchanged. ##### *Examples* ~~~ $redis->setOption(Redis::OPT_PREFIX, 'my-prefix:'); $redis->_prefix('my-value'); // Will return 'my-prefix:my-value' ~~~ ### _serialize ----- _**Description**_: A utility method to serialize values manually. This method allows you to serialize a value with whatever serializer is configured, manually. This can be useful for serialization/unserialization of data going in and out of EVAL commands as phpredis can't automatically do this itself. Note that if no serializer is set, phpredis will change Array values to 'Array', and Objects to 'Object'. ##### *Parameters* *value*: Mixed. The value to be serialized ##### *Examples* ~~~ $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); $redis->_serialize("foo"); // returns "foo" $redis->_serialize(Array()); // Returns "Array" $redis->_serialize(new stdClass()); // Returns "Object" $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); $redis->_serialize("foo"); // Returns 's:3:"foo";' ~~~ ### _unserialize ----- _**Description**_: A utility method to unserialize data with whatever serializer is set up. If there is no serializer set, the value will be returned unchanged. If there is a serializer set up, and the data passed in is malformed, an exception will be thrown. This can be useful if phpredis is serializing values, and you return something from redis in a LUA script that is serialized. ##### *Parameters* *value* string. The value to be unserialized ##### *Examples* ~~~ $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); $redis->_unserialize('a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}'); // Will return Array(1,2,3) ~~~ ## Introspection ### IsConnected ----- _**Description**_: A method to determine if a phpredis object thinks it's connected to a server ##### *Parameters* None ##### *Return value* *Boolean* Returns TRUE if phpredis thinks it's connected and FALSE if not ### GetHost ----- _**Description**_: Retreive our host or unix socket that we're connected to ##### *Parameters* None ##### *Return value* *Mixed* The host or unix socket we're connected to or FALSE if we're not connected ### GetPort ----- _**Description**_: Get the port we're connected to ##### *Parameters* None ##### *Return value* *Mixed* Returns the port we're connected to or FALSE if we're not connected ### getDBNum ----- _**Description**_: Get the database number phpredis is pointed to ##### *Parameters* None ##### *Return value* *Mixed* Returns the database number (LONG) phpredis thinks it's pointing to or FALSE if we're not connected ### GetTimeout ----- _**Description**_: Get the (write) timeout in use for phpredis ##### *Parameters* None ##### *Return value* *Mixed* The timeout (DOUBLE) specified in our connect call or FALSE if we're not connected ### GetReadTimeout _**Description**_: Get the read timeout specified to phpredis or FALSE if we're not connected ##### *Parameters* None ##### *Return value* *Mixed* Returns the read timeout (which can be set using setOption and Redis::OPT_READ_TIMOUT) or FALSE if we're not connected ### GetPersistentID ----- _**Description**_: Gets the persistent ID that phpredis is using ##### *Parameters* None ##### *Return value* *Mixed* Returns the persistent id phpredis is using (which will only be set if connected with pconnect), NULL if we're not using a persistent ID, and FALSE if we're not connected ### GetAuth ----- _**Description**_: Get the password used to authenticate the phpredis connection ### *Parameters* None ### *Return value* *Mixed* Returns the password used to authenticate a phpredis session or NULL if none was used, and FALSE if we're not connected Redis Arrays ============ A Redis array is an isolated namespace in which keys are related in some manner. Keys are distributed across a number of Redis instances, using consistent hashing. A hash function is used to spread the keys across the array in order to keep a uniform distribution. **This feature was added as the result of a generous sponsorship by [A+E Networks](http://www.aetn.com/).** An array is composed of the following: * A list of Redis hosts. * A key extraction function, used to hash part of the key in order to distribute related keys on the same node (optional). This is set by the "function" option. * A list of nodes previously in the ring, only present after a node has been added or removed. When a read command is sent to the array (e.g. GET, LRANGE...), the key is first queryied in the main ring, and then in the secondary ring if it was not found in the main one. Optionally, the keys can be migrated automatically when this happens. Write commands will always go to the main ring. This is set by the "previous" option. * An optional index in the form of a Redis set per node, used to migrate keys when nodes are added or removed; set by the "index" option. * An option to rehash the array automatically as nodes are added or removed, set by the "autorehash" option. ## Creating an array There are several ways of creating Redis arrays; they can be pre-defined in redis.ini using `new RedisArray(string $name);`, or created dynamically using `new RedisArray(array $hosts, array $options);` #### Declaring a new array with a list of nodes <pre> $ra = new RedisArray(array("host1", "host2:63792", "host2:6380")); </pre> #### Declaring a new array with a list of nodes and a function to extract a part of the key <pre> function extract_key_part($k) { return substr($k, 0, 3); // hash only on first 3 characters. } $ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("function" => "extract_key_part")); </pre> #### Defining a "previous" array when nodes are added or removed. When a new node is added to an array, phpredis needs to know about it. The old list of nodes becomes the “previous” array, and the new list of nodes is used as a main ring. Right after a node has been added, some read commands will point to the wrong nodes and will need to look up the keys in the previous ring. <pre> // adding host3 to a ring containing host1 and host2. Read commands will look in the previous ring if the data is not found in the main ring. $ra = new RedisArray(array("host1", "host2", "host3"), array("previous" => array("host1", "host2"))); </pre> #### Specifying the "retry_interval" parameter The retry_interval is used to specify a delay in milliseconds between reconnection attempts in case the client loses connection with a server <pre> $ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("retry_timeout" => 100))); </pre> #### Specifying the "lazy_connect" parameter This option is useful when a cluster has many shards but not of them are necessarily used at one time. <pre> $ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("lazy_connect" => true))); </pre> #### Defining arrays in Redis.ini Because php.ini parameters must be pre-defined, Redis Arrays must all share the same .ini settings. <pre> // list available Redis Arrays ini_set('redis.array.names', 'users,friends'); // set host names for each array. ini_set('redis.arrays.hosts', 'users[]=localhost:6379&users[]=localhost:6380&users[]=localhost:6381&users[]=localhost:6382&friends[]=localhost'); // set functions ini_set('redis.arrays.functions', 'users=user_hash'); // use index only for users ini_set('redis.arrays.index', 'users=1,friends=0'); </pre> ## Usage Redis arrays can be used just as Redis objects: <pre> $ra = new RedisArray("users"); $ra->set("user1:name", "Joe"); $ra->set("user2:name", "Mike"); </pre> ## Key hashing By default and in order to be compatible with other libraries, phpredis will try to find a substring enclosed in curly braces within the key name, and use it to distribute the data. For instance, the keys “{user:1}:name” and “{user:1}:email” will be stored on the same server as only “user:1” will be hashed. You can provide a custom function name in your redis array with the "function" option; this function will be called every time a key needs to be hashed. It should take a string and return a string. ## Custom key distribution function In order to control the distribution of keys by hand, you can provide a custom function or closure that returns the server number, which is the index in the array of servers that you created the RedisArray object with. For instance, instanciate a RedisArray object with `new RedisArray(array("us-host", "uk-host", "de-host"), array("distributor" => "dist"));` and write a function called "dist" that will return `2` for all the keys that should end up on the "de-host" server. ### Example <pre> $ra = new RedisArray(array("host1", "host2", "host3", "host4", "host5", "host6", "host7", "host8"), array("distributor" => array(2, 2))); </pre> This declares that we started with 2 shards and moved to 4 then 8 shards. The number of initial shards is 2 and the resharding level (or number of iterations) is 2. ## Migrating keys When a node is added or removed from a ring, RedisArray instances must be instanciated with a “previous” list of nodes. A single call to `$ra->_rehash()` causes all the keys to be redistributed according to the new list of nodes. Passing a callback function to `_rehash()` makes it possible to track the progress of that operation: the function is called with a node name and a number of keys that will be examined, e.g. `_rehash(function ($host, $count){ ... });`. It is possible to automate this process, by setting `'autorehash' => TRUE` in the constructor options. This will cause keys to be migrated when they need to be read from the previous array. In order to migrate keys, they must all be examined and rehashed. If the "index" option was set, a single key per node lists all keys present there. Otherwise, the `KEYS` command is used to list them. If a “previous” list of servers is provided, it will be used as a backup ring when keys can not be found in the current ring. Writes will always go to the new ring, whilst reads will go to the new ring first, and to the second ring as a backup. Adding and/or removing several instances is supported. ### Example <pre> $ra = new RedisArray("users"); // load up a new config from redis.ini, using the “.previous” listing. $ra->_rehash(); </pre> Running this code will: * Create a new ring with the updated list of nodes. * Server by server, look up all the keys in the previous list of nodes. * Rehash each key and possibly move it to another server. * Update the array object with the new list of nodes. ## Multi/exec Multi/exec is still available, but must be run on a single node: <pre> $host = $ra->_target("{users}:user1:name"); // find host first $ra->multi($host) // then run transaction on that host. ->del("{users}:user1:name") ->srem("{users}:index", "user1") ->exec(); </pre> ## Limitations Key arrays offer no guarantee when using Redis commands that span multiple keys. Except for the use of MGET, MSET, and DEL, a single connection will be used and all the keys read or written there. Running KEYS() on a RedisArray object will execute the command on each node and return an associative array of keys, indexed by host name. ## Array info RedisArray objects provide several methods to help understand the state of the cluster. These methods start with an underscore. * `$ra->_hosts()` → returns a list of hosts for the selected array. * `$ra->_function()` → returns the name of the function used to extract key parts during consistent hashing. * `$ra->_target($key)` → returns the host to be used for a certain key. * `$ra->_instance($host)` → returns a redis instance connected to a specific node; use with `_target` to get a single Redis object. ## Running the unit tests <pre> $ cd tests $ ./mkring.sh start $ php array-tests.php </pre> Redis client extension for PHP Alfonso Jimenez (yo@alfonsojimenez.com) Nasreddine Bouafif (n.bouafif@owlient.eu) Nicolas Favre-Felix (n.favre-felix@owlient.eu) Michael Grunder (michael.grunder@gmail.com) -------------------------------------------------------------------- The PHP License, version 3.01 Copyright (c) 1999 - 2010 The PHP Group. All rights reserved. -------------------------------------------------------------------- Redistribution and use in source and binary forms, with or without modification, is permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name "PHP" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact group@php.net. 4. Products derived from this software may not be called "PHP", nor may "PHP" appear in their name, without prior written permission from group@php.net. You may indicate that your software works in conjunction with PHP by saying "Foo for PHP" instead of calling it "PHP Foo" or "phpfoo" 5. The PHP Group may publish revised and/or new versions of the license from time to time. Each version will be given a distinguishing version number. Once covered code has been published under a particular version of the license, you may always continue to use it under the terms of that version. You may also choose to use such covered code under the terms of any subsequent version of the license published by the PHP Group. No one other than the PHP Group has the right to modify the terms applicable to covered code created under this License. 6. Redistributions of any form whatsoever must retain the following acknowledgment: "This product includes PHP software, freely available from <http://www.php.net/software/>". THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------- This software consists of voluntary contributions made by many individuals on behalf of the PHP Group. The PHP Group can be contacted via Email at group@php.net. For more information on the PHP Group and the PHP project, please see <http://www.php.net>. PHP includes the Zend Engine, freely available at <http://www.zend.com>. dnl $Id$ dnl config.m4 for extension redis PHP_ARG_ENABLE(redis, whether to enable redis support, dnl Make sure that the comment is aligned: [ --enable-redis Enable redis support]) PHP_ARG_ENABLE(redis-session, whether to enable sessions, [ --disable-redis-session Disable session support], yes, no) PHP_ARG_ENABLE(redis-igbinary, whether to enable igbinary serializer support, [ --enable-redis-igbinary Enable igbinary serializer support], no, no) if test "$PHP_REDIS" != "no"; then if test "$PHP_REDIS_SESSION" != "no"; then AC_DEFINE(PHP_SESSION,1,[redis sessions]) fi dnl Check for igbinary if test "$PHP_REDIS_IGBINARY" != "no"; then AC_MSG_CHECKING([for igbinary includes]) igbinary_inc_path="" if test -f "$abs_srcdir/include/php/ext/igbinary/igbinary.h"; then igbinary_inc_path="$abs_srcdir/include/php" elif test -f "$abs_srcdir/ext/igbinary/igbinary.h"; then igbinary_inc_path="$abs_srcdir" elif test -f "$phpincludedir/ext/igbinary/igbinary.h"; then igbinary_inc_path="$phpincludedir" else for i in php php4 php5 php6; do if test -f "$prefix/include/$i/ext/igbinary/igbinary.h"; then igbinary_inc_path="$prefix/include/$i" fi done fi if test "$igbinary_inc_path" = ""; then AC_MSG_ERROR([Cannot find igbinary.h]) else AC_MSG_RESULT([$igbinary_inc_path]) fi fi AC_MSG_CHECKING([for redis igbinary support]) if test "$PHP_REDIS_IGBINARY" != "no"; then AC_MSG_RESULT([enabled]) AC_DEFINE(HAVE_REDIS_IGBINARY,1,[Whether redis igbinary serializer is enabled]) IGBINARY_INCLUDES="-I$igbinary_inc_path" IGBINARY_EXT_DIR="$igbinary_inc_path/ext" ifdef([PHP_ADD_EXTENSION_DEP], [ PHP_ADD_EXTENSION_DEP(redis, igbinary) ]) PHP_ADD_INCLUDE($IGBINARY_EXT_DIR) else IGBINARY_INCLUDES="" AC_MSG_RESULT([disabled]) fi dnl # --with-redis -> check with-path dnl SEARCH_PATH="/usr/local /usr" # you might want to change this dnl SEARCH_FOR="/include/redis.h" # you most likely want to change this dnl if test -r $PHP_REDIS/$SEARCH_FOR; then # path given as parameter dnl REDIS_DIR=$PHP_REDIS dnl else # search default path list dnl AC_MSG_CHECKING([for redis files in default path]) dnl for i in $SEARCH_PATH ; do dnl if test -r $i/$SEARCH_FOR; then dnl REDIS_DIR=$i dnl AC_MSG_RESULT(found in $i) dnl fi dnl done dnl fi dnl dnl if test -z "$REDIS_DIR"; then dnl AC_MSG_RESULT([not found]) dnl AC_MSG_ERROR([Please reinstall the redis distribution]) dnl fi dnl # --with-redis -> add include path dnl PHP_ADD_INCLUDE($REDIS_DIR/include) dnl # --with-redis -> check for lib and symbol presence dnl LIBNAME=redis # you may want to change this dnl LIBSYMBOL=redis # you most likely want to change this dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, dnl [ dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $REDIS_DIR/lib, REDIS_SHARED_LIBADD) dnl AC_DEFINE(HAVE_REDISLIB,1,[ ]) dnl ],[ dnl AC_MSG_ERROR([wrong redis lib version or lib not found]) dnl ],[ dnl -L$REDIS_DIR/lib -lm -ldl dnl ]) dnl dnl PHP_SUBST(REDIS_SHARED_LIBADD) PHP_NEW_EXTENSION(redis, redis.c library.c redis_session.c redis_array.c redis_array_impl.c, $ext_shared) fi // vim: ft=javascript: ARG_ENABLE("redis", "whether to enable redis support", "yes"); ARG_ENABLE("redis-session", "whether to enable sessions", "yes"); ARG_ENABLE("redis-igbinary", "whether to enable igbinary serializer support", "no"); if (PHP_REDIS != "no") { var sources = "redis.c library.c redis_array.c redis_array_impl.c"; if (PHP_REDIS_SESSION != "no") { ADD_SOURCES(configure_module_dirname, "redis_session.c", "redis"); ADD_EXTENSION_DEP("redis", "session"); ADD_FLAG("CFLAGS_REDIS", ' /D PHP_SESSION=1 '); AC_DEFINE("HAVE_REDIS_SESSION", 1); } if (PHP_REDIS_IGBINARY != "no") { if (CHECK_HEADER_ADD_INCLUDE("igbinary.h", "CFLAGS_REDIS", configure_module_dirname + "\\..\\igbinary")) { ADD_EXTENSION_DEP("redis", "igbinary"); AC_DEFINE("HAVE_REDIS_IGBINARY", 1); } else { WARNING("redis igbinary support not enabled"); } } EXTENSION("redis", sources); } #include "php.h" #include "php_ini.h" #include <ext/standard/php_smart_str.h> #ifndef REDIS_COMMON_H #define REDIS_COMMON_H /* NULL check so Eclipse doesn't go crazy */ #ifndef NULL #define NULL ((void *) 0) #endif #define redis_sock_name "Redis Socket Buffer" #define REDIS_SOCK_STATUS_FAILED 0 #define REDIS_SOCK_STATUS_DISCONNECTED 1 #define REDIS_SOCK_STATUS_UNKNOWN 2 #define REDIS_SOCK_STATUS_CONNECTED 3 #define redis_multi_access_type_name "Redis Multi type access" #define _NL "\r\n" /* properties */ #define REDIS_NOT_FOUND 0 #define REDIS_STRING 1 #define REDIS_SET 2 #define REDIS_LIST 3 #define REDIS_ZSET 4 #define REDIS_HASH 5 /* reply types */ typedef enum _REDIS_REPLY_TYPE { TYPE_EOF = EOF, TYPE_LINE = '+', TYPE_INT = ':', TYPE_ERR = '-', TYPE_BULK = '$', TYPE_MULTIBULK = '*' } REDIS_REPLY_TYPE; /* SCAN variants */ typedef enum _REDIS_SCAN_TYPE { TYPE_SCAN, TYPE_SSCAN, TYPE_HSCAN, TYPE_ZSCAN } REDIS_SCAN_TYPE; /* PUBSUB subcommands */ typedef enum _PUBSUB_TYPE { PUBSUB_CHANNELS, PUBSUB_NUMSUB, PUBSUB_NUMPAT } PUBSUB_TYPE; /* options */ #define REDIS_OPT_SERIALIZER 1 #define REDIS_OPT_PREFIX 2 #define REDIS_OPT_READ_TIMEOUT 3 #define REDIS_OPT_SCAN 4 /* serializers */ #define REDIS_SERIALIZER_NONE 0 #define REDIS_SERIALIZER_PHP 1 #define REDIS_SERIALIZER_IGBINARY 2 /* SCAN options */ #define REDIS_SCAN_NORETRY 0 #define REDIS_SCAN_RETRY 1 /* GETBIT/SETBIT offset range limits */ #define BITOP_MIN_OFFSET 0 #define BITOP_MAX_OFFSET 4294967295 /* Specific error messages we want to throw against */ #define REDIS_ERR_LOADING_MSG "LOADING Redis is loading the dataset in memory" #define REDIS_ERR_LOADING_KW "LOADING" #define REDIS_ERR_AUTH_MSG "NOAUTH Authentication required." #define REDIS_ERR_AUTH_KW "NOAUTH" #define REDIS_ERR_SYNC_MSG "MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'" #define REDIS_ERR_SYNC_KW "MASTERDOWN" #define IF_MULTI() if(redis_sock->mode == MULTI) #define IF_MULTI_OR_ATOMIC() if(redis_sock->mode == MULTI || redis_sock->mode == ATOMIC)\ #define IF_MULTI_OR_PIPELINE() if(redis_sock->mode == MULTI || redis_sock->mode == PIPELINE) #define IF_PIPELINE() if(redis_sock->mode == PIPELINE) #define IF_NOT_MULTI() if(redis_sock->mode != MULTI) #define IF_NOT_ATOMIC() if(redis_sock->mode != ATOMIC) #define IF_ATOMIC() if(redis_sock->mode == ATOMIC) #define ELSE_IF_MULTI() else if(redis_sock->mode == MULTI) { \ if(redis_response_enqueued(redis_sock TSRMLS_CC) == 1) {\ RETURN_ZVAL(getThis(), 1, 0);\ } else {\ RETURN_FALSE;\ } \ } #define ELSE_IF_PIPELINE() else IF_PIPELINE() { \ RETURN_ZVAL(getThis(), 1, 0);\ } #define MULTI_RESPONSE(callback) IF_MULTI_OR_PIPELINE() { \ fold_item *f1, *current; \ f1 = malloc(sizeof(fold_item)); \ f1->fun = (void *)callback; \ f1->next = NULL; \ current = redis_sock->current;\ if(current) current->next = f1; \ redis_sock->current = f1; \ } #define PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len) request_item *tmp; \ struct request_item *current_request;\ tmp = malloc(sizeof(request_item));\ tmp->request_str = calloc(cmd_len, 1);\ memcpy(tmp->request_str, cmd, cmd_len);\ tmp->request_size = cmd_len;\ tmp->next = NULL;\ current_request = redis_sock->pipeline_current; \ if(current_request) {\ current_request->next = tmp;\ } \ redis_sock->pipeline_current = tmp; \ if(NULL == redis_sock->pipeline_head) { \ redis_sock->pipeline_head = redis_sock->pipeline_current;\ } #define SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len) if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { \ efree(cmd); \ RETURN_FALSE; \ } #define REDIS_SAVE_CALLBACK(callback, closure_context) IF_MULTI_OR_PIPELINE() { \ fold_item *f1, *current; \ f1 = malloc(sizeof(fold_item)); \ f1->fun = (void *)callback; \ f1->ctx = closure_context; \ f1->next = NULL; \ current = redis_sock->current;\ if(current) current->next = f1; \ redis_sock->current = f1; \ if(NULL == redis_sock->head) { \ redis_sock->head = redis_sock->current;\ }\ } #define REDIS_ELSE_IF_MULTI(function, closure_context) \ else if(redis_sock->mode == MULTI) { \ if(redis_response_enqueued(redis_sock TSRMLS_CC) == 1) {\ REDIS_SAVE_CALLBACK(function, closure_context); \ RETURN_ZVAL(getThis(), 1, 0);\ } else {\ RETURN_FALSE;\ }\ } #define REDIS_ELSE_IF_PIPELINE(function, closure_context) else IF_PIPELINE() { \ REDIS_SAVE_CALLBACK(function, closure_context); \ RETURN_ZVAL(getThis(), 1, 0);\ } #define REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) \ IF_MULTI_OR_ATOMIC() { \ SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len); \ efree(cmd); \ }\ IF_PIPELINE() { \ PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len); \ efree(cmd); \ } #define REDIS_PROCESS_RESPONSE_CLOSURE(function, closure_context) \ REDIS_ELSE_IF_MULTI(function, closure_context) \ REDIS_ELSE_IF_PIPELINE(function, closure_context); #define REDIS_PROCESS_RESPONSE(function) REDIS_PROCESS_RESPONSE_CLOSURE(function, NULL) /* Extended SET argument detection */ #define IS_EX_ARG(a) ((a[0]=='e' || a[0]=='E') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') #define IS_PX_ARG(a) ((a[0]=='p' || a[0]=='P') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') #define IS_NX_ARG(a) ((a[0]=='n' || a[0]=='N') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') #define IS_XX_ARG(a) ((a[0]=='x' || a[0]=='X') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') #define IS_EX_PX_ARG(a) (IS_EX_ARG(a) || IS_PX_ARG(a)) #define IS_NX_XX_ARG(a) (IS_NX_ARG(a) || IS_XX_ARG(a)) /* Given a string and length, validate a zRangeByLex argument. The semantics * here are that the argument must start with '(' or '[' or be just the char * '+' or '-' */ #define IS_LEX_ARG(s,l) \ (l>0 && (*s=='(' || *s=='[' || (l==1 && (*s=='+' || *s=='-')))) typedef enum {ATOMIC, MULTI, PIPELINE} redis_mode; typedef struct fold_item { zval * (*fun)(INTERNAL_FUNCTION_PARAMETERS, void *, ...); void *ctx; struct fold_item *next; } fold_item; typedef struct request_item { char *request_str; int request_size; /* size_t */ struct request_item *next; } request_item; /* {{{ struct RedisSock */ typedef struct { php_stream *stream; char *host; short port; char *auth; double timeout; double read_timeout; long retry_interval; int failed; int status; int persistent; int watching; char *persistent_id; int serializer; long dbNumber; char *prefix; int prefix_len; redis_mode mode; fold_item *head; fold_item *current; request_item *pipeline_head; request_item *pipeline_current; char *err; int err_len; zend_bool lazy_connect; int scan; } RedisSock; /* }}} */ void free_reply_callbacks(zval *z_this, RedisSock *redis_sock); #endif #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "common.h" #include "php_network.h" #include <sys/types.h> #ifndef _MSC_VER #include <netinet/tcp.h> /* TCP_NODELAY */ #include <sys/socket.h> #endif #include <ext/standard/php_smart_str.h> #include <ext/standard/php_var.h> #ifdef HAVE_REDIS_IGBINARY #include "igbinary/igbinary.h" #endif #include <zend_exceptions.h> #include "php_redis.h" #include "library.h" #include <ext/standard/php_math.h> #include <ext/standard/php_rand.h> #ifdef PHP_WIN32 # if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION <= 4 /* This proto is available from 5.5 on only */ PHPAPI int usleep(unsigned int useconds); # endif #endif #ifdef _MSC_VER #define atoll _atoi64 #define random rand #define usleep Sleep #endif #define UNSERIALIZE_NONE 0 #define UNSERIALIZE_KEYS 1 #define UNSERIALIZE_VALS 2 #define UNSERIALIZE_ALL 3 #define SCORE_DECODE_NONE 0 #define SCORE_DECODE_INT 1 #define SCORE_DECODE_DOUBLE 2 extern zend_class_entry *redis_ce; extern zend_class_entry *redis_exception_ce; extern zend_class_entry *spl_ce_RuntimeException; /* Helper to reselect the proper DB number when we reconnect */ static int reselect_db(RedisSock *redis_sock TSRMLS_DC) { char *cmd, *response; int cmd_len, response_len; cmd_len = redis_cmd_format_static(&cmd, "SELECT", "d", redis_sock->dbNumber); if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); return -1; } efree(cmd); if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { return -1; } if (strncmp(response, "+OK", 3)) { efree(response); return -1; } efree(response); return 0; } /* Helper to resend AUTH <password> in the case of a reconnect */ static int resend_auth(RedisSock *redis_sock TSRMLS_DC) { char *cmd, *response; int cmd_len, response_len; cmd_len = redis_cmd_format_static(&cmd, "AUTH", "s", redis_sock->auth, strlen(redis_sock->auth)); if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); return -1; } efree(cmd); response = redis_sock_read(redis_sock, &response_len TSRMLS_CC); if (response == NULL) { return -1; } if (strncmp(response, "+OK", 3)) { efree(response); return -1; } efree(response); return 0; } /* Helper function that will throw an exception for a small number of ERR codes * returned by Redis. Typically we just return FALSE to the caller in the event * of an ERROR reply, but for the following error types: * 1) MASTERDOWN * 2) AUTH * 3) LOADING */ static void redis_error_throw(char *err, size_t err_len TSRMLS_DC) { /* Handle stale data error (slave syncing with master) */ if (err_len == sizeof(REDIS_ERR_SYNC_MSG) - 1 && !memcmp(err,REDIS_ERR_SYNC_KW,sizeof(REDIS_ERR_SYNC_KW)-1)) { zend_throw_exception(redis_exception_ce, "SYNC with master in progress or master down!", 0 TSRMLS_CC); } else if (err_len == sizeof(REDIS_ERR_LOADING_MSG) - 1 && !memcmp(err,REDIS_ERR_LOADING_KW,sizeof(REDIS_ERR_LOADING_KW)-1)) { zend_throw_exception(redis_exception_ce, "Redis is LOADING the dataset", 0 TSRMLS_CC); } else if (err_len == sizeof(REDIS_ERR_AUTH_MSG) -1 && !memcmp(err,REDIS_ERR_AUTH_KW,sizeof(REDIS_ERR_AUTH_KW)-1)) { zend_throw_exception(redis_exception_ce, "Failed to AUTH connection", 0 TSRMLS_CC); } } PHP_REDIS_API void redis_stream_close(RedisSock *redis_sock TSRMLS_DC) { if (!redis_sock->persistent) { php_stream_close(redis_sock->stream); } else { php_stream_pclose(redis_sock->stream); } } PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock TSRMLS_DC) { int eof; int count = 0; if (!redis_sock->stream) { return -1; } eof = php_stream_eof(redis_sock->stream); for (; eof; count++) { /* Only try up to a certain point */ if((MULTI == redis_sock->mode) || redis_sock->watching || count == 10) { if(redis_sock->stream) { /* close stream if still here */ redis_stream_close(redis_sock TSRMLS_CC); redis_sock->stream = NULL; redis_sock->mode = ATOMIC; redis_sock->status = REDIS_SOCK_STATUS_FAILED; redis_sock->watching = 0; } zend_throw_exception(redis_exception_ce, "Connection lost", 0 TSRMLS_CC); return -1; } /* Close existing stream before reconnecting */ if(redis_sock->stream) { redis_stream_close(redis_sock TSRMLS_CC); redis_sock->stream = NULL; redis_sock->mode = ATOMIC; redis_sock->watching = 0; } /* Wait for a while before trying to reconnect */ if (redis_sock->retry_interval) { // Random factor to avoid having several (or many) concurrent connections trying to reconnect at the same time long retry_interval = (count ? redis_sock->retry_interval : (php_rand(TSRMLS_C) % redis_sock->retry_interval)); usleep(retry_interval); } redis_sock_connect(redis_sock TSRMLS_CC); /* reconnect */ if(redis_sock->stream) { /* check for EOF again. */ eof = php_stream_eof(redis_sock->stream); } } /* We've reconnected if we have a count */ if (count) { /* If we're using a password, attempt a reauthorization */ if (redis_sock->auth && resend_auth(redis_sock TSRMLS_CC) != 0) { return -1; } /* If we're using a non zero db, reselect it */ if (redis_sock->dbNumber && reselect_db(redis_sock TSRMLS_CC) != 0) { return -1; } } /* Success */ return 0; } PHP_REDIS_API int redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_SCAN_TYPE type, long *iter) { REDIS_REPLY_TYPE reply_type; int reply_info; char *p_iter; /* Our response should have two multibulk replies */ if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC)<0 || reply_type != TYPE_MULTIBULK || reply_info != 2) { return -1; } /* The BULK response iterator */ if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC)<0 || reply_type != TYPE_BULK) { return -1; } /* Attempt to read the iterator */ if(!(p_iter = redis_sock_read_bulk_reply(redis_sock, reply_info TSRMLS_CC))) { return -1; } /* Push the iterator out to the caller */ *iter = atol(p_iter); efree(p_iter); /* Read our actual keys/members/etc differently depending on what kind of scan command this is. They all come back in slightly different ways */ switch(type) { case TYPE_SCAN: return redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); case TYPE_SSCAN: return redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); case TYPE_ZSCAN: return redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); case TYPE_HSCAN: return redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); default: return -1; } } PHP_REDIS_API zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) { char inbuf[1024]; int numElems; zval *z_tab; if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { return NULL; } if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { redis_stream_close(redis_sock TSRMLS_CC); redis_sock->stream = NULL; redis_sock->status = REDIS_SOCK_STATUS_FAILED; redis_sock->mode = ATOMIC; redis_sock->watching = 0; zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); return NULL; } if(inbuf[0] != '*') { return NULL; } numElems = atoi(inbuf+1); MAKE_STD_ZVAL(z_tab); array_init(z_tab); redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, numElems, UNSERIALIZE_ALL); return z_tab; } /** * redis_sock_read_bulk_reply */ PHP_REDIS_API char *redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes TSRMLS_DC) { int offset = 0; size_t got; char * reply; if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { return NULL; } if (bytes == -1) { return NULL; } else { char c; int i; reply = emalloc(bytes+1); while(offset < bytes) { got = php_stream_read(redis_sock->stream, reply + offset, bytes-offset); if (got <= 0) { /* Error or EOF */ zend_throw_exception(redis_exception_ce, "socket error on read socket", 0 TSRMLS_CC); break; } offset += got; } for(i = 0; i < 2; i++) { php_stream_read(redis_sock->stream, &c, 1); } } reply[bytes] = 0; return reply; } /** * redis_sock_read */ PHP_REDIS_API char *redis_sock_read(RedisSock *redis_sock, int *buf_len TSRMLS_DC) { char inbuf[1024]; char *resp = NULL; size_t err_len; if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { return NULL; } if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { redis_stream_close(redis_sock TSRMLS_CC); redis_sock->stream = NULL; redis_sock->status = REDIS_SOCK_STATUS_FAILED; redis_sock->mode = ATOMIC; redis_sock->watching = 0; zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); return NULL; } switch(inbuf[0]) { case '-': /* Set the last error */ err_len = strlen(inbuf+1) - 2; redis_sock_set_err(redis_sock, inbuf+1, err_len); /* Filter our ERROR through the few that should actually throw */ redis_error_throw(inbuf + 1, err_len TSRMLS_CC); return NULL; case '$': *buf_len = atoi(inbuf + 1); resp = redis_sock_read_bulk_reply(redis_sock, *buf_len TSRMLS_CC); return resp; case '*': /* For null multi-bulk replies (like timeouts from brpoplpush): */ if(memcmp(inbuf + 1, "-1", 2) == 0) { return NULL; } /* fall through */ case '+': case ':': /* Single Line Reply */ /* :123\r\n */ *buf_len = strlen(inbuf) - 2; if(*buf_len >= 2) { resp = emalloc(1+*buf_len); memcpy(resp, inbuf, *buf_len); resp[*buf_len] = 0; return resp; } default: zend_throw_exception_ex( redis_exception_ce, 0 TSRMLS_CC, "protocol error, got '%c' as reply type byte\n", inbuf[0] ); } return NULL; } void add_constant_long(zend_class_entry *ce, char *name, int value) { zval *constval; constval = pemalloc(sizeof(zval), 1); INIT_PZVAL(constval); ZVAL_LONG(constval, value); zend_hash_add(&ce->constants_table, name, 1 + strlen(name), (void*)&constval, sizeof(zval*), NULL); } int integer_length(int i) { int sz = 0; int ci = abs(i); while (ci > 0) { ci /= 10; sz++; } if (i == 0) { /* log 0 doesn't make sense. */ sz = 1; } else if (i < 0) { /* allow for neg sign as well. */ sz++; } return sz; } int redis_cmd_format_header(char **ret, char *keyword, int arg_count) { /* Our return buffer */ smart_str buf = {0}; /* Keyword length */ int l = strlen(keyword); smart_str_appendc(&buf, '*'); smart_str_append_long(&buf, arg_count + 1); smart_str_appendl(&buf, _NL, sizeof(_NL) -1); smart_str_appendc(&buf, '$'); smart_str_append_long(&buf, l); smart_str_appendl(&buf, _NL, sizeof(_NL) -1); smart_str_appendl(&buf, keyword, l); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); /* Set our return pointer */ *ret = buf.c; /* Return the length */ return buf.len; } int redis_cmd_format_static(char **ret, char *keyword, char *format, ...) { char *p = format; va_list ap; smart_str buf = {0}; int l = strlen(keyword); char *dbl_str; int dbl_len; va_start(ap, format); /* add header */ smart_str_appendc(&buf, '*'); smart_str_append_long(&buf, strlen(format) + 1); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); smart_str_appendc(&buf, '$'); smart_str_append_long(&buf, l); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); smart_str_appendl(&buf, keyword, l); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); while (*p) { smart_str_appendc(&buf, '$'); switch(*p) { case 's': { char *val = va_arg(ap, char*); int val_len = va_arg(ap, int); smart_str_append_long(&buf, val_len); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); smart_str_appendl(&buf, val, val_len); } break; case 'f': case 'F': { double d = va_arg(ap, double); REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, d) smart_str_append_long(&buf, dbl_len); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); smart_str_appendl(&buf, dbl_str, dbl_len); efree(dbl_str); } break; case 'i': case 'd': { int i = va_arg(ap, int); char tmp[32]; int tmp_len = snprintf(tmp, sizeof(tmp), "%d", i); smart_str_append_long(&buf, tmp_len); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); smart_str_appendl(&buf, tmp, tmp_len); } break; case 'l': case 'L': { long l = va_arg(ap, long); char tmp[32]; int tmp_len = snprintf(tmp, sizeof(tmp), "%ld", l); smart_str_append_long(&buf, tmp_len); smart_str_appendl(&buf, _NL, sizeof(_NL) -1); smart_str_appendl(&buf, tmp, tmp_len); } break; } p++; smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); } smart_str_0(&buf); *ret = buf.c; return buf.len; } /** * This command behave somehow like printf, except that strings need 2 arguments: * Their data and their size (strlen). * Supported formats are:d, %i, %s, %l */ int redis_cmd_format(char **ret, char *format, ...) { smart_str buf = {0}; va_list ap; char *p = format; char *dbl_str; int dbl_len; va_start(ap, format); while (*p) { if (*p == '%') { switch (*(++p)) { case 's': { char *tmp = va_arg(ap, char*); int tmp_len = va_arg(ap, int); smart_str_appendl(&buf, tmp, tmp_len); } break; case 'F': case 'f': { double d = va_arg(ap, double); REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, d) smart_str_append_long(&buf, dbl_len); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); smart_str_appendl(&buf, dbl_str, dbl_len); efree(dbl_str); } break; case 'd': case 'i': { int i = va_arg(ap, int); char tmp[32]; int tmp_len = snprintf(tmp, sizeof(tmp), "%d", i); smart_str_appendl(&buf, tmp, tmp_len); } break; } } else { smart_str_appendc(&buf, *p); } p++; } smart_str_0(&buf); *ret = buf.c; return buf.len; } /* * Append a command sequence to a Redis command */ int redis_cmd_append_str(char **cmd, int cmd_len, char *append, int append_len) { /* Smart string buffer */ smart_str buf = {0}; /* Append the current command to our smart_str */ smart_str_appendl(&buf, *cmd, cmd_len); /* Append our new command sequence */ smart_str_appendc(&buf, '$'); smart_str_append_long(&buf, append_len); smart_str_appendl(&buf, _NL, sizeof(_NL) -1); smart_str_appendl(&buf, append, append_len); smart_str_appendl(&buf, _NL, sizeof(_NL) -1); /* Free our old command */ efree(*cmd); /* Set our return pointer */ *cmd = buf.c; /* Return new command length */ return buf.len; } /* * Given a smart string, number of arguments, a keyword, and the length of the keyword * initialize our smart string with the proper Redis header for the command to follow */ int redis_cmd_init_sstr(smart_str *str, int num_args, char *keyword, int keyword_len) { smart_str_appendc(str, '*'); smart_str_append_long(str, num_args + 1); smart_str_appendl(str, _NL, sizeof(_NL) -1); smart_str_appendc(str, '$'); smart_str_append_long(str, keyword_len); smart_str_appendl(str, _NL, sizeof(_NL) - 1); smart_str_appendl(str, keyword, keyword_len); smart_str_appendl(str, _NL, sizeof(_NL) - 1); return str->len; } /* * Append a command sequence to a smart_str */ int redis_cmd_append_sstr(smart_str *str, char *append, int append_len) { smart_str_appendc(str, '$'); smart_str_append_long(str, append_len); smart_str_appendl(str, _NL, sizeof(_NL) - 1); smart_str_appendl(str, append, append_len); smart_str_appendl(str, _NL, sizeof(_NL) - 1); /* Return our new length */ return str->len; } /* * Append an integer to a smart string command */ int redis_cmd_append_sstr_int(smart_str *str, int append) { char int_buf[32]; int int_len = snprintf(int_buf, sizeof(int_buf), "%d", append); return redis_cmd_append_sstr(str, int_buf, int_len); } /* * Append a long to a smart string command */ int redis_cmd_append_sstr_long(smart_str *str, long append) { char long_buf[32]; int long_len = snprintf(long_buf, sizeof(long_buf), "%ld", append); return redis_cmd_append_sstr(str, long_buf, long_len); } /* * Append a double to a smart string command */ int redis_cmd_append_sstr_dbl(smart_str *str, double value) { char *dbl_str; int dbl_len; int retval; /* Convert to double */ REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, value); /* Append the string */ retval = redis_cmd_append_sstr(str, dbl_str, dbl_len); /* Free our double string */ efree(dbl_str); /* Return new length */ return retval; } /* * Append an integer command to a Redis command */ int redis_cmd_append_int(char **cmd, int cmd_len, int append) { char int_buf[32]; int int_len; /* Conver to an int, capture length */ int_len = snprintf(int_buf, sizeof(int_buf), "%d", append); /* Return the new length */ return redis_cmd_append_str(cmd, cmd_len, int_buf, int_len); } PHP_REDIS_API void redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; double ret; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { IF_MULTI_OR_PIPELINE() { add_next_index_bool(z_tab, 0); } else { RETURN_FALSE; } return; } ret = atof(response); efree(response); IF_MULTI_OR_PIPELINE() { add_next_index_double(z_tab, ret); } else { RETURN_DOUBLE(ret); } } PHP_REDIS_API void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; long l; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { IF_MULTI_OR_PIPELINE() { add_next_index_bool(z_tab, 0); } else { RETURN_FALSE; } } if (strncmp(response, "+string", 7) == 0) { l = REDIS_STRING; } else if (strncmp(response, "+set", 4) == 0){ l = REDIS_SET; } else if (strncmp(response, "+list", 5) == 0){ l = REDIS_LIST; } else if (strncmp(response, "+zset", 5) == 0){ l = REDIS_ZSET; } else if (strncmp(response, "+hash", 5) == 0){ l = REDIS_HASH; } else { l = REDIS_NOT_FOUND; } efree(response); IF_MULTI_OR_PIPELINE() { add_next_index_long(z_tab, l); } else { RETURN_LONG(l); } } PHP_REDIS_API void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; char *pos, *cur; char *key, *value, *p; int is_numeric; zval *z_multi_result; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { RETURN_FALSE; } MAKE_STD_ZVAL(z_multi_result); array_init(z_multi_result); /* pre-allocate array for multi's results. */ /* response :: [response_line] * response_line :: key ':' value CRLF */ cur = response; while(1) { /* skip comments and empty lines */ if(*cur == '#' || *cur == '\r') { if(!(cur = strchr(cur, '\n'))) break; cur++; continue; } /* key */ pos = strchr(cur, ':'); if(pos == NULL) { break; } key = emalloc(pos - cur + 1); memcpy(key, cur, pos-cur); key[pos-cur] = 0; /* value */ cur = pos + 1; pos = strchr(cur, '\r'); if(pos == NULL) { break; } value = emalloc(pos - cur + 1); memcpy(value, cur, pos-cur); value[pos-cur] = 0; pos += 2; /* \r, \n */ cur = pos; is_numeric = 1; for(p = value; *p; ++p) { if(*p < '0' || *p > '9') { is_numeric = 0; break; } } if(is_numeric == 1) { add_assoc_long(z_multi_result, key, atol(value)); efree(value); } else { add_assoc_string(z_multi_result, key, value, 0); } efree(key); } efree(response); IF_MULTI_OR_PIPELINE() { add_next_index_zval(z_tab, z_multi_result); } else { RETVAL_ZVAL(z_multi_result, 0, 1); } } /* * Specialized handling of the CLIENT LIST output so it comes out in a simple way for PHP userland code * to handle. */ PHP_REDIS_API void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab) { char *resp; int resp_len; zval *z_result, *z_sub_result; /* Pointers for parsing */ char *p, *lpos, *kpos = NULL, *vpos = NULL, *p2, *key, *value; /* Key length, done flag */ int klen, done = 0, is_numeric; /* Make sure we can read a response from Redis */ if((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC)) == NULL) { RETURN_FALSE; } /* Allocate memory for our response */ MAKE_STD_ZVAL(z_result); array_init(z_result); /* Allocate memory for one user (there should be at least one, namely us!) */ ALLOC_INIT_ZVAL(z_sub_result); array_init(z_sub_result); p = resp; lpos = resp; /* While we've got more to parse */ while(!done) { /* What character are we on */ switch(*p) { /* We're done */ case '\0': done = 1; break; /* \n, ' ' mean we can pull a k/v pair */ case '\n': case ' ': /* Grab our value */ vpos = lpos; /* There is some communication error or Redis bug if we don't have a key and value, but check anyway. */ if(kpos && vpos) { /* Allocate, copy in our key */ key = emalloc(klen + 1); strncpy(key, kpos, klen); key[klen] = 0; /* Allocate, copy in our value */ value = emalloc(p-lpos+1); strncpy(value,lpos,p-lpos+1); value[p-lpos]=0; /* Treat numbers as numbers, strings as strings */ is_numeric = 1; for(p2 = value; *p2; ++p2) { if(*p2 < '0' || *p2 > '9') { is_numeric = 0; break; } } /* Add as a long or string, depending */ if(is_numeric == 1) { add_assoc_long(z_sub_result, key, atol(value)); efree(value); } else { add_assoc_string(z_sub_result, key, value, 0); } /* If we hit a '\n', then we can add this user to our list */ if(*p == '\n') { /* Add our user */ add_next_index_zval(z_result, z_sub_result); /* If we have another user, make another one */ if(*(p+1) != '\0') { ALLOC_INIT_ZVAL(z_sub_result); array_init(z_sub_result); } } /* Free our key */ efree(key); } else { /* Something is wrong */ efree(resp); RETURN_FALSE; } /* Move forward */ lpos = p + 1; break; /* We can pull the key and null terminate at our sep */ case '=': /* Key, key length */ kpos = lpos; klen = p - lpos; /* Move forward */ lpos = p + 1; break; } /* Increment */ p++; } /* Free our respoonse */ efree(resp); IF_MULTI_OR_PIPELINE() { add_next_index_zval(z_tab, z_result); } else { RETVAL_ZVAL(z_result, 0, 1); } } PHP_REDIS_API void redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx, SuccessCallback success_callback) { char *response; int response_len; char ret; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { IF_MULTI_OR_PIPELINE() { add_next_index_bool(z_tab, 0); return; } RETURN_FALSE; } ret = response[0]; efree(response); IF_MULTI_OR_PIPELINE() { if (ret == '+') { if (success_callback != NULL) { success_callback(redis_sock); } add_next_index_bool(z_tab, 1); } else { add_next_index_bool(z_tab, 0); } } else { if (ret == '+') { if (success_callback != NULL) { success_callback(redis_sock); } RETURN_TRUE; } else { RETURN_FALSE; } } } PHP_REDIS_API void redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, ctx, NULL); } PHP_REDIS_API void redis_long_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval * z_tab, void *ctx) { char *response; int response_len; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { IF_MULTI_OR_PIPELINE() { add_next_index_bool(z_tab, 0); return; } else { RETURN_FALSE; } } if(response[0] == ':') { #ifdef PHP_WIN32 __int64 ret = _atoi64(response + 1); #else long long ret = atoll(response + 1); #endif IF_MULTI_OR_PIPELINE() { if(ret > LONG_MAX) { /* overflow */ add_next_index_stringl(z_tab, response+1, response_len-1, 1); } else { efree(response); add_next_index_long(z_tab, (long)ret); } } else { if(ret > LONG_MAX) { /* overflow */ RETURN_STRINGL(response+1, response_len-1, 1); } else { efree(response); RETURN_LONG((long)ret); } } } else { efree(response); IF_MULTI_OR_PIPELINE() { add_next_index_null(z_tab); } else { RETURN_FALSE; } } } /* Helper method to convert [key, value, key, value] into [key => value, * key => value] when returning data to the caller. Depending on our decode * flag we'll convert the value data types */ static void array_zip_values_and_scores(RedisSock *redis_sock, zval *z_tab, int decode TSRMLS_DC) { zval *z_ret; HashTable *keytable; MAKE_STD_ZVAL(z_ret); array_init(z_ret); keytable = Z_ARRVAL_P(z_tab); for(zend_hash_internal_pointer_reset(keytable); zend_hash_has_more_elements(keytable) == SUCCESS; zend_hash_move_forward(keytable)) { char *tablekey, *hkey, *hval; unsigned int tablekey_len; int hkey_len; unsigned long idx; zval **z_key_pp, **z_value_pp; zend_hash_get_current_key_ex(keytable, &tablekey, &tablekey_len, &idx, 0, NULL); if(zend_hash_get_current_data(keytable, (void**)&z_key_pp) == FAILURE) { continue; /* this should never happen, according to the PHP people. */ } /* get current value, a key */ convert_to_string(*z_key_pp); hkey = Z_STRVAL_PP(z_key_pp); hkey_len = Z_STRLEN_PP(z_key_pp); /* move forward */ zend_hash_move_forward(keytable); /* fetch again */ zend_hash_get_current_key_ex(keytable, &tablekey, &tablekey_len, &idx, 0, NULL); if(zend_hash_get_current_data(keytable, (void**)&z_value_pp) == FAILURE) { continue; /* this should never happen, according to the PHP people. */ } /* get current value, a hash value now. */ hval = Z_STRVAL_PP(z_value_pp); /* Decode the score depending on flag */ if (decode == SCORE_DECODE_INT && Z_STRLEN_PP(z_value_pp) > 0) { add_assoc_long_ex(z_ret, hkey, 1+hkey_len, atoi(hval+1)); } else if (decode == SCORE_DECODE_DOUBLE) { add_assoc_double_ex(z_ret, hkey, 1+hkey_len, atof(hval)); } else { zval *z = NULL; MAKE_STD_ZVAL(z); *z = **z_value_pp; zval_copy_ctor(z); add_assoc_zval_ex(z_ret, hkey, 1+hkey_len, z); } } /* replace */ zval_dtor(z_tab); *z_tab = *z_ret; zval_copy_ctor(z_tab); zval_dtor(z_ret); efree(z_ret); } static int redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int unserialize, int decode) { char inbuf[1024]; int numElems; zval *z_multi_result; if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { return -1; } if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { redis_stream_close(redis_sock TSRMLS_CC); redis_sock->stream = NULL; redis_sock->status = REDIS_SOCK_STATUS_FAILED; redis_sock->mode = ATOMIC; redis_sock->watching = 0; zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); return -1; } if(inbuf[0] != '*') { IF_MULTI_OR_PIPELINE() { add_next_index_bool(z_tab, 0); } else { RETVAL_FALSE; } return -1; } numElems = atoi(inbuf+1); MAKE_STD_ZVAL(z_multi_result); array_init(z_multi_result); /* pre-allocate array for multi's results. */ /* Grab our key, value, key, value array */ redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_multi_result, numElems, unserialize); /* Zip keys and values */ array_zip_values_and_scores(redis_sock, z_multi_result, decode TSRMLS_CC); IF_MULTI_OR_PIPELINE() { add_next_index_zval(z_tab, z_multi_result); } else { *return_value = *z_multi_result; zval_copy_ctor(return_value); zval_dtor(z_multi_result); efree(z_multi_result); } return 0; } /* Zipped key => value reply but we don't touch anything (e.g. CONFIG GET) */ PHP_REDIS_API int redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, UNSERIALIZE_NONE, SCORE_DECODE_NONE); } /* Zipped key => value reply unserializing keys and decoding the score as an integer (PUBSUB) */ PHP_REDIS_API int redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, UNSERIALIZE_KEYS, SCORE_DECODE_INT); } /* Zipped key => value reply unserializing keys and decoding the score as a double (ZSET commands) */ PHP_REDIS_API int redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, UNSERIALIZE_KEYS, SCORE_DECODE_DOUBLE); } /* Zipped key => value reply where only the values are unserialized (e.g. HMGET) */ PHP_REDIS_API int redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, UNSERIALIZE_VALS, SCORE_DECODE_NONE); } PHP_REDIS_API void redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; char ret; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { IF_MULTI_OR_PIPELINE() { add_next_index_bool(z_tab, 0); return; } else { RETURN_FALSE; } } ret = response[1]; efree(response); IF_MULTI_OR_PIPELINE() { if(ret == '1') { add_next_index_bool(z_tab, 1); } else { add_next_index_bool(z_tab, 0); } } else { if (ret == '1') { RETURN_TRUE; } else { RETURN_FALSE; } } } PHP_REDIS_API void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { IF_MULTI_OR_PIPELINE() { add_next_index_bool(z_tab, 0); return; } RETURN_FALSE; } IF_MULTI_OR_PIPELINE() { zval *z = NULL; if(redis_unserialize(redis_sock, response, response_len, &z TSRMLS_CC) == 1) { efree(response); add_next_index_zval(z_tab, z); } else { add_next_index_stringl(z_tab, response, response_len, 0); } } else { if(redis_unserialize(redis_sock, response, response_len, &return_value TSRMLS_CC) == 0) { RETURN_STRINGL(response, response_len, 0); } else { efree(response); } } } /* like string response, but never unserialized. */ PHP_REDIS_API void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { IF_MULTI_OR_PIPELINE() { add_next_index_bool(z_tab, 0); return; } RETURN_FALSE; } IF_MULTI_OR_PIPELINE() { add_next_index_stringl(z_tab, response, response_len, 0); } else { RETURN_STRINGL(response, response_len, 0); } } /* Response for DEBUG object which is a formatted single line reply */ PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *resp, *p, *p2, *p3, *p4; int is_numeric, resp_len; zval *z_result; /* Add or return false if we can't read from the socket */ if((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC))==NULL) { IF_MULTI_OR_PIPELINE() { add_next_index_bool(z_tab, 0); return; } RETURN_FALSE; } MAKE_STD_ZVAL(z_result); array_init(z_result); /* Skip the '+' */ p = resp + 1; /* <info>:<value> <info2:value2> ... */ while((p2 = strchr(p, ':'))!=NULL) { /* Null terminate at the ':' */ *p2++ = '\0'; /* Null terminate at the space if we have one */ if((p3 = strchr(p2, ' '))!=NULL) { *p3++ = '\0'; } else { p3 = resp + resp_len; } is_numeric = 1; for(p4=p2; *p4; ++p4) { if(*p4 < '0' || *p4 > '9') { is_numeric = 0; break; } } /* Add our value */ if(is_numeric) { add_assoc_long(z_result, p, atol(p2)); } else { add_assoc_string(z_result, p, p2, 1); } p = p3; } efree(resp); IF_MULTI_OR_PIPELINE() { add_next_index_zval(z_tab, z_result); } else { RETVAL_ZVAL(z_result, 0, 1); } } /** * redis_sock_create */ PHP_REDIS_API RedisSock* redis_sock_create(char *host, int host_len, unsigned short port, double timeout, int persistent, char *persistent_id, long retry_interval, zend_bool lazy_connect) { RedisSock *redis_sock; redis_sock = ecalloc(1, sizeof(RedisSock)); redis_sock->host = estrndup(host, host_len); redis_sock->stream = NULL; redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED; redis_sock->watching = 0; redis_sock->dbNumber = 0; redis_sock->retry_interval = retry_interval * 1000; redis_sock->persistent = persistent; redis_sock->lazy_connect = lazy_connect; if(persistent_id) { size_t persistent_id_len = strlen(persistent_id); redis_sock->persistent_id = ecalloc(persistent_id_len + 1, 1); memcpy(redis_sock->persistent_id, persistent_id, persistent_id_len); } else { redis_sock->persistent_id = NULL; } memcpy(redis_sock->host, host, host_len); redis_sock->host[host_len] = '\0'; redis_sock->port = port; redis_sock->timeout = timeout; redis_sock->read_timeout = timeout; redis_sock->serializer = REDIS_SERIALIZER_NONE; redis_sock->mode = ATOMIC; redis_sock->head = NULL; redis_sock->current = NULL; redis_sock->pipeline_head = NULL; redis_sock->pipeline_current = NULL; redis_sock->err = NULL; redis_sock->err_len = 0; redis_sock->scan = REDIS_SCAN_NORETRY; return redis_sock; } /** * redis_sock_connect */ PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock TSRMLS_DC) { struct timeval tv, read_tv, *tv_ptr = NULL; char *host = NULL, *persistent_id = NULL, *errstr = NULL; int host_len, err = 0; php_netstream_data_t *sock; int tcp_flag = 1; if (redis_sock->stream != NULL) { redis_sock_disconnect(redis_sock TSRMLS_CC); } tv.tv_sec = (time_t)redis_sock->timeout; tv.tv_usec = (int)((redis_sock->timeout - tv.tv_sec) * 1000000); if(tv.tv_sec != 0 || tv.tv_usec != 0) { tv_ptr = &tv; } read_tv.tv_sec = (time_t)redis_sock->read_timeout; read_tv.tv_usec = (int)((redis_sock->read_timeout - read_tv.tv_sec) * 1000000); if(redis_sock->host[0] == '/' && redis_sock->port < 1) { host_len = spprintf(&host, 0, "unix://%s", redis_sock->host); } else { if(redis_sock->port == 0) redis_sock->port = 6379; host_len = spprintf(&host, 0, "%s:%d", redis_sock->host, redis_sock->port); } if (redis_sock->persistent) { if (redis_sock->persistent_id) { spprintf(&persistent_id, 0, "phpredis:%s:%s", host, redis_sock->persistent_id); } else { spprintf(&persistent_id, 0, "phpredis:%s:%f", host, redis_sock->timeout); } } redis_sock->stream = php_stream_xport_create(host, host_len, ENFORCE_SAFE_MODE, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, persistent_id, tv_ptr, NULL, &errstr, &err ); if (persistent_id) { efree(persistent_id); } efree(host); if (!redis_sock->stream) { efree(errstr); return -1; } /* set TCP_NODELAY */ sock = (php_netstream_data_t*)redis_sock->stream->abstract; setsockopt(sock->socket, IPPROTO_TCP, TCP_NODELAY, (char *) &tcp_flag, sizeof(int)); php_stream_auto_cleanup(redis_sock->stream); if(tv.tv_sec != 0 || tv.tv_usec != 0) { php_stream_set_option(redis_sock->stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &read_tv); } php_stream_set_option(redis_sock->stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_NONE, NULL); redis_sock->status = REDIS_SOCK_STATUS_CONNECTED; return 0; } /** * redis_sock_server_open */ PHP_REDIS_API int redis_sock_server_open(RedisSock *redis_sock, int force_connect TSRMLS_DC) { int res = -1; switch (redis_sock->status) { case REDIS_SOCK_STATUS_DISCONNECTED: return redis_sock_connect(redis_sock TSRMLS_CC); case REDIS_SOCK_STATUS_CONNECTED: res = 0; break; case REDIS_SOCK_STATUS_UNKNOWN: if (force_connect > 0 && redis_sock_connect(redis_sock TSRMLS_CC) < 0) { res = -1; } else { res = 0; redis_sock->status = REDIS_SOCK_STATUS_CONNECTED; } break; } return res; } /** * redis_sock_disconnect */ PHP_REDIS_API int redis_sock_disconnect(RedisSock *redis_sock TSRMLS_DC) { if (redis_sock == NULL) { return 1; } redis_sock->dbNumber = 0; if (redis_sock->stream != NULL) { if (!redis_sock->persistent) { redis_sock_write(redis_sock, "QUIT" _NL, sizeof("QUIT" _NL) - 1 TSRMLS_CC); } redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED; redis_sock->watching = 0; if(redis_sock->stream && !redis_sock->persistent) { /* still valid after the write? */ php_stream_close(redis_sock->stream); } redis_sock->stream = NULL; return 1; } return 0; } PHP_REDIS_API void redis_send_discard(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) { char *cmd; int response_len, cmd_len; char * response; cmd_len = redis_cmd_format_static(&cmd, "DISCARD", ""); if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); RETURN_FALSE; } efree(cmd); if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { RETURN_FALSE; } if(response_len == 3 && strncmp(response, "+OK", 3) == 0) { RETURN_TRUE; } RETURN_FALSE; } /** * redis_sock_set_err */ PHP_REDIS_API int redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len) { /* Allocate/Reallocate our last error member */ if(msg != NULL && msg_len > 0) { if(redis_sock->err == NULL) { redis_sock->err = emalloc(msg_len + 1); } else if(msg_len > redis_sock->err_len) { redis_sock->err = erealloc(redis_sock->err, msg_len +1); } /* Copy in our new error message, set new length, and null terminate */ memcpy(redis_sock->err, msg, msg_len); redis_sock->err[msg_len] = '\0'; redis_sock->err_len = msg_len; } else { /* Free our last error */ if(redis_sock->err != NULL) { efree(redis_sock->err); } /* Set to null, with zero length */ redis_sock->err = NULL; redis_sock->err_len = 0; } /* Success */ return 0; } /** * redis_sock_read_multibulk_reply */ PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char inbuf[1024]; int numElems, err_len; zval *z_multi_result; if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { return -1; } if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { redis_stream_close(redis_sock TSRMLS_CC); redis_sock->stream = NULL; redis_sock->status = REDIS_SOCK_STATUS_FAILED; redis_sock->mode = ATOMIC; redis_sock->watching = 0; zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); return -1; } if(inbuf[0] != '*') { IF_MULTI_OR_PIPELINE() { add_next_index_bool(z_tab, 0); } else { /* Capture our error if redis has given us one */ if (inbuf[0] == '-') { err_len = strlen(inbuf+1) - 2; redis_sock_set_err(redis_sock, inbuf+1, err_len); } RETVAL_FALSE; } return -1; } numElems = atoi(inbuf+1); MAKE_STD_ZVAL(z_multi_result); array_init(z_multi_result); /* pre-allocate array for multi's results. */ redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_multi_result, numElems, UNSERIALIZE_ALL); IF_MULTI_OR_PIPELINE() { add_next_index_zval(z_tab, z_multi_result); } else { *return_value = *z_multi_result; efree(z_multi_result); } /*zval_copy_ctor(return_value); */ return 0; } /** * Like multibulk reply, but don't touch the values, they won't be compressed. (this is used by HKEYS). */ PHP_REDIS_API int redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char inbuf[1024]; int numElems; zval *z_multi_result; if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { return -1; } if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { redis_stream_close(redis_sock TSRMLS_CC); redis_sock->stream = NULL; redis_sock->status = REDIS_SOCK_STATUS_FAILED; redis_sock->mode = ATOMIC; redis_sock->watching = 0; zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); return -1; } if(inbuf[0] != '*') { IF_MULTI_OR_PIPELINE() { add_next_index_bool(z_tab, 0); } else { RETVAL_FALSE; } return -1; } numElems = atoi(inbuf+1); MAKE_STD_ZVAL(z_multi_result); array_init(z_multi_result); /* pre-allocate array for multi's results. */ redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_multi_result, numElems, UNSERIALIZE_NONE); IF_MULTI_OR_PIPELINE() { add_next_index_zval(z_tab, z_multi_result); } else { *return_value = *z_multi_result; efree(z_multi_result); } /*zval_copy_ctor(return_value); */ return 0; } PHP_REDIS_API void redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int count, int unserialize) { char *line; int len; while(count > 0) { line = redis_sock_read(redis_sock, &len TSRMLS_CC); if (line != NULL) { zval *z = NULL; int unwrap; /* We will attempt unserialization, if we're unserializing everything, * or if we're unserializing keys and we're on a key, or we're * unserializing values and we're on a value! */ unwrap = unserialize == UNSERIALIZE_ALL || (unserialize == UNSERIALIZE_KEYS && count % 2 == 0) || (unserialize == UNSERIALIZE_VALS && count % 2 != 0); if (unwrap && redis_unserialize(redis_sock, line, len, &z TSRMLS_CC)) { efree(line); add_next_index_zval(z_tab, z); } else { add_next_index_stringl(z_tab, line, len, 0); } } else { add_next_index_bool(z_tab, 0); } count--; } } /* PHP_REDIS_API int redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int numElems, int unwrap_key, int unserialize_even_only) { char *response; int response_len; while(numElems > 0) { response = redis_sock_read(redis_sock, &response_len TSRMLS_CC); if(response != NULL) { zval *z = NULL; int can_unserialize = unwrap_key; if(unserialize_even_only == UNSERIALIZE_ONLY_VALUES && numElems % 2 == 0) can_unserialize = 0; if(can_unserialize && redis_unserialize(redis_sock, response, response_len, &z TSRMLS_CC) == 1) { efree(response); add_next_index_zval(z_tab, z); } else { add_next_index_stringl(z_tab, response, response_len, 0); } } else { add_next_index_bool(z_tab, 0); } numElems --; } return 0; } */ /* Specialized multibulk processing for HMGET where we need to pair requested * keys with their returned values */ PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char inbuf[1024], *response; int response_len; int i, numElems; zval *z_multi_result; zval **z_keys = ctx; if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { return -1; } if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { redis_stream_close(redis_sock TSRMLS_CC); redis_sock->stream = NULL; redis_sock->status = REDIS_SOCK_STATUS_FAILED; redis_sock->mode = ATOMIC; redis_sock->watching = 0; zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); return -1; } if(inbuf[0] != '*') { IF_MULTI_OR_PIPELINE() { add_next_index_bool(z_tab, 0); } else { RETVAL_FALSE; } return -1; } numElems = atoi(inbuf+1); MAKE_STD_ZVAL(z_multi_result); array_init(z_multi_result); /* pre-allocate array for multi's results. */ for(i = 0; i < numElems; ++i) { response = redis_sock_read(redis_sock, &response_len TSRMLS_CC); if(response != NULL) { zval *z = NULL; if(redis_unserialize(redis_sock, response, response_len, &z TSRMLS_CC) == 1) { efree(response); add_assoc_zval_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), z); } else { add_assoc_stringl_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), response, response_len, 0); } } else { add_assoc_bool_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), 0); } zval_dtor(z_keys[i]); efree(z_keys[i]); } efree(z_keys); IF_MULTI_OR_PIPELINE() { add_next_index_zval(z_tab, z_multi_result); } else { *return_value = *z_multi_result; zval_copy_ctor(return_value); INIT_PZVAL(return_value); zval_dtor(z_multi_result); efree(z_multi_result); } return 0; } /** * redis_sock_write */ PHP_REDIS_API int redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz TSRMLS_DC) { if(redis_sock && redis_sock->status == REDIS_SOCK_STATUS_DISCONNECTED) { zend_throw_exception(redis_exception_ce, "Connection closed", 0 TSRMLS_CC); return -1; } if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { return -1; } return php_stream_write(redis_sock->stream, cmd, sz); } /** * redis_free_socket */ PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock) { if(redis_sock->prefix) { efree(redis_sock->prefix); } if(redis_sock->err) { efree(redis_sock->err); } if(redis_sock->auth) { efree(redis_sock->auth); } if(redis_sock->persistent_id) { efree(redis_sock->persistent_id); } efree(redis_sock->host); efree(redis_sock); } PHP_REDIS_API int redis_serialize(RedisSock *redis_sock, zval *z, char **val, int *val_len TSRMLS_DC) { #if ZEND_MODULE_API_NO >= 20100000 php_serialize_data_t ht; #else HashTable ht; #endif smart_str sstr = {0}; zval *z_copy; #ifdef HAVE_REDIS_IGBINARY size_t sz; uint8_t *val8; #endif switch(redis_sock->serializer) { case REDIS_SERIALIZER_NONE: switch(Z_TYPE_P(z)) { case IS_STRING: *val = Z_STRVAL_P(z); *val_len = Z_STRLEN_P(z); return 0; case IS_OBJECT: MAKE_STD_ZVAL(z_copy); ZVAL_STRINGL(z_copy, "Object", 6, 1); break; case IS_ARRAY: MAKE_STD_ZVAL(z_copy); ZVAL_STRINGL(z_copy, "Array", 5, 1); break; default: /* copy */ MAKE_STD_ZVAL(z_copy); *z_copy = *z; zval_copy_ctor(z_copy); break; } /* return string */ convert_to_string(z_copy); *val = Z_STRVAL_P(z_copy); *val_len = Z_STRLEN_P(z_copy); efree(z_copy); return 1; case REDIS_SERIALIZER_PHP: #if ZEND_MODULE_API_NO >= 20100000 PHP_VAR_SERIALIZE_INIT(ht); #else zend_hash_init(&ht, 10, NULL, NULL, 0); #endif php_var_serialize(&sstr, &z, &ht TSRMLS_CC); *val = sstr.c; *val_len = (int)sstr.len; #if ZEND_MODULE_API_NO >= 20100000 PHP_VAR_SERIALIZE_DESTROY(ht); #else zend_hash_destroy(&ht); #endif return 1; case REDIS_SERIALIZER_IGBINARY: #ifdef HAVE_REDIS_IGBINARY if(igbinary_serialize(&val8, (size_t *)&sz, z TSRMLS_CC) == 0) { /* ok */ *val = (char*)val8; *val_len = (int)sz; return 1; } #endif return 0; } return 0; } PHP_REDIS_API int redis_unserialize(RedisSock *redis_sock, const char *val, int val_len, zval **return_value TSRMLS_DC) { php_unserialize_data_t var_hash; int ret, rv_free = 0; switch(redis_sock->serializer) { case REDIS_SERIALIZER_NONE: return 0; case REDIS_SERIALIZER_PHP: if(!*return_value) { MAKE_STD_ZVAL(*return_value); rv_free = 1; } #if ZEND_MODULE_API_NO >= 20100000 PHP_VAR_UNSERIALIZE_INIT(var_hash); #else memset(&var_hash, 0, sizeof(var_hash)); #endif if(!php_var_unserialize(return_value, (const unsigned char**)&val, (const unsigned char*)val + val_len, &var_hash TSRMLS_CC)) { if(rv_free==1) efree(*return_value); ret = 0; } else { ret = 1; } #if ZEND_MODULE_API_NO >= 20100000 PHP_VAR_UNSERIALIZE_DESTROY(var_hash); #else var_destroy(&var_hash); #endif return ret; case REDIS_SERIALIZER_IGBINARY: #ifdef HAVE_REDIS_IGBINARY if(!*return_value) { MAKE_STD_ZVAL(*return_value); rv_free = 1; } if(igbinary_unserialize((const uint8_t *)val, (size_t)val_len, return_value TSRMLS_CC) == 0) { return 1; } if(rv_free==1) efree(*return_value); #endif return 0; break; } return 0; } PHP_REDIS_API int redis_key_prefix(RedisSock *redis_sock, char **key, int *key_len TSRMLS_DC) { int ret_len; char *ret; if(redis_sock->prefix == NULL || redis_sock->prefix_len == 0) { return 0; } ret_len = redis_sock->prefix_len + *key_len; ret = ecalloc(1 + ret_len, 1); memcpy(ret, redis_sock->prefix, redis_sock->prefix_len); memcpy(ret + redis_sock->prefix_len, *key, *key_len); *key = ret; *key_len = ret_len; return 1; } /* * Processing for variant reply types (think EVAL) */ PHP_REDIS_API int redis_sock_gets(RedisSock *redis_sock, char *buf, int buf_size, size_t *line_size TSRMLS_DC) { /* Handle EOF */ if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { return -1; } if(php_stream_get_line(redis_sock->stream, buf, buf_size, line_size) == NULL) { /* Close, put our socket state into error */ redis_stream_close(redis_sock TSRMLS_CC); redis_sock->stream = NULL; redis_sock->status = REDIS_SOCK_STATUS_FAILED; redis_sock->mode = ATOMIC; redis_sock->watching = 0; /* Throw a read error exception */ zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); } /* We don't need \r\n */ *line_size-=2; buf[*line_size]='\0'; /* Success! */ return 0; } PHP_REDIS_API int redis_read_reply_type(RedisSock *redis_sock, REDIS_REPLY_TYPE *reply_type, int *reply_info TSRMLS_DC) { /* Make sure we haven't lost the connection, even trying to reconnect */ if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { /* Failure */ return -1; } /* Attempt to read the reply-type byte */ if((*reply_type = php_stream_getc(redis_sock->stream)) == EOF) { zend_throw_exception(redis_exception_ce, "socket error on read socket", 0 TSRMLS_CC); } /* If this is a BULK, MULTI BULK, or simply an INTEGER response, we can extract the value or size info here */ if(*reply_type == TYPE_INT || *reply_type == TYPE_BULK || *reply_type == TYPE_MULTIBULK) { /* Buffer to hold size information */ char inbuf[255]; /* Read up to our newline */ if(php_stream_gets(redis_sock->stream, inbuf, sizeof(inbuf)) < 0) { return -1; } /* Set our size response */ *reply_info = atoi(inbuf); } /* Success! */ return 0; } /* * Read a single line response, having already consumed the reply-type byte */ PHP_REDIS_API int redis_read_variant_line(RedisSock *redis_sock, REDIS_REPLY_TYPE reply_type, zval **z_ret TSRMLS_DC) { /* Buffer to read our single line reply */ char inbuf[1024]; size_t line_size; /* Attempt to read our single line reply */ if(redis_sock_gets(redis_sock, inbuf, sizeof(inbuf), &line_size TSRMLS_CC) < 0) { return -1; } /* If this is an error response, filter specific errors that should throw * an exception, and set our error field in our RedisSock object. */ if(reply_type == TYPE_ERR) { /* Handle throwable errors */ redis_error_throw(inbuf, line_size TSRMLS_CC); /* Set our last error */ redis_sock_set_err(redis_sock, inbuf, line_size); /* Set our response to FALSE */ ZVAL_FALSE(*z_ret); } else { /* Set our response to TRUE */ ZVAL_TRUE(*z_ret); } return 0; } PHP_REDIS_API int redis_read_variant_bulk(RedisSock *redis_sock, int size, zval **z_ret TSRMLS_DC) { /* Attempt to read the bulk reply */ char *bulk_resp = redis_sock_read_bulk_reply(redis_sock, size TSRMLS_CC); /* Set our reply to FALSE on failure, and the string on success */ if(bulk_resp == NULL) { ZVAL_FALSE(*z_ret); return -1; } else { ZVAL_STRINGL(*z_ret, bulk_resp, size, 0); return 0; } } PHP_REDIS_API int redis_read_multibulk_recursive(RedisSock *redis_sock, int elements, zval **z_ret TSRMLS_DC) { int reply_info; REDIS_REPLY_TYPE reply_type; zval *z_subelem; /* Iterate while we have elements */ while(elements > 0) { /* Attempt to read our reply type */ if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC) < 0) { zend_throw_exception_ex(redis_exception_ce, 0 TSRMLS_CC, "protocol error, couldn't parse MULTI-BULK response\n", reply_type); return -1; } /* Switch on our reply-type byte */ switch(reply_type) { case TYPE_ERR: case TYPE_LINE: ALLOC_INIT_ZVAL(z_subelem); redis_read_variant_line(redis_sock, reply_type, &z_subelem TSRMLS_CC); add_next_index_zval(*z_ret, z_subelem); break; case TYPE_INT: /* Add our long value */ add_next_index_long(*z_ret, reply_info); break; case TYPE_BULK: /* Init a zval for our bulk response, read and add it */ ALLOC_INIT_ZVAL(z_subelem); redis_read_variant_bulk(redis_sock, reply_info, &z_subelem TSRMLS_CC); add_next_index_zval(*z_ret, z_subelem); break; case TYPE_MULTIBULK: /* Construct an array for our sub element, and add it, and recurse */ ALLOC_INIT_ZVAL(z_subelem); array_init(z_subelem); add_next_index_zval(*z_ret, z_subelem); redis_read_multibulk_recursive(redis_sock, reply_info, &z_subelem TSRMLS_CC); break; default: break; /* We know it's not < 0 from previous check */ } /* Decrement our element counter */ elements--; } return 0; } PHP_REDIS_API int redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab) { /* Reply type, and reply size vars */ REDIS_REPLY_TYPE reply_type; int reply_info; /*char *bulk_resp; */ zval *z_ret; /* Attempt to read our header */ if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC) < 0) { return -1; } /* Our return ZVAL */ MAKE_STD_ZVAL(z_ret); /* Switch based on our top level reply type */ switch(reply_type) { case TYPE_ERR: case TYPE_LINE: redis_read_variant_line(redis_sock, reply_type, &z_ret TSRMLS_CC); break; case TYPE_INT: ZVAL_LONG(z_ret, reply_info); break; case TYPE_BULK: redis_read_variant_bulk(redis_sock, reply_info, &z_ret TSRMLS_CC); break; case TYPE_MULTIBULK: /* Initialize an array for our multi-bulk response */ array_init(z_ret); /* If we've got more than zero elements, parse our multi bulk respoinse recursively */ if(reply_info > -1) { redis_read_multibulk_recursive(redis_sock, reply_info, &z_ret TSRMLS_CC); } break; default: /* Protocol error */ zend_throw_exception_ex(redis_exception_ce, 0 TSRMLS_CC, "protocol error, got '%c' as reply-type byte\n", reply_type); return FAILURE; } IF_MULTI_OR_PIPELINE() { add_next_index_zval(z_tab, z_ret); } else { /* Set our return value */ *return_value = *z_ret; zval_copy_ctor(return_value); zval_dtor(z_ret); efree(z_ret); } /* Success */ return 0; } /* vim: set tabstop=4 softtabstop=4 noexpandtab shiftwidth=4: */ void add_constant_long(zend_class_entry *ce, char *name, int value); int integer_length(int i); int redis_cmd_format(char **ret, char *format, ...); int redis_cmd_format_static(char **ret, char *keyword, char *format, ...); int redis_cmd_format_header(char **ret, char *keyword, int arg_count); int redis_cmd_append_str(char **cmd, int cmd_len, char *append, int append_len); int redis_cmd_init_sstr(smart_str *str, int num_args, char *keyword, int keyword_len); int redis_cmd_append_sstr(smart_str *str, char *append, int append_len); int redis_cmd_append_sstr_int(smart_str *str, int append); int redis_cmd_append_sstr_long(smart_str *str, long append); int redis_cmd_append_int(char **cmd, int cmd_len, int append); int redis_cmd_append_sstr_dbl(smart_str *str, double value); PHP_REDIS_API char * redis_sock_read(RedisSock *redis_sock, int *buf_len TSRMLS_DC); PHP_REDIS_API void redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_long_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval* z_tab, void *ctx); typedef void (*SuccessCallback)(RedisSock *redis_sock); PHP_REDIS_API void redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx, SuccessCallback success_callback); PHP_REDIS_API void redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API RedisSock* redis_sock_create(char *host, int host_len, unsigned short port, double timeout, int persistent, char *persistent_id, long retry_interval, zend_bool lazy_connect); PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock TSRMLS_DC); PHP_REDIS_API int redis_sock_server_open(RedisSock *redis_sock, int force_connect TSRMLS_DC); PHP_REDIS_API int redis_sock_disconnect(RedisSock *redis_sock TSRMLS_DC); PHP_REDIS_API zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); PHP_REDIS_API char *redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes TSRMLS_DC); PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *_z_tab, void *ctx); PHP_REDIS_API void redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int count, int unserialize); PHP_REDIS_API int redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_SCAN_TYPE type, long *iter); PHP_REDIS_API int redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz TSRMLS_DC); PHP_REDIS_API void redis_stream_close(RedisSock *redis_sock TSRMLS_DC); PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock TSRMLS_DC); /*PHP_REDIS_API int redis_sock_get(zval *id, RedisSock **redis_sock TSRMLS_DC);*/ PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock); PHP_REDIS_API void redis_send_discard(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); PHP_REDIS_API int redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len); PHP_REDIS_API int redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz TSRMLS_DC); PHP_REDIS_API void redis_stream_close(RedisSock *redis_sock TSRMLS_DC); PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock TSRMLS_DC); /* PHP_REDIS_API int redis_sock_get(zval *id, RedisSock **redis_sock TSRMLS_DC); */ PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock); PHP_REDIS_API void redis_send_discard(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); PHP_REDIS_API int redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len); PHP_REDIS_API int redis_serialize(RedisSock *redis_sock, zval *z, char **val, int *val_len TSRMLS_DC); PHP_REDIS_API int redis_key_prefix(RedisSock *redis_sock, char **key, int *key_len TSRMLS_DC); PHP_REDIS_API int redis_unserialize(RedisSock *redis_sock, const char *val, int val_len, zval **return_value TSRMLS_DC); /* * Variant Read methods, mostly to implement eval */ PHP_REDIS_API int redis_read_reply_type(RedisSock *redis_sock, REDIS_REPLY_TYPE *reply_type, int *reply_info TSRMLS_DC); PHP_REDIS_API int redis_read_variant_line(RedisSock *redis_sock, REDIS_REPLY_TYPE reply_type, zval **z_ret TSRMLS_DC); PHP_REDIS_API int redis_read_variant_bulk(RedisSock *redis_sock, int size, zval **z_ret TSRMLS_DC); PHP_REDIS_API int redis_read_multibulk_recursive(RedisSock *redis_sock, int elements, zval **z_ret TSRMLS_DC); PHP_REDIS_API int redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab); PHP_REDIS_API void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab); #if ZEND_MODULE_API_NO >= 20100000 #define REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, dbl) do { \ char dbl_decsep; \ dbl_decsep = '.'; \ dbl_str = _php_math_number_format_ex(dbl, 16, &dbl_decsep, 1, NULL, 0); \ dbl_len = strlen(dbl_str); \ } while (0); #else #define REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, dbl) \ dbl_str = _php_math_number_format(dbl, 16, '.', '\x00'); \ dbl_len = strlen(dbl_str); #endif /* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2009 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Original author: Alfonso Jimenez <yo@alfonsojimenez.com> | | Maintainer: Nicolas Favre-Felix <n.favre-felix@owlient.eu> | | Maintainer: Michael Grunder <michael.grunder@gmail.com> | | Maintainer: Nasreddine Bouafif <n.bouafif@owlient.eu> | +----------------------------------------------------------------------+ */ #include "common.h" #ifndef PHP_REDIS_H #define PHP_REDIS_H PHP_METHOD(Redis, __construct); PHP_METHOD(Redis, __destruct); PHP_METHOD(Redis, connect); PHP_METHOD(Redis, pconnect); PHP_METHOD(Redis, close); PHP_METHOD(Redis, ping); PHP_METHOD(Redis, echo); PHP_METHOD(Redis, get); PHP_METHOD(Redis, set); PHP_METHOD(Redis, setex); PHP_METHOD(Redis, psetex); PHP_METHOD(Redis, setnx); PHP_METHOD(Redis, getSet); PHP_METHOD(Redis, randomKey); PHP_METHOD(Redis, renameKey); PHP_METHOD(Redis, renameNx); PHP_METHOD(Redis, getMultiple); PHP_METHOD(Redis, exists); PHP_METHOD(Redis, delete); PHP_METHOD(Redis, incr); PHP_METHOD(Redis, incrBy); PHP_METHOD(Redis, incrByFloat); PHP_METHOD(Redis, decr); PHP_METHOD(Redis, decrBy); PHP_METHOD(Redis, type); PHP_METHOD(Redis, append); PHP_METHOD(Redis, getRange); PHP_METHOD(Redis, setRange); PHP_METHOD(Redis, getBit); PHP_METHOD(Redis, setBit); PHP_METHOD(Redis, strlen); PHP_METHOD(Redis, getKeys); PHP_METHOD(Redis, sort); PHP_METHOD(Redis, sortAsc); PHP_METHOD(Redis, sortAscAlpha); PHP_METHOD(Redis, sortDesc); PHP_METHOD(Redis, sortDescAlpha); PHP_METHOD(Redis, lPush); PHP_METHOD(Redis, lPushx); PHP_METHOD(Redis, rPush); PHP_METHOD(Redis, rPushx); PHP_METHOD(Redis, lPop); PHP_METHOD(Redis, rPop); PHP_METHOD(Redis, blPop); PHP_METHOD(Redis, brPop); PHP_METHOD(Redis, lSize); PHP_METHOD(Redis, lRemove); PHP_METHOD(Redis, listTrim); PHP_METHOD(Redis, lGet); PHP_METHOD(Redis, lGetRange); PHP_METHOD(Redis, lSet); PHP_METHOD(Redis, lInsert); PHP_METHOD(Redis, sAdd); PHP_METHOD(Redis, sSize); PHP_METHOD(Redis, sRemove); PHP_METHOD(Redis, sMove); PHP_METHOD(Redis, sPop); PHP_METHOD(Redis, sRandMember); PHP_METHOD(Redis, sContains); PHP_METHOD(Redis, sMembers); PHP_METHOD(Redis, sInter); PHP_METHOD(Redis, sInterStore); PHP_METHOD(Redis, sUnion); PHP_METHOD(Redis, sUnionStore); PHP_METHOD(Redis, sDiff); PHP_METHOD(Redis, sDiffStore); PHP_METHOD(Redis, setTimeout); PHP_METHOD(Redis, pexpire); PHP_METHOD(Redis, save); PHP_METHOD(Redis, bgSave); PHP_METHOD(Redis, lastSave); PHP_METHOD(Redis, flushDB); PHP_METHOD(Redis, flushAll); PHP_METHOD(Redis, dbSize); PHP_METHOD(Redis, auth); PHP_METHOD(Redis, ttl); PHP_METHOD(Redis, pttl); PHP_METHOD(Redis, persist); PHP_METHOD(Redis, info); PHP_METHOD(Redis, resetStat); PHP_METHOD(Redis, select); PHP_METHOD(Redis, move); PHP_METHOD(Redis, zAdd); PHP_METHOD(Redis, zDelete); PHP_METHOD(Redis, zRange); PHP_METHOD(Redis, zReverseRange); PHP_METHOD(Redis, zRangeByScore); PHP_METHOD(Redis, zRangeByLex); PHP_METHOD(Redis, zRevRangeByScore); PHP_METHOD(Redis, zCount); PHP_METHOD(Redis, zDeleteRangeByScore); PHP_METHOD(Redis, zDeleteRangeByRank); PHP_METHOD(Redis, zCard); PHP_METHOD(Redis, zScore); PHP_METHOD(Redis, zRank); PHP_METHOD(Redis, zRevRank); PHP_METHOD(Redis, zIncrBy); PHP_METHOD(Redis, zInter); PHP_METHOD(Redis, zUnion); PHP_METHOD(Redis, expireAt); PHP_METHOD(Redis, pexpireAt); PHP_METHOD(Redis, bgrewriteaof); PHP_METHOD(Redis, slaveof); PHP_METHOD(Redis, object); PHP_METHOD(Redis, bitop); PHP_METHOD(Redis, bitcount); PHP_METHOD(Redis, bitpos); PHP_METHOD(Redis, eval); PHP_METHOD(Redis, evalsha); PHP_METHOD(Redis, script); PHP_METHOD(Redis, debug); PHP_METHOD(Redis, dump); PHP_METHOD(Redis, restore); PHP_METHOD(Redis, migrate); PHP_METHOD(Redis, time); PHP_METHOD(Redis, getLastError); PHP_METHOD(Redis, clearLastError); PHP_METHOD(Redis, _prefix); PHP_METHOD(Redis, _serialize); PHP_METHOD(Redis, _unserialize); PHP_METHOD(Redis, mset); PHP_METHOD(Redis, msetnx); PHP_METHOD(Redis, rpoplpush); PHP_METHOD(Redis, brpoplpush); PHP_METHOD(Redis, hGet); PHP_METHOD(Redis, hSet); PHP_METHOD(Redis, hSetNx); PHP_METHOD(Redis, hDel); PHP_METHOD(Redis, hLen); PHP_METHOD(Redis, hKeys); PHP_METHOD(Redis, hVals); PHP_METHOD(Redis, hGetAll); PHP_METHOD(Redis, hExists); PHP_METHOD(Redis, hIncrBy); PHP_METHOD(Redis, hIncrByFloat); PHP_METHOD(Redis, hMset); PHP_METHOD(Redis, hMget); PHP_METHOD(Redis, multi); PHP_METHOD(Redis, discard); PHP_METHOD(Redis, exec); PHP_METHOD(Redis, watch); PHP_METHOD(Redis, unwatch); PHP_METHOD(Redis, pipeline); PHP_METHOD(Redis, publish); PHP_METHOD(Redis, subscribe); PHP_METHOD(Redis, psubscribe); PHP_METHOD(Redis, unsubscribe); PHP_METHOD(Redis, punsubscribe); PHP_METHOD(Redis, getOption); PHP_METHOD(Redis, setOption); PHP_METHOD(Redis, config); PHP_METHOD(Redis, slowlog); PHP_METHOD(Redis, wait); PHP_METHOD(Redis, pubsub); PHP_METHOD(Redis, client); /* SCAN and friends */ PHP_METHOD(Redis, scan); PHP_METHOD(Redis, hscan); PHP_METHOD(Redis, sscan); PHP_METHOD(Redis, zscan); /* HyperLogLog commands */ PHP_METHOD(Redis, pfadd); PHP_METHOD(Redis, pfcount); PHP_METHOD(Redis, pfmerge); /* Reflection */ PHP_METHOD(Redis, getHost); PHP_METHOD(Redis, getPort); PHP_METHOD(Redis, getDBNum); PHP_METHOD(Redis, getTimeout); PHP_METHOD(Redis, getReadTimeout); PHP_METHOD(Redis, isConnected); PHP_METHOD(Redis, getPersistentID); PHP_METHOD(Redis, getAuth); PHP_METHOD(Redis, getMode); PHP_METHOD(Redis, rawCommand); #ifdef PHP_WIN32 #define PHP_REDIS_API __declspec(dllexport) #else #define PHP_REDIS_API #endif #ifdef ZTS #include "TSRM.h" #endif PHP_MINIT_FUNCTION(redis); PHP_MSHUTDOWN_FUNCTION(redis); PHP_RINIT_FUNCTION(redis); PHP_RSHUTDOWN_FUNCTION(redis); PHP_MINFO_FUNCTION(redis); PHP_REDIS_API int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent); PHP_REDIS_API void redis_atomic_increment(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int count); PHP_REDIS_API int generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len, int min_argc, RedisSock **redis_sock, int has_timeout, int all_keys, int can_serialize); PHP_REDIS_API void generic_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sort, int use_alpha); typedef void (*ResultCallback)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void generic_empty_cmd_impl(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ResultCallback result_callback); PHP_REDIS_API void generic_empty_cmd(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ...); PHP_REDIS_API void generic_empty_long_cmd(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ...); PHP_REDIS_API void generic_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sub_cmd); PHP_REDIS_API void generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *unsub_cmd); PHP_REDIS_API int redis_response_enqueued(RedisSock *redis_sock TSRMLS_DC); PHP_REDIS_API int get_flag(zval *object TSRMLS_DC); PHP_REDIS_API void set_flag(zval *object, int new_flag TSRMLS_DC); PHP_REDIS_API int redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int numElems); /* pipeline */ PHP_REDIS_API request_item* get_pipeline_head(zval *object); PHP_REDIS_API void set_pipeline_head(zval *object, request_item *head); PHP_REDIS_API request_item* get_pipeline_current(zval *object); PHP_REDIS_API void set_pipeline_current(zval *object, request_item *current); #ifndef _MSC_VER ZEND_BEGIN_MODULE_GLOBALS(redis) ZEND_END_MODULE_GLOBALS(redis) #endif struct redis_queued_item { /* reading function */ zval * (*fun)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, ...); char *cmd; int cmd_len; struct redis_queued_item *next; }; extern zend_module_entry redis_module_entry; #define redis_module_ptr &redis_module_entry #define phpext_redis_ptr redis_module_ptr #define PHP_REDIS_VERSION "2.2.7" #endif /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */ /* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2009 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Nicolas Favre-Felix <n.favre-felix@owlient.eu> | | Maintainer: Michael Grunder <michael.grunder@gmail.com> | +----------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "common.h" #include "ext/standard/info.h" #include "php_ini.h" #include "php_redis.h" #include "redis_array.h" #include <zend_exceptions.h> #include "library.h" #include "redis_array.h" #include "redis_array_impl.h" /* Simple macro to detect failure in a RedisArray call */ #define RA_CALL_FAILED(rv, cmd) \ ((Z_TYPE_P(rv) == IS_BOOL && Z_BVAL_P(rv) == 0) || \ (Z_TYPE_P(rv) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(rv)) == 0) || \ (Z_TYPE_P(rv) == IS_LONG && Z_LVAL_P(rv) == 0 && !strcasecmp(cmd, "TYPE"))) \ extern zend_class_entry *redis_ce; zend_class_entry *redis_array_ce; ZEND_BEGIN_ARG_INFO_EX(__redis_array_call_args, 0, 0, 2) ZEND_ARG_INFO(0, function_name) ZEND_ARG_INFO(0, arguments) ZEND_END_ARG_INFO() zend_function_entry redis_array_functions[] = { PHP_ME(RedisArray, __construct, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, __call, __redis_array_call_args, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, _hosts, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, _target, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, _instance, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, _function, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, _distributor, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, _rehash, NULL, ZEND_ACC_PUBLIC) /* special implementation for a few functions */ PHP_ME(RedisArray, select, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, info, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, ping, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, flushdb, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, flushall, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, mget, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, mset, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, del, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, getOption, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, setOption, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, keys, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, save, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, bgsave, NULL, ZEND_ACC_PUBLIC) /* Multi/Exec */ PHP_ME(RedisArray, multi, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, exec, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, discard, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, unwatch, NULL, ZEND_ACC_PUBLIC) /* Aliases */ PHP_MALIAS(RedisArray, delete, del, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(RedisArray, getMultiple, mget, NULL, ZEND_ACC_PUBLIC) {NULL, NULL, NULL} }; static void redis_array_free(RedisArray *ra) { int i; /* Redis objects */ for(i=0;i<ra->count;i++) { zval_dtor(ra->redis[i]); efree(ra->redis[i]); efree(ra->hosts[i]); } efree(ra->redis); efree(ra->hosts); /* delete hash function */ if(ra->z_fun) { zval_dtor(ra->z_fun); efree(ra->z_fun); } /* Distributor */ if(ra->z_dist) { zval_dtor(ra->z_dist); efree(ra->z_dist); } /* Delete pur commands */ zval_dtor(ra->z_pure_cmds); efree(ra->z_pure_cmds); /* Free structure itself */ efree(ra); } int le_redis_array; void redis_destructor_redis_array(zend_rsrc_list_entry * rsrc TSRMLS_DC) { RedisArray *ra = (RedisArray*)rsrc->ptr; /* Free previous ring if it's set */ if(ra->prev) redis_array_free(ra->prev); /* Free parent array */ redis_array_free(ra); } /** * redis_array_get */ PHP_REDIS_API int redis_array_get(zval *id, RedisArray **ra TSRMLS_DC) { zval **socket; int resource_type; if (Z_TYPE_P(id) != IS_OBJECT || zend_hash_find(Z_OBJPROP_P(id), "socket", sizeof("socket"), (void **) &socket) == FAILURE) { return -1; } *ra = (RedisArray *) zend_list_find(Z_LVAL_PP(socket), &resource_type); if (!*ra || resource_type != le_redis_array) { return -1; } return Z_LVAL_PP(socket); } uint32_t rcrc32(const char *s, size_t sz) { static const uint32_t table[256] = { 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535, 0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD, 0xE7B82D07,0x90BF1D91,0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D, 0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC, 0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,0x3B6E20C8,0x4C69105E,0xD56041E4, 0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C, 0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,0x26D930AC, 0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F, 0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB, 0xB6662D3D,0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F, 0x9FBFE4A5,0xE8B8D433,0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB, 0x086D3D2D,0x91646C97,0xE6635C01,0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E, 0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950,0x8BBEB8EA, 0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,0x4DB26158,0x3AB551CE, 0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,0x4369E96A, 0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9, 0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409, 0xCE61E49F,0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81, 0xB7BD5C3B,0xC0BA6CAD,0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739, 0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8, 0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,0xF00F9344,0x8708A3D2,0x1E01F268, 0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0, 0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,0xD6D6A3E8, 0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B, 0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF, 0x4669BE79,0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703, 0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7, 0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A, 0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE, 0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,0x86D3D2D4,0xF1D4E242, 0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6, 0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D, 0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5, 0x47B2CF7F,0x30B5FFE9,0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605, 0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94, 0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D}; unsigned long ret = 0xffffffff; size_t i; for (i = 0; i < sz; i++) { ret = (ret >> 8) ^ table[ (ret ^ ((unsigned char)s[i])) & 0xFF ]; } return (ret ^ 0xFFFFFFFF); } /* {{{ proto RedisArray RedisArray::__construct() Public constructor */ PHP_METHOD(RedisArray, __construct) { zval *z0, *z_fun = NULL, *z_dist = NULL, **zpData, *z_opts = NULL; int id; RedisArray *ra = NULL; zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0; HashTable *hPrev = NULL, *hOpts = NULL; long l_retry_interval = 0; zend_bool b_lazy_connect = 0; double d_connect_timeout = 0; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|a", &z0, &z_opts) == FAILURE) { RETURN_FALSE; } /* extract options */ if(z_opts) { zval **z_retry_interval_pp; zval **z_connect_timeout_pp; hOpts = Z_ARRVAL_P(z_opts); /* extract previous ring. */ if(FAILURE != zend_hash_find(hOpts, "previous", sizeof("previous"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_PP(zpData)) != 0) { /* consider previous array as non-existent if empty. */ hPrev = Z_ARRVAL_PP(zpData); } /* extract function name. */ if(FAILURE != zend_hash_find(hOpts, "function", sizeof("function"), (void**)&zpData)) { MAKE_STD_ZVAL(z_fun); *z_fun = **zpData; zval_copy_ctor(z_fun); } /* extract function name. */ if(FAILURE != zend_hash_find(hOpts, "distributor", sizeof("distributor"), (void**)&zpData)) { MAKE_STD_ZVAL(z_dist); *z_dist = **zpData; zval_copy_ctor(z_dist); } /* extract index option. */ if(FAILURE != zend_hash_find(hOpts, "index", sizeof("index"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) { b_index = Z_BVAL_PP(zpData); } /* extract autorehash option. */ if(FAILURE != zend_hash_find(hOpts, "autorehash", sizeof("autorehash"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) { b_autorehash = Z_BVAL_PP(zpData); } /* pconnect */ if(FAILURE != zend_hash_find(hOpts, "pconnect", sizeof("pconnect"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) { b_pconnect = Z_BVAL_PP(zpData); } /* extract retry_interval option. */ if (FAILURE != zend_hash_find(hOpts, "retry_interval", sizeof("retry_interval"), (void**)&z_retry_interval_pp)) { if (Z_TYPE_PP(z_retry_interval_pp) == IS_LONG || Z_TYPE_PP(z_retry_interval_pp) == IS_STRING) { if (Z_TYPE_PP(z_retry_interval_pp) == IS_LONG) { l_retry_interval = Z_LVAL_PP(z_retry_interval_pp); } else { l_retry_interval = atol(Z_STRVAL_PP(z_retry_interval_pp)); } } } /* extract lazy connect option. */ if(FAILURE != zend_hash_find(hOpts, "lazy_connect", sizeof("lazy_connect"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) { b_lazy_connect = Z_BVAL_PP(zpData); } /* extract connect_timeout option */ if (FAILURE != zend_hash_find(hOpts, "connect_timeout", sizeof("connect_timeout"), (void**)&z_connect_timeout_pp)) { if (Z_TYPE_PP(z_connect_timeout_pp) == IS_DOUBLE || Z_TYPE_PP(z_connect_timeout_pp) == IS_STRING || Z_TYPE_PP(z_connect_timeout_pp) == IS_LONG) { if (Z_TYPE_PP(z_connect_timeout_pp) == IS_DOUBLE) { d_connect_timeout = Z_DVAL_PP(z_connect_timeout_pp); } else if (Z_TYPE_PP(z_connect_timeout_pp) == IS_LONG) { d_connect_timeout = Z_LVAL_PP(z_connect_timeout_pp); } else { d_connect_timeout = atof(Z_STRVAL_PP(z_connect_timeout_pp)); } } } } /* extract either name of list of hosts from z0 */ switch(Z_TYPE_P(z0)) { case IS_STRING: ra = ra_load_array(Z_STRVAL_P(z0) TSRMLS_CC); break; case IS_ARRAY: ra = ra_make_array(Z_ARRVAL_P(z0), z_fun, z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout TSRMLS_CC); break; default: WRONG_PARAM_COUNT; break; } if(ra) { ra->auto_rehash = b_autorehash; ra->connect_timeout = d_connect_timeout; if(ra->prev) ra->prev->auto_rehash = b_autorehash; #if PHP_VERSION_ID >= 50400 id = zend_list_insert(ra, le_redis_array TSRMLS_CC); #else id = zend_list_insert(ra, le_redis_array); #endif add_property_resource(getThis(), "socket", id); } } static void ra_forward_call(INTERNAL_FUNCTION_PARAMETERS, RedisArray *ra, const char *cmd, int cmd_len, zval *z_args, zval *z_new_target) { zval **zp_tmp, z_tmp; char *key = NULL; /* set to avoid "unused-but-set-variable" */ int key_len; int i; zval *redis_inst; zval z_fun, **z_callargs; HashPosition pointer; HashTable *h_args; int argc; zend_bool b_write_cmd = 0; h_args = Z_ARRVAL_P(z_args); argc = zend_hash_num_elements(h_args); if(ra->z_multi_exec) { redis_inst = ra->z_multi_exec; /* we already have the instance */ } else { /* extract key and hash it. */ if(!(key = ra_find_key(ra, z_args, cmd, &key_len))) { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not find key"); RETURN_FALSE; } /* find node */ redis_inst = ra_find_node(ra, key, key_len, NULL TSRMLS_CC); if(!redis_inst) { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not find any redis servers for this key."); RETURN_FALSE; } } /* check if write cmd */ b_write_cmd = ra_is_write_cmd(ra, cmd, cmd_len); if(ra->index && b_write_cmd && !ra->z_multi_exec) { /* add MULTI + SADD */ ra_index_multi(redis_inst, MULTI TSRMLS_CC); } /* pass call through */ ZVAL_STRING(&z_fun, cmd, 0); /* method name */ z_callargs = emalloc(argc * sizeof(zval*)); /* copy args to array */ for (i = 0, zend_hash_internal_pointer_reset_ex(h_args, &pointer); zend_hash_get_current_data_ex(h_args, (void**) &zp_tmp, &pointer) == SUCCESS; ++i, zend_hash_move_forward_ex(h_args, &pointer)) { z_callargs[i] = *zp_tmp; } /* multi/exec */ if(ra->z_multi_exec) { call_user_function(&redis_ce->function_table, &ra->z_multi_exec, &z_fun, return_value, argc, z_callargs TSRMLS_CC); efree(z_callargs); RETURN_ZVAL(getThis(), 1, 0); } /* CALL! */ if(ra->index && b_write_cmd) { /* call using discarded temp value and extract exec results after. */ call_user_function(&redis_ce->function_table, &redis_inst, &z_fun, &z_tmp, argc, z_callargs TSRMLS_CC); zval_dtor(&z_tmp); /* add keys to index. */ ra_index_key(key, key_len, redis_inst TSRMLS_CC); /* call EXEC */ ra_index_exec(redis_inst, return_value, 0 TSRMLS_CC); } else { /* call directly through. */ call_user_function(&redis_ce->function_table, &redis_inst, &z_fun, return_value, argc, z_callargs TSRMLS_CC); /* check if we have an error. */ if(RA_CALL_FAILED(return_value,cmd) && ra->prev && !b_write_cmd) { /* there was an error reading, try with prev ring. */ /* ERROR, FALLBACK TO PREVIOUS RING and forward a reference to the first redis instance we were looking at. */ ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra->prev, cmd, cmd_len, z_args, z_new_target?z_new_target:redis_inst); } /* Autorehash if the key was found on the previous node if this is a read command and auto rehashing is on */ if(!RA_CALL_FAILED(return_value,cmd) && !b_write_cmd && z_new_target && ra->auto_rehash) { /* move key from old ring to new ring */ ra_move_key(key, key_len, redis_inst, z_new_target TSRMLS_CC); } } /* cleanup */ efree(z_callargs); } PHP_METHOD(RedisArray, __call) { zval *object; RedisArray *ra; zval *z_args; char *cmd; int cmd_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa", &object, redis_array_ce, &cmd, &cmd_len, &z_args) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra, cmd, cmd_len, z_args, NULL); } PHP_METHOD(RedisArray, _hosts) { zval *object; int i; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_array_ce) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } array_init(return_value); for(i = 0; i < ra->count; ++i) { add_next_index_string(return_value, ra->hosts[i], 1); } } PHP_METHOD(RedisArray, _target) { zval *object; RedisArray *ra; char *key; int key_len, i; zval *redis_inst; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_array_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } redis_inst = ra_find_node(ra, key, key_len, &i TSRMLS_CC); if(redis_inst) { ZVAL_STRING(return_value, ra->hosts[i], 1); } else { RETURN_NULL(); } } PHP_METHOD(RedisArray, _instance) { zval *object; RedisArray *ra; char *target; int target_len; zval *z_redis; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_array_ce, &target, &target_len) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } z_redis = ra_find_node_by_name(ra, target, target_len TSRMLS_CC); if(z_redis) { RETURN_ZVAL(z_redis, 1, 0); } else { RETURN_NULL(); } } PHP_METHOD(RedisArray, _function) { zval *object; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_array_ce) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } if(ra->z_fun) { *return_value = *ra->z_fun; zval_copy_ctor(return_value); } else { RETURN_NULL(); } } PHP_METHOD(RedisArray, _distributor) { zval *object; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_array_ce) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } if(ra->z_fun) { *return_value = *ra->z_fun; zval_copy_ctor(return_value); } else { RETURN_NULL(); } } PHP_METHOD(RedisArray, _rehash) { zval *object; RedisArray *ra; zend_fcall_info z_cb; zend_fcall_info_cache z_cb_cache; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|f", &object, redis_array_ce, &z_cb, &z_cb_cache) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } if (ZEND_NUM_ARGS() == 0) { ra_rehash(ra, NULL, NULL TSRMLS_CC); } else { ra_rehash(ra, &z_cb, &z_cb_cache TSRMLS_CC); } } static void multihost_distribute(INTERNAL_FUNCTION_PARAMETERS, const char *method_name) { zval *object, z_fun, *z_tmp; int i; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_array_ce) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } /* prepare call */ ZVAL_STRING(&z_fun, method_name, 0); array_init(return_value); for(i = 0; i < ra->count; ++i) { MAKE_STD_ZVAL(z_tmp); /* Call each node in turn */ call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 0, NULL TSRMLS_CC); add_assoc_zval(return_value, ra->hosts[i], z_tmp); } } PHP_METHOD(RedisArray, info) { multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "INFO"); } PHP_METHOD(RedisArray, ping) { multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PING"); } PHP_METHOD(RedisArray, flushdb) { multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHDB"); } PHP_METHOD(RedisArray, flushall) { multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHALL"); } PHP_METHOD(RedisArray, save) { multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SAVE"); } PHP_METHOD(RedisArray, bgsave) { multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "BGSAVE"); } PHP_METHOD(RedisArray, keys) { zval *object, *z_args[1], *z_tmp, z_fun; RedisArray *ra; char *pattern; int pattern_len, i; /* Make sure the prototype is correct */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_array_ce, &pattern, &pattern_len) == FAILURE) { RETURN_FALSE; } /* Make sure we can grab our RedisArray object */ if(redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } /* Set up our function call (KEYS) */ ZVAL_STRING(&z_fun, "KEYS", 0); /* We will be passing with one string argument (the pattern) */ MAKE_STD_ZVAL(z_args[0]); ZVAL_STRINGL(z_args[0], pattern, pattern_len, 0); /* Init our array return */ array_init(return_value); /* Iterate our RedisArray nodes */ for(i=0; i<ra->count; ++i) { /* Return for this node */ MAKE_STD_ZVAL(z_tmp); /* Call KEYS on each node */ call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 1, z_args TSRMLS_CC); /* Add the result for this host */ add_assoc_zval(return_value, ra->hosts[i], z_tmp); } /* Free arg array */ efree(z_args[0]); } PHP_METHOD(RedisArray, getOption) { zval *object, z_fun, *z_tmp, *z_args[1]; int i; RedisArray *ra; long opt; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", &object, redis_array_ce, &opt) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } /* prepare call */ ZVAL_STRING(&z_fun, "getOption", 0); /* copy arg */ MAKE_STD_ZVAL(z_args[0]); ZVAL_LONG(z_args[0], opt); array_init(return_value); for(i = 0; i < ra->count; ++i) { MAKE_STD_ZVAL(z_tmp); /* Call each node in turn */ call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 1, z_args TSRMLS_CC); add_assoc_zval(return_value, ra->hosts[i], z_tmp); } /* cleanup */ efree(z_args[0]); } PHP_METHOD(RedisArray, setOption) { zval *object, z_fun, *z_tmp, *z_args[2]; int i; RedisArray *ra; long opt; char *val_str; int val_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ols", &object, redis_array_ce, &opt, &val_str, &val_len) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } /* prepare call */ ZVAL_STRING(&z_fun, "setOption", 0); /* copy args */ MAKE_STD_ZVAL(z_args[0]); ZVAL_LONG(z_args[0], opt); MAKE_STD_ZVAL(z_args[1]); ZVAL_STRINGL(z_args[1], val_str, val_len, 0); array_init(return_value); for(i = 0; i < ra->count; ++i) { MAKE_STD_ZVAL(z_tmp); /* Call each node in turn */ call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 2, z_args TSRMLS_CC); add_assoc_zval(return_value, ra->hosts[i], z_tmp); } /* cleanup */ efree(z_args[0]); efree(z_args[1]); } PHP_METHOD(RedisArray, select) { zval *object, z_fun, *z_tmp, *z_args[2]; int i; RedisArray *ra; long opt; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", &object, redis_array_ce, &opt) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } /* prepare call */ ZVAL_STRING(&z_fun, "select", 0); /* copy args */ MAKE_STD_ZVAL(z_args[0]); ZVAL_LONG(z_args[0], opt); array_init(return_value); for(i = 0; i < ra->count; ++i) { MAKE_STD_ZVAL(z_tmp); /* Call each node in turn */ call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 1, z_args TSRMLS_CC); add_assoc_zval(return_value, ra->hosts[i], z_tmp); } /* cleanup */ efree(z_args[0]); } #define HANDLE_MULTI_EXEC(cmd) do {\ if (redis_array_get(getThis(), &ra TSRMLS_CC) >= 0 && ra->z_multi_exec) {\ int i, num_varargs;\ zval ***varargs = NULL;\ zval z_arg_array;\ if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O*",\ &object, redis_array_ce, &varargs, &num_varargs) == FAILURE) {\ RETURN_FALSE;\ }\ /* copy all args into a zval hash table */\ array_init(&z_arg_array);\ for(i = 0; i < num_varargs; ++i) {\ zval *z_tmp;\ MAKE_STD_ZVAL(z_tmp);\ *z_tmp = **varargs[i];\ zval_copy_ctor(z_tmp);\ INIT_PZVAL(z_tmp);\ add_next_index_zval(&z_arg_array, z_tmp);\ }\ /* call */\ ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra, cmd, sizeof(cmd)-1, &z_arg_array, NULL);\ zval_dtor(&z_arg_array);\ if(varargs) {\ efree(varargs);\ }\ return;\ }\ }while(0) /* MGET will distribute the call to several nodes and regroup the values. */ PHP_METHOD(RedisArray, mget) { zval *object, *z_keys, z_fun, *z_argarray, **data, *z_ret, **z_cur, *z_tmp_array, *z_tmp; int i, j, n; RedisArray *ra; int *pos, argc, *argc_each; HashTable *h_keys; HashPosition pointer; zval **redis_instances, **argv; /* Multi/exec support */ HANDLE_MULTI_EXEC("MGET"); if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", &object, redis_array_ce, &z_keys) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } /* prepare call */ ZVAL_STRING(&z_fun, "MGET", 0); /* init data structures */ h_keys = Z_ARRVAL_P(z_keys); argc = zend_hash_num_elements(h_keys); argv = emalloc(argc * sizeof(zval*)); pos = emalloc(argc * sizeof(int)); redis_instances = emalloc(argc * sizeof(zval*)); memset(redis_instances, 0, argc * sizeof(zval*)); argc_each = emalloc(ra->count * sizeof(int)); memset(argc_each, 0, ra->count * sizeof(int)); /* associate each key to a redis node */ for (i = 0, zend_hash_internal_pointer_reset_ex(h_keys, &pointer); zend_hash_get_current_data_ex(h_keys, (void**) &data, &pointer) == SUCCESS; zend_hash_move_forward_ex(h_keys, &pointer), ++i) { /* If we need to represent a long key as a string */ unsigned int key_len; char kbuf[40], *key_lookup; /* phpredis proper can only use string or long keys, so restrict to that here */ if(Z_TYPE_PP(data) != IS_STRING && Z_TYPE_PP(data) != IS_LONG) { php_error_docref(NULL TSRMLS_CC, E_ERROR, "MGET: all keys must be strings or longs"); efree(argv); efree(pos); efree(redis_instances); efree(argc_each); RETURN_FALSE; } /* Convert to a string for hash lookup if it isn't one */ if(Z_TYPE_PP(data) == IS_STRING) { key_len = Z_STRLEN_PP(data); key_lookup = Z_STRVAL_PP(data); } else { key_len = snprintf(kbuf, sizeof(kbuf), "%ld", Z_LVAL_PP(data)); key_lookup = (char*)kbuf; } /* Find our node */ redis_instances[i] = ra_find_node(ra, key_lookup, key_len, &pos[i] TSRMLS_CC); argc_each[pos[i]]++; /* count number of keys per node */ argv[i] = *data; } /* prepare return value */ array_init(return_value); MAKE_STD_ZVAL(z_tmp_array); array_init(z_tmp_array); /* calls */ for(n = 0; n < ra->count; ++n) { /* for each node */ /* We don't even need to make a call to this node if no keys go there */ if(!argc_each[n]) continue; /* copy args for MGET call on node. */ MAKE_STD_ZVAL(z_argarray); array_init(z_argarray); for(i = 0; i < argc; ++i) { if(pos[i] != n) continue; MAKE_STD_ZVAL(z_tmp); *z_tmp = *argv[i]; zval_copy_ctor(z_tmp); INIT_PZVAL(z_tmp); add_next_index_zval(z_argarray, z_tmp); } /* call MGET on the node */ MAKE_STD_ZVAL(z_ret); call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, z_ret, 1, &z_argarray TSRMLS_CC); /* cleanup args array */ zval_ptr_dtor(&z_argarray); for(i = 0, j = 0; i < argc; ++i) { /* Error out if we didn't get a proper response */ if(Z_TYPE_P(z_ret) != IS_ARRAY) { /* cleanup */ zval_dtor(z_ret); efree(z_ret); zval_ptr_dtor(&z_tmp_array); efree(pos); efree(redis_instances); efree(argc_each); /* failure */ RETURN_FALSE; } if(pos[i] != n) continue; zend_hash_quick_find(Z_ARRVAL_P(z_ret), NULL, 0, j, (void**)&z_cur); j++; MAKE_STD_ZVAL(z_tmp); *z_tmp = **z_cur; zval_copy_ctor(z_tmp); INIT_PZVAL(z_tmp); add_index_zval(z_tmp_array, i, z_tmp); } zval_dtor(z_ret); efree(z_ret); } /* copy temp array in the right order to return_value */ for(i = 0; i < argc; ++i) { zend_hash_quick_find(Z_ARRVAL_P(z_tmp_array), NULL, 0, i, (void**)&z_cur); MAKE_STD_ZVAL(z_tmp); *z_tmp = **z_cur; zval_copy_ctor(z_tmp); INIT_PZVAL(z_tmp); add_next_index_zval(return_value, z_tmp); } /* cleanup */ zval_ptr_dtor(&z_tmp_array); efree(argv); efree(pos); efree(redis_instances); efree(argc_each); } /* MSET will distribute the call to several nodes and regroup the values. */ PHP_METHOD(RedisArray, mset) { zval *object, *z_keys, z_fun, *z_argarray, **data, z_ret; int i, n; RedisArray *ra; int *pos, argc, *argc_each; HashTable *h_keys; zval **redis_instances, *redis_inst, **argv; char *key, **keys, **key_free, kbuf[40]; unsigned int key_len, free_idx = 0; int type, *key_lens; unsigned long idx; /* Multi/exec support */ HANDLE_MULTI_EXEC("MSET"); if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", &object, redis_array_ce, &z_keys) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } /* init data structures */ h_keys = Z_ARRVAL_P(z_keys); argc = zend_hash_num_elements(h_keys); argv = emalloc(argc * sizeof(zval*)); pos = emalloc(argc * sizeof(int)); keys = emalloc(argc * sizeof(char*)); key_lens = emalloc(argc * sizeof(int)); redis_instances = emalloc(argc * sizeof(zval*)); memset(redis_instances, 0, argc * sizeof(zval*)); /* Allocate an array holding the indexes of any keys that need freeing */ key_free = emalloc(argc * sizeof(char*)); argc_each = emalloc(ra->count * sizeof(int)); memset(argc_each, 0, ra->count * sizeof(int)); /* associate each key to a redis node */ for(i = 0, zend_hash_internal_pointer_reset(h_keys); zend_hash_has_more_elements(h_keys) == SUCCESS; zend_hash_move_forward(h_keys), i++) { /* We have to skip the element if we can't get the array value */ if(zend_hash_get_current_data(h_keys, (void**)&data) == FAILURE) { continue; } /* Grab our key */ type = zend_hash_get_current_key_ex(h_keys, &key, &key_len, &idx, 0, NULL); /* If the key isn't a string, make a string representation of it */ if(type != HASH_KEY_IS_STRING) { key_len = snprintf(kbuf, sizeof(kbuf), "%ld", (long)idx); key = estrndup(kbuf, key_len); key_free[free_idx++]=key; } else { key_len--; /* We don't want the null terminator */ } redis_instances[i] = ra_find_node(ra, key, (int)key_len, &pos[i] TSRMLS_CC); argc_each[pos[i]]++; /* count number of keys per node */ argv[i] = *data; keys[i] = key; key_lens[i] = (int)key_len; } /* calls */ for(n = 0; n < ra->count; ++n) { /* for each node */ int found = 0; /* prepare call */ ZVAL_STRING(&z_fun, "MSET", 0); redis_inst = ra->redis[n]; /* copy args */ MAKE_STD_ZVAL(z_argarray); array_init(z_argarray); for(i = 0; i < argc; ++i) { zval *z_tmp; if(pos[i] != n) continue; ALLOC_ZVAL(z_tmp); *z_tmp = *argv[i]; zval_copy_ctor(z_tmp); INIT_PZVAL(z_tmp); add_assoc_zval_ex(z_argarray, keys[i], key_lens[i] + 1, z_tmp); /* +1 to count the \0 here */ found++; } if(!found) { zval_dtor(z_argarray); efree(z_argarray); continue; /* don't run empty MSETs */ } if(ra->index) { /* add MULTI */ ra_index_multi(redis_inst, MULTI TSRMLS_CC); } /* call */ call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_argarray TSRMLS_CC); if(ra->index) { ra_index_keys(z_argarray, redis_inst TSRMLS_CC); /* use SADD to add keys to node index */ ra_index_exec(redis_inst, NULL, 0 TSRMLS_CC); /* run EXEC */ } zval_dtor(&z_ret); zval_ptr_dtor(&z_argarray); } /* Free any keys that we needed to allocate memory for, because they weren't strings */ for(i=0; i<free_idx; i++) { efree(key_free[i]); } /* cleanup */ efree(keys); efree(key_free); efree(key_lens); efree(argv); efree(pos); efree(redis_instances); efree(argc_each); RETURN_TRUE; } /* DEL will distribute the call to several nodes and regroup the values. */ PHP_METHOD(RedisArray, del) { zval *object, *z_keys, z_fun, *z_argarray, **data, *z_ret, *z_tmp, **z_args; int i, n; RedisArray *ra; int *pos, argc, *argc_each; HashTable *h_keys; HashPosition pointer; zval **redis_instances, *redis_inst, **argv; long total = 0; int free_zkeys = 0; /* Multi/exec support */ HANDLE_MULTI_EXEC("DEL"); /* get all args in z_args */ z_args = emalloc(ZEND_NUM_ARGS() * sizeof(zval*)); if(zend_get_parameters_array(ht, ZEND_NUM_ARGS(), z_args) == FAILURE) { efree(z_args); RETURN_FALSE; } /* if single array arg, point z_keys to it. */ if(ZEND_NUM_ARGS() == 1 && Z_TYPE_P(z_args[0]) == IS_ARRAY) { z_keys = z_args[0]; } else { /* copy all elements to z_keys */ MAKE_STD_ZVAL(z_keys); array_init(z_keys); free_zkeys = 1; for(i = 0; i < ZEND_NUM_ARGS(); ++i) { MAKE_STD_ZVAL(z_tmp); *z_tmp = *z_args[i]; zval_copy_ctor(z_tmp); INIT_PZVAL(z_tmp); /* add copy to z_keys */ add_next_index_zval(z_keys, z_tmp); } } if (redis_array_get(getThis(), &ra TSRMLS_CC) < 0) { RETURN_FALSE; } /* prepare call */ ZVAL_STRING(&z_fun, "DEL", 0); /* init data structures */ h_keys = Z_ARRVAL_P(z_keys); argc = zend_hash_num_elements(h_keys); argv = emalloc(argc * sizeof(zval*)); pos = emalloc(argc * sizeof(int)); redis_instances = emalloc(argc * sizeof(zval*)); memset(redis_instances, 0, argc * sizeof(zval*)); argc_each = emalloc(ra->count * sizeof(int)); memset(argc_each, 0, ra->count * sizeof(int)); /* associate each key to a redis node */ for (i = 0, zend_hash_internal_pointer_reset_ex(h_keys, &pointer); zend_hash_get_current_data_ex(h_keys, (void**) &data, &pointer) == SUCCESS; zend_hash_move_forward_ex(h_keys, &pointer), ++i) { if (Z_TYPE_PP(data) != IS_STRING) { php_error_docref(NULL TSRMLS_CC, E_ERROR, "DEL: all keys must be string."); efree(pos); RETURN_FALSE; } redis_instances[i] = ra_find_node(ra, Z_STRVAL_PP(data), Z_STRLEN_PP(data), &pos[i] TSRMLS_CC); argc_each[pos[i]]++; /* count number of keys per node */ argv[i] = *data; } /* calls */ for(n = 0; n < ra->count; ++n) { /* for each node */ int found = 0; redis_inst = ra->redis[n]; /* copy args */ MAKE_STD_ZVAL(z_argarray); array_init(z_argarray); for(i = 0; i < argc; ++i) { if(pos[i] != n) continue; MAKE_STD_ZVAL(z_tmp); *z_tmp = *argv[i]; zval_copy_ctor(z_tmp); INIT_PZVAL(z_tmp); add_next_index_zval(z_argarray, z_tmp); found++; } if(!found) { /* don't run empty DELs */ zval_dtor(z_argarray); efree(z_argarray); continue; } if(ra->index) { /* add MULTI */ ra_index_multi(redis_inst, MULTI TSRMLS_CC); } /* call */ MAKE_STD_ZVAL(z_ret); call_user_function(&redis_ce->function_table, &redis_inst, &z_fun, z_ret, 1, &z_argarray TSRMLS_CC); if(ra->index) { ra_index_del(z_argarray, redis_inst TSRMLS_CC); /* use SREM to remove keys from node index */ ra_index_exec(redis_inst, z_tmp, 0 TSRMLS_CC); /* run EXEC */ total += Z_LVAL_P(z_tmp); /* increment total from multi/exec block */ } else { total += Z_LVAL_P(z_ret); /* increment total from single command */ } zval_dtor(z_ret); efree(z_ret); zval_dtor(z_argarray); efree(z_argarray); } /* cleanup */ efree(argv); efree(pos); efree(redis_instances); efree(argc_each); if(free_zkeys) { zval_dtor(z_keys); efree(z_keys); } efree(z_args); RETURN_LONG(total); } PHP_METHOD(RedisArray, multi) { zval *object; RedisArray *ra; zval *z_redis; char *host; int host_len; long multi_value = MULTI; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", &object, redis_array_ce, &host, &host_len, &multi_value) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0) { RETURN_FALSE; } /* find node */ z_redis = ra_find_node_by_name(ra, host, host_len TSRMLS_CC); if(!z_redis) { RETURN_FALSE; } if(multi_value != MULTI && multi_value != PIPELINE) { RETURN_FALSE; } /* save multi object */ ra->z_multi_exec = z_redis; /* switch redis instance to multi/exec mode. */ ra_index_multi(z_redis, multi_value TSRMLS_CC); /* return this. */ RETURN_ZVAL(object, 1, 0); } PHP_METHOD(RedisArray, exec) { zval *object; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_array_ce) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0 || !ra->z_multi_exec) { RETURN_FALSE; } /* switch redis instance out of multi/exec mode. */ ra_index_exec(ra->z_multi_exec, return_value, 1 TSRMLS_CC); /* remove multi object */ ra->z_multi_exec = NULL; } PHP_METHOD(RedisArray, discard) { zval *object; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_array_ce) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0 || !ra->z_multi_exec) { RETURN_FALSE; } /* switch redis instance out of multi/exec mode. */ ra_index_discard(ra->z_multi_exec, return_value TSRMLS_CC); /* remove multi object */ ra->z_multi_exec = NULL; } PHP_METHOD(RedisArray, unwatch) { zval *object; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_array_ce) == FAILURE) { RETURN_FALSE; } if (redis_array_get(object, &ra TSRMLS_CC) < 0 || !ra->z_multi_exec) { RETURN_FALSE; } /* unwatch keys, stay in multi/exec mode. */ ra_index_unwatch(ra->z_multi_exec, return_value TSRMLS_CC); } #ifndef REDIS_ARRAY_H #define REDIS_ARRAY_H #ifdef PHP_WIN32 #include "win32/php_stdint.h" #else #include <stdint.h> #endif #include "common.h" void redis_destructor_redis_array(zend_rsrc_list_entry * rsrc TSRMLS_DC); PHP_METHOD(RedisArray, __construct); PHP_METHOD(RedisArray, __call); PHP_METHOD(RedisArray, _hosts); PHP_METHOD(RedisArray, _target); PHP_METHOD(RedisArray, _instance); PHP_METHOD(RedisArray, _function); PHP_METHOD(RedisArray, _distributor); PHP_METHOD(RedisArray, _rehash); PHP_METHOD(RedisArray, select); PHP_METHOD(RedisArray, info); PHP_METHOD(RedisArray, ping); PHP_METHOD(RedisArray, flushdb); PHP_METHOD(RedisArray, flushall); PHP_METHOD(RedisArray, mget); PHP_METHOD(RedisArray, mset); PHP_METHOD(RedisArray, del); PHP_METHOD(RedisArray, keys); PHP_METHOD(RedisArray, getOption); PHP_METHOD(RedisArray, setOption); PHP_METHOD(RedisArray, save); PHP_METHOD(RedisArray, bgsave); PHP_METHOD(RedisArray, multi); PHP_METHOD(RedisArray, exec); PHP_METHOD(RedisArray, discard); PHP_METHOD(RedisArray, unwatch); typedef struct RedisArray_ { int count; char **hosts; /* array of host:port strings */ zval **redis; /* array of Redis instances */ zval *z_multi_exec; /* Redis instance to be used in multi-exec */ zend_bool index; /* use per-node index */ zend_bool auto_rehash; /* migrate keys on read operations */ zend_bool pconnect; /* should we use pconnect */ zval *z_fun; /* key extractor, callable */ zval *z_dist; /* key distributor, callable */ zval *z_pure_cmds; /* hash table */ double connect_timeout; /* socket connect timeout */ struct RedisArray_ *prev; } RedisArray; uint32_t rcrc32(const char *s, size_t sz); #endif /* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2009 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Nicolas Favre-Felix <n.favre-felix@owlient.eu> | | Maintainer: Michael Grunder <michael.grunder@gmail.com> | +----------------------------------------------------------------------+ */ #include "redis_array_impl.h" #include "php_redis.h" #include "library.h" #include "php_variables.h" #include "SAPI.h" #include "ext/standard/url.h" #define PHPREDIS_INDEX_NAME "__phpredis_array_index__" extern int le_redis_sock; extern zend_class_entry *redis_ce; RedisArray* ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC) { int i, host_len, id; int count = zend_hash_num_elements(hosts); char *host, *p; short port; zval **zpData, z_cons, z_ret; RedisSock *redis_sock = NULL; /* function calls on the Redis object */ ZVAL_STRING(&z_cons, "__construct", 0); /* init connections */ for(i = 0; i < count; ++i) { if(FAILURE == zend_hash_quick_find(hosts, NULL, 0, i, (void**)&zpData) || Z_TYPE_PP(zpData) != IS_STRING) { efree(ra); return NULL; } ra->hosts[i] = estrdup(Z_STRVAL_PP(zpData)); /* default values */ host = Z_STRVAL_PP(zpData); host_len = Z_STRLEN_PP(zpData); port = 6379; if((p = strchr(host, ':'))) { /* found port */ host_len = p - host; port = (short)atoi(p+1); } else if(strchr(host,'/') != NULL) { /* unix socket */ port = -1; } /* create Redis object */ MAKE_STD_ZVAL(ra->redis[i]); object_init_ex(ra->redis[i], redis_ce); INIT_PZVAL(ra->redis[i]); call_user_function(&redis_ce->function_table, &ra->redis[i], &z_cons, &z_ret, 0, NULL TSRMLS_CC); /* create socket */ redis_sock = redis_sock_create(host, host_len, port, ra->connect_timeout, ra->pconnect, NULL, retry_interval, b_lazy_connect); if (!b_lazy_connect) { /* connect */ redis_sock_server_open(redis_sock, 1 TSRMLS_CC); } /* attach */ #if PHP_VERSION_ID >= 50400 id = zend_list_insert(redis_sock, le_redis_sock TSRMLS_CC); #else id = zend_list_insert(redis_sock, le_redis_sock); #endif add_property_resource(ra->redis[i], "socket", id); } return ra; } /* List pure functions */ void ra_init_function_table(RedisArray *ra) { MAKE_STD_ZVAL(ra->z_pure_cmds); array_init(ra->z_pure_cmds); add_assoc_bool(ra->z_pure_cmds, "HGET", 1); add_assoc_bool(ra->z_pure_cmds, "HGETALL", 1); add_assoc_bool(ra->z_pure_cmds, "HKEYS", 1); add_assoc_bool(ra->z_pure_cmds, "HLEN", 1); add_assoc_bool(ra->z_pure_cmds, "SRANDMEMBER", 1); add_assoc_bool(ra->z_pure_cmds, "HMGET", 1); add_assoc_bool(ra->z_pure_cmds, "STRLEN", 1); add_assoc_bool(ra->z_pure_cmds, "SUNION", 1); add_assoc_bool(ra->z_pure_cmds, "HVALS", 1); add_assoc_bool(ra->z_pure_cmds, "TYPE", 1); add_assoc_bool(ra->z_pure_cmds, "EXISTS", 1); add_assoc_bool(ra->z_pure_cmds, "LINDEX", 1); add_assoc_bool(ra->z_pure_cmds, "SCARD", 1); add_assoc_bool(ra->z_pure_cmds, "LLEN", 1); add_assoc_bool(ra->z_pure_cmds, "SDIFF", 1); add_assoc_bool(ra->z_pure_cmds, "ZCARD", 1); add_assoc_bool(ra->z_pure_cmds, "ZCOUNT", 1); add_assoc_bool(ra->z_pure_cmds, "LRANGE", 1); add_assoc_bool(ra->z_pure_cmds, "ZRANGE", 1); add_assoc_bool(ra->z_pure_cmds, "ZRANK", 1); add_assoc_bool(ra->z_pure_cmds, "GET", 1); add_assoc_bool(ra->z_pure_cmds, "GETBIT", 1); add_assoc_bool(ra->z_pure_cmds, "SINTER", 1); add_assoc_bool(ra->z_pure_cmds, "GETRANGE", 1); add_assoc_bool(ra->z_pure_cmds, "ZREVRANGE", 1); add_assoc_bool(ra->z_pure_cmds, "SISMEMBER", 1); add_assoc_bool(ra->z_pure_cmds, "ZREVRANGEBYSCORE", 1); add_assoc_bool(ra->z_pure_cmds, "ZREVRANK", 1); add_assoc_bool(ra->z_pure_cmds, "HEXISTS", 1); add_assoc_bool(ra->z_pure_cmds, "ZSCORE", 1); add_assoc_bool(ra->z_pure_cmds, "HGET", 1); add_assoc_bool(ra->z_pure_cmds, "OBJECT", 1); add_assoc_bool(ra->z_pure_cmds, "SMEMBERS", 1); } static int ra_find_name(const char *name) { const char *ini_names, *p, *next; /* php_printf("Loading redis array with name=[%s]\n", name); */ ini_names = INI_STR("redis.arrays.names"); for(p = ini_names; p;) { next = strchr(p, ','); if(next) { if(strncmp(p, name, next - p) == 0) { return 1; } } else { if(strcmp(p, name) == 0) { return 1; } break; } p = next + 1; } return 0; } /* laod array from INI settings */ RedisArray *ra_load_array(const char *name TSRMLS_DC) { zval *z_params_hosts, **z_hosts; zval *z_params_prev, **z_prev; zval *z_params_funs, **z_data_pp, *z_fun = NULL, *z_dist = NULL; zval *z_params_index; zval *z_params_autorehash; zval *z_params_retry_interval; zval *z_params_pconnect; zval *z_params_connect_timeout; zval *z_params_lazy_connect; RedisArray *ra = NULL; zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0; long l_retry_interval = 0; zend_bool b_lazy_connect = 0; double d_connect_timeout = 0; HashTable *hHosts = NULL, *hPrev = NULL; /* find entry */ if(!ra_find_name(name)) return ra; /* find hosts */ MAKE_STD_ZVAL(z_params_hosts); array_init(z_params_hosts); sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.hosts")), z_params_hosts TSRMLS_CC); if (zend_hash_find(Z_ARRVAL_P(z_params_hosts), name, strlen(name) + 1, (void **) &z_hosts) != FAILURE) { hHosts = Z_ARRVAL_PP(z_hosts); } /* find previous hosts */ MAKE_STD_ZVAL(z_params_prev); array_init(z_params_prev); sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.previous")), z_params_prev TSRMLS_CC); if (zend_hash_find(Z_ARRVAL_P(z_params_prev), name, strlen(name) + 1, (void **) &z_prev) != FAILURE) { hPrev = Z_ARRVAL_PP(z_prev); } /* find function */ MAKE_STD_ZVAL(z_params_funs); array_init(z_params_funs); sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.functions")), z_params_funs TSRMLS_CC); if (zend_hash_find(Z_ARRVAL_P(z_params_funs), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { MAKE_STD_ZVAL(z_fun); *z_fun = **z_data_pp; zval_copy_ctor(z_fun); } /* find distributor */ MAKE_STD_ZVAL(z_params_funs); array_init(z_params_funs); sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.distributor")), z_params_funs TSRMLS_CC); if (zend_hash_find(Z_ARRVAL_P(z_params_funs), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { MAKE_STD_ZVAL(z_dist); *z_dist = **z_data_pp; zval_copy_ctor(z_dist); } /* find index option */ MAKE_STD_ZVAL(z_params_index); array_init(z_params_index); sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.index")), z_params_index TSRMLS_CC); if (zend_hash_find(Z_ARRVAL_P(z_params_index), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { if(Z_TYPE_PP(z_data_pp) == IS_STRING && strncmp(Z_STRVAL_PP(z_data_pp), "1", 1) == 0) { b_index = 1; } } /* find autorehash option */ MAKE_STD_ZVAL(z_params_autorehash); array_init(z_params_autorehash); sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.autorehash")), z_params_autorehash TSRMLS_CC); if (zend_hash_find(Z_ARRVAL_P(z_params_autorehash), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { if(Z_TYPE_PP(z_data_pp) == IS_STRING && strncmp(Z_STRVAL_PP(z_data_pp), "1", 1) == 0) { b_autorehash = 1; } } /* find retry interval option */ MAKE_STD_ZVAL(z_params_retry_interval); array_init(z_params_retry_interval); sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.retryinterval")), z_params_retry_interval TSRMLS_CC); if (zend_hash_find(Z_ARRVAL_P(z_params_retry_interval), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { if (Z_TYPE_PP(z_data_pp) == IS_LONG || Z_TYPE_PP(z_data_pp) == IS_STRING) { if (Z_TYPE_PP(z_data_pp) == IS_LONG) { l_retry_interval = Z_LVAL_PP(z_data_pp); } else { l_retry_interval = atol(Z_STRVAL_PP(z_data_pp)); } } } /* find pconnect option */ MAKE_STD_ZVAL(z_params_pconnect); array_init(z_params_pconnect); sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.pconnect")), z_params_pconnect TSRMLS_CC); if (zend_hash_find(Z_ARRVAL_P(z_params_pconnect), name, strlen(name) + 1, (void**) &z_data_pp) != FAILURE) { if(Z_TYPE_PP(z_data_pp) == IS_STRING && strncmp(Z_STRVAL_PP(z_data_pp), "1", 1) == 0) { b_pconnect = 1; } } /* find lazy connect option */ MAKE_STD_ZVAL(z_params_lazy_connect); array_init(z_params_lazy_connect); sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.lazyconnect")), z_params_lazy_connect TSRMLS_CC); if (zend_hash_find(Z_ARRVAL_P(z_params_lazy_connect), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { if(Z_TYPE_PP(z_data_pp) == IS_STRING && strncmp(Z_STRVAL_PP(z_data_pp), "1", 1) == 0) { b_lazy_connect = 1; } } /* find connect timeout option */ MAKE_STD_ZVAL(z_params_connect_timeout); array_init(z_params_connect_timeout); sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.connecttimeout")), z_params_connect_timeout TSRMLS_CC); if (zend_hash_find(Z_ARRVAL_P(z_params_connect_timeout), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { if (Z_TYPE_PP(z_data_pp) == IS_DOUBLE || Z_TYPE_PP(z_data_pp) == IS_STRING || Z_TYPE_PP(z_data_pp) == IS_LONG) { if (Z_TYPE_PP(z_data_pp) == IS_DOUBLE) { d_connect_timeout = Z_DVAL_PP(z_data_pp); } else if (Z_TYPE_PP(z_data_pp) == IS_LONG) { d_connect_timeout = Z_LVAL_PP(z_data_pp); } else { d_connect_timeout = atof(Z_STRVAL_PP(z_data_pp)); } } } /* create RedisArray object */ ra = ra_make_array(hHosts, z_fun, z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout TSRMLS_CC); ra->auto_rehash = b_autorehash; if(ra->prev) ra->prev->auto_rehash = b_autorehash; /* cleanup */ zval_dtor(z_params_hosts); efree(z_params_hosts); zval_dtor(z_params_prev); efree(z_params_prev); zval_dtor(z_params_funs); efree(z_params_funs); zval_dtor(z_params_index); efree(z_params_index); zval_dtor(z_params_autorehash); efree(z_params_autorehash); zval_dtor(z_params_retry_interval); efree(z_params_retry_interval); zval_dtor(z_params_pconnect); efree(z_params_pconnect); zval_dtor(z_params_connect_timeout); efree(z_params_connect_timeout); zval_dtor(z_params_lazy_connect); efree(z_params_lazy_connect); return ra; } RedisArray * ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout TSRMLS_DC) { int count = zend_hash_num_elements(hosts); /* create object */ RedisArray *ra = emalloc(sizeof(RedisArray)); ra->hosts = emalloc(count * sizeof(char*)); ra->redis = emalloc(count * sizeof(zval*)); ra->count = count; ra->z_fun = NULL; ra->z_dist = NULL; ra->z_multi_exec = NULL; ra->index = b_index; ra->auto_rehash = 0; ra->pconnect = b_pconnect; ra->connect_timeout = connect_timeout; /* init array data structures */ ra_init_function_table(ra); if(NULL == ra_load_hosts(ra, hosts, retry_interval, b_lazy_connect TSRMLS_CC)) { return NULL; } ra->prev = hosts_prev ? ra_make_array(hosts_prev, z_fun, z_dist, NULL, b_index, b_pconnect, retry_interval, b_lazy_connect, connect_timeout TSRMLS_CC) : NULL; /* copy function if provided */ if(z_fun) { MAKE_STD_ZVAL(ra->z_fun); *ra->z_fun = *z_fun; zval_copy_ctor(ra->z_fun); } /* copy distributor if provided */ if(z_dist) { MAKE_STD_ZVAL(ra->z_dist); *ra->z_dist = *z_dist; zval_copy_ctor(ra->z_dist); } return ra; } /* call userland key extraction function */ char * ra_call_extractor(RedisArray *ra, const char *key, int key_len, int *out_len TSRMLS_DC) { char *out; zval z_ret; zval *z_argv0; /* check that we can call the extractor function */ if(!zend_is_callable_ex(ra->z_fun, NULL, 0, NULL, NULL, NULL, NULL TSRMLS_CC)) { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not call extractor function"); return NULL; } /* convert_to_string(ra->z_fun); */ /* call extraction function */ MAKE_STD_ZVAL(z_argv0); ZVAL_STRINGL(z_argv0, key, key_len, 0); call_user_function(EG(function_table), NULL, ra->z_fun, &z_ret, 1, &z_argv0 TSRMLS_CC); efree(z_argv0); if(Z_TYPE(z_ret) != IS_STRING) { zval_dtor(&z_ret); return NULL; } *out_len = Z_STRLEN(z_ret); out = emalloc(*out_len + 1); out[*out_len] = 0; memcpy(out, Z_STRVAL(z_ret), *out_len); zval_dtor(&z_ret); return out; } static char * ra_extract_key(RedisArray *ra, const char *key, int key_len, int *out_len TSRMLS_DC) { char *start, *end, *out; *out_len = key_len; if(ra->z_fun) return ra_call_extractor(ra, key, key_len, out_len TSRMLS_CC); /* look for '{' */ start = strchr(key, '{'); if(!start) return estrndup(key, key_len); /* look for '}' */ end = strchr(start + 1, '}'); if(!end) return estrndup(key, key_len); /* found substring */ *out_len = end - start - 1; out = emalloc(*out_len + 1); out[*out_len] = 0; memcpy(out, start+1, *out_len); return out; } /* call userland key distributor function */ zend_bool ra_call_distributor(RedisArray *ra, const char *key, int key_len, int *pos TSRMLS_DC) { zval z_ret; zval *z_argv0; /* check that we can call the extractor function */ if(!zend_is_callable_ex(ra->z_dist, NULL, 0, NULL, NULL, NULL, NULL TSRMLS_CC)) { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not call distributor function"); return 0; } /* convert_to_string(ra->z_fun); */ /* call extraction function */ MAKE_STD_ZVAL(z_argv0); ZVAL_STRINGL(z_argv0, key, key_len, 0); call_user_function(EG(function_table), NULL, ra->z_dist, &z_ret, 1, &z_argv0 TSRMLS_CC); efree(z_argv0); if(Z_TYPE(z_ret) != IS_LONG) { zval_dtor(&z_ret); return 0; } *pos = Z_LVAL(z_ret); zval_dtor(&z_ret); return 1; } zval * ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_DC) { uint32_t hash; char *out; int pos, out_len; /* extract relevant part of the key */ out = ra_extract_key(ra, key, key_len, &out_len TSRMLS_CC); if(!out) return NULL; if(ra->z_dist) { if (!ra_call_distributor(ra, key, key_len, &pos TSRMLS_CC)) { return NULL; } } else { uint64_t h64; /* hash */ hash = rcrc32(out, out_len); efree(out); /* get position on ring */ h64 = hash; h64 *= ra->count; h64 /= 0xffffffff; pos = (int)h64; } if(out_pos) *out_pos = pos; return ra->redis[pos]; } zval * ra_find_node_by_name(RedisArray *ra, const char *host, int host_len TSRMLS_DC) { int i; for(i = 0; i < ra->count; ++i) { if(strncmp(ra->hosts[i], host, host_len) == 0) { return ra->redis[i]; } } return NULL; } char * ra_find_key(RedisArray *ra, zval *z_args, const char *cmd, int *key_len) { zval **zp_tmp; int key_pos = 0; /* TODO: change this depending on the command */ if( zend_hash_num_elements(Z_ARRVAL_P(z_args)) == 0 || zend_hash_quick_find(Z_ARRVAL_P(z_args), NULL, 0, key_pos, (void**)&zp_tmp) == FAILURE || Z_TYPE_PP(zp_tmp) != IS_STRING) { return NULL; } *key_len = Z_STRLEN_PP(zp_tmp); return Z_STRVAL_PP(zp_tmp); } void ra_index_multi(zval *z_redis, long multi_value TSRMLS_DC) { zval z_fun_multi, z_ret; zval *z_args[1]; /* run MULTI */ ZVAL_STRING(&z_fun_multi, "MULTI", 0); MAKE_STD_ZVAL(z_args[0]); ZVAL_LONG(z_args[0], multi_value); call_user_function(&redis_ce->function_table, &z_redis, &z_fun_multi, &z_ret, 1, z_args TSRMLS_CC); efree(z_args[0]); /* zval_dtor(&z_ret); */ } static void ra_index_change_keys(const char *cmd, zval *z_keys, zval *z_redis TSRMLS_DC) { int i, argc; zval z_fun, z_ret, **z_args; /* alloc */ argc = 1 + zend_hash_num_elements(Z_ARRVAL_P(z_keys)); z_args = emalloc(argc * sizeof(zval*)); /* prepare first parameters */ ZVAL_STRING(&z_fun, cmd, 0); MAKE_STD_ZVAL(z_args[0]); ZVAL_STRING(z_args[0], PHPREDIS_INDEX_NAME, 0); /* prepare keys */ for(i = 0; i < argc - 1; ++i) { zval **zpp; zend_hash_quick_find(Z_ARRVAL_P(z_keys), NULL, 0, i, (void**)&zpp); z_args[i+1] = *zpp; } /* run cmd */ call_user_function(&redis_ce->function_table, &z_redis, &z_fun, &z_ret, argc, z_args TSRMLS_CC); /* don't dtor z_ret, since we're returning z_redis */ efree(z_args[0]); /* free index name zval */ efree(z_args); /* free container */ } void ra_index_del(zval *z_keys, zval *z_redis TSRMLS_DC) { ra_index_change_keys("SREM", z_keys, z_redis TSRMLS_CC); } void ra_index_keys(zval *z_pairs, zval *z_redis TSRMLS_DC) { /* Initialize key array */ zval *z_keys, **z_entry_pp; HashPosition pos; MAKE_STD_ZVAL(z_keys); #if PHP_VERSION_ID > 50300 array_init_size(z_keys, zend_hash_num_elements(Z_ARRVAL_P(z_pairs))); #else array_init(z_keys); #endif /* Go through input array and add values to the key array */ zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(z_pairs), &pos); while (zend_hash_get_current_data_ex(Z_ARRVAL_P(z_pairs), (void **)&z_entry_pp, &pos) == SUCCESS) { char *key; unsigned int key_len; unsigned long num_key; zval *z_new; MAKE_STD_ZVAL(z_new); switch (zend_hash_get_current_key_ex(Z_ARRVAL_P(z_pairs), &key, &key_len, &num_key, 1, &pos)) { case HASH_KEY_IS_STRING: ZVAL_STRINGL(z_new, key, (int)key_len - 1, 0); zend_hash_next_index_insert(Z_ARRVAL_P(z_keys), &z_new, sizeof(zval *), NULL); break; case HASH_KEY_IS_LONG: Z_TYPE_P(z_new) = IS_LONG; Z_LVAL_P(z_new) = (long)num_key; zend_hash_next_index_insert(Z_ARRVAL_P(z_keys), &z_new, sizeof(zval *), NULL); break; } zend_hash_move_forward_ex(Z_ARRVAL_P(z_pairs), &pos); } /* add keys to index */ ra_index_change_keys("SADD", z_keys, z_redis TSRMLS_CC); /* cleanup */ zval_dtor(z_keys); efree(z_keys); } void ra_index_key(const char *key, int key_len, zval *z_redis TSRMLS_DC) { zval z_fun_sadd, z_ret, *z_args[2]; MAKE_STD_ZVAL(z_args[0]); MAKE_STD_ZVAL(z_args[1]); /* prepare args */ ZVAL_STRINGL(&z_fun_sadd, "SADD", 4, 0); ZVAL_STRING(z_args[0], PHPREDIS_INDEX_NAME, 0); ZVAL_STRINGL(z_args[1], key, key_len, 1); /* run SADD */ call_user_function(&redis_ce->function_table, &z_redis, &z_fun_sadd, &z_ret, 2, z_args TSRMLS_CC); /* don't dtor z_ret, since we're returning z_redis */ efree(z_args[0]); zval_dtor(z_args[1]); efree(z_args[1]); } void ra_index_exec(zval *z_redis, zval *return_value, int keep_all TSRMLS_DC) { zval z_fun_exec, z_ret, **zp_tmp; /* run EXEC */ ZVAL_STRING(&z_fun_exec, "EXEC", 0); call_user_function(&redis_ce->function_table, &z_redis, &z_fun_exec, &z_ret, 0, NULL TSRMLS_CC); /* extract first element of exec array and put into return_value. */ if(Z_TYPE(z_ret) == IS_ARRAY) { if(return_value) { if(keep_all) { *return_value = z_ret; zval_copy_ctor(return_value); } else if(zend_hash_quick_find(Z_ARRVAL(z_ret), NULL, 0, 0, (void**)&zp_tmp) != FAILURE) { *return_value = **zp_tmp; zval_copy_ctor(return_value); } } zval_dtor(&z_ret); } /* zval *zptr = &z_ret; */ /* php_var_dump(&zptr, 0 TSRMLS_CC); */ } void ra_index_discard(zval *z_redis, zval *return_value TSRMLS_DC) { zval z_fun_discard, z_ret; /* run DISCARD */ ZVAL_STRING(&z_fun_discard, "DISCARD", 0); call_user_function(&redis_ce->function_table, &z_redis, &z_fun_discard, &z_ret, 0, NULL TSRMLS_CC); zval_dtor(&z_ret); } void ra_index_unwatch(zval *z_redis, zval *return_value TSRMLS_DC) { zval z_fun_unwatch, z_ret; /* run UNWATCH */ ZVAL_STRING(&z_fun_unwatch, "UNWATCH", 0); call_user_function(&redis_ce->function_table, &z_redis, &z_fun_unwatch, &z_ret, 0, NULL TSRMLS_CC); zval_dtor(&z_ret); } zend_bool ra_is_write_cmd(RedisArray *ra, const char *cmd, int cmd_len) { zend_bool ret; int i; char *cmd_up = emalloc(1 + cmd_len); /* convert to uppercase */ for(i = 0; i < cmd_len; ++i) cmd_up[i] = toupper(cmd[i]); cmd_up[cmd_len] = 0; ret = zend_hash_exists(Z_ARRVAL_P(ra->z_pure_cmds), cmd_up, cmd_len+1); efree(cmd_up); return !ret; } /* list keys from array index */ static long ra_rehash_scan(zval *z_redis, char ***keys, int **key_lens, const char *cmd, const char *arg TSRMLS_DC) { long count, i; zval z_fun_smembers, z_ret, *z_arg, **z_data_pp; HashTable *h_keys; HashPosition pointer; char *key; int key_len; /* arg */ MAKE_STD_ZVAL(z_arg); ZVAL_STRING(z_arg, arg, 0); /* run SMEMBERS */ ZVAL_STRING(&z_fun_smembers, cmd, 0); call_user_function(&redis_ce->function_table, &z_redis, &z_fun_smembers, &z_ret, 1, &z_arg TSRMLS_CC); efree(z_arg); if(Z_TYPE(z_ret) != IS_ARRAY) { /* failure */ return -1; /* TODO: log error. */ } h_keys = Z_ARRVAL(z_ret); /* allocate key array */ count = zend_hash_num_elements(h_keys); *keys = emalloc(count * sizeof(char*)); *key_lens = emalloc(count * sizeof(int)); for (i = 0, zend_hash_internal_pointer_reset_ex(h_keys, &pointer); zend_hash_get_current_data_ex(h_keys, (void**) &z_data_pp, &pointer) == SUCCESS; zend_hash_move_forward_ex(h_keys, &pointer), ++i) { key = Z_STRVAL_PP(z_data_pp); key_len = Z_STRLEN_PP(z_data_pp); /* copy key and length */ (*keys)[i] = emalloc(1 + key_len); memcpy((*keys)[i], key, key_len); (*key_lens)[i] = key_len; (*keys)[i][key_len] = 0; /* null-terminate string */ } /* cleanup */ zval_dtor(&z_ret); return count; } static long ra_rehash_scan_index(zval *z_redis, char ***keys, int **key_lens TSRMLS_DC) { return ra_rehash_scan(z_redis, keys, key_lens, "SMEMBERS", PHPREDIS_INDEX_NAME TSRMLS_CC); } /* list keys using KEYS command */ static long ra_rehash_scan_keys(zval *z_redis, char ***keys, int **key_lens TSRMLS_DC) { return ra_rehash_scan(z_redis, keys, key_lens, "KEYS", "*" TSRMLS_CC); } /* run TYPE to find the type */ static zend_bool ra_get_key_type(zval *z_redis, const char *key, int key_len, zval *z_from, long *res TSRMLS_DC) { int i; zval z_fun_type, z_ret, *z_arg; zval **z_data; long success = 1; MAKE_STD_ZVAL(z_arg); /* Pipelined */ ra_index_multi(z_from, PIPELINE TSRMLS_CC); /* prepare args */ ZVAL_STRINGL(&z_fun_type, "TYPE", 4, 0); ZVAL_STRINGL(z_arg, key, key_len, 0); /* run TYPE */ call_user_function(&redis_ce->function_table, &z_redis, &z_fun_type, &z_ret, 1, &z_arg TSRMLS_CC); ZVAL_STRINGL(&z_fun_type, "TTL", 3, 0); ZVAL_STRINGL(z_arg, key, key_len, 0); /* run TYPE */ call_user_function(&redis_ce->function_table, &z_redis, &z_fun_type, &z_ret, 1, &z_arg TSRMLS_CC); /* cleanup */ efree(z_arg); /* Get the result from the pipeline. */ ra_index_exec(z_from, &z_ret, 1 TSRMLS_CC); if(Z_TYPE(z_ret) == IS_ARRAY) { HashTable *retHash = Z_ARRVAL(z_ret); for(i = 0, zend_hash_internal_pointer_reset(retHash); zend_hash_has_more_elements(retHash) == SUCCESS; zend_hash_move_forward(retHash)) { if(zend_hash_get_current_data(retHash, (void**)&z_data) == FAILURE) { success = 0; break; } if(Z_TYPE_PP(z_data) != IS_LONG) { success = 0; break; } /* Get the result - Might change in the future to handle doubles as well */ res[i] = Z_LVAL_PP(z_data); i++; } } zval_dtor(&z_ret); return success; } /* delete key from source server index during rehashing */ static void ra_remove_from_index(zval *z_redis, const char *key, int key_len TSRMLS_DC) { zval z_fun_srem, z_ret, *z_args[2]; /* run SREM on source index */ ZVAL_STRINGL(&z_fun_srem, "SREM", 4, 0); MAKE_STD_ZVAL(z_args[0]); ZVAL_STRING(z_args[0], PHPREDIS_INDEX_NAME, 0); MAKE_STD_ZVAL(z_args[1]); ZVAL_STRINGL(z_args[1], key, key_len, 0); call_user_function(&redis_ce->function_table, &z_redis, &z_fun_srem, &z_ret, 2, z_args TSRMLS_CC); /* cleanup */ efree(z_args[0]); efree(z_args[1]); } /* delete key from source server during rehashing */ static zend_bool ra_del_key(const char *key, int key_len, zval *z_from TSRMLS_DC) { zval z_fun_del, z_ret, *z_args; /* in a transaction */ ra_index_multi(z_from, MULTI TSRMLS_CC); /* run DEL on source */ MAKE_STD_ZVAL(z_args); ZVAL_STRINGL(&z_fun_del, "DEL", 3, 0); ZVAL_STRINGL(z_args, key, key_len, 0); call_user_function(&redis_ce->function_table, &z_from, &z_fun_del, &z_ret, 1, &z_args TSRMLS_CC); efree(z_args); /* remove key from index */ ra_remove_from_index(z_from, key, key_len TSRMLS_CC); /* close transaction */ ra_index_exec(z_from, NULL, 0 TSRMLS_CC); return 1; } static zend_bool ra_expire_key(const char *key, int key_len, zval *z_to, long ttl TSRMLS_DC) { zval z_fun_expire, z_ret, *z_args[2]; if (ttl > 0) { /* run EXPIRE on target */ MAKE_STD_ZVAL(z_args[0]); MAKE_STD_ZVAL(z_args[1]); ZVAL_STRINGL(&z_fun_expire, "EXPIRE", 6, 0); ZVAL_STRINGL(z_args[0], key, key_len, 0); ZVAL_LONG(z_args[1], ttl); call_user_function(&redis_ce->function_table, &z_to, &z_fun_expire, &z_ret, 2, z_args TSRMLS_CC); /* cleanup */ efree(z_args[0]); efree(z_args[1]); } return 1; } static zend_bool ra_move_zset(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { zval z_fun_zrange, z_fun_zadd, z_ret, *z_args[4], **z_zadd_args, **z_score_pp; int count; HashTable *h_zset_vals; char *val; unsigned int val_len; int i; unsigned long idx; /* run ZRANGE key 0 -1 WITHSCORES on source */ ZVAL_STRINGL(&z_fun_zrange, "ZRANGE", 6, 0); for(i = 0; i < 4; ++i) { MAKE_STD_ZVAL(z_args[i]); } ZVAL_STRINGL(z_args[0], key, key_len, 0); ZVAL_STRINGL(z_args[1], "0", 1, 0); ZVAL_STRINGL(z_args[2], "-1", 2, 0); ZVAL_BOOL(z_args[3], 1); call_user_function(&redis_ce->function_table, &z_from, &z_fun_zrange, &z_ret, 4, z_args TSRMLS_CC); /* cleanup zrange args */ for(i = 0; i < 4; ++i) { efree(z_args[i]); /* FIXME */ } if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */ /* TODO: report? */ return 0; } /* we now have an array of value → score pairs in z_ret. */ h_zset_vals = Z_ARRVAL(z_ret); /* allocate argument array for ZADD */ count = zend_hash_num_elements(h_zset_vals); z_zadd_args = emalloc((1 + 2*count) * sizeof(zval*)); for(i = 1, zend_hash_internal_pointer_reset(h_zset_vals); zend_hash_has_more_elements(h_zset_vals) == SUCCESS; zend_hash_move_forward(h_zset_vals)) { if(zend_hash_get_current_data(h_zset_vals, (void**)&z_score_pp) == FAILURE) { continue; } /* add score */ convert_to_double(*z_score_pp); MAKE_STD_ZVAL(z_zadd_args[i]); ZVAL_DOUBLE(z_zadd_args[i], Z_DVAL_PP(z_score_pp)); /* add value */ MAKE_STD_ZVAL(z_zadd_args[i+1]); switch (zend_hash_get_current_key_ex(h_zset_vals, &val, &val_len, &idx, 0, NULL)) { case HASH_KEY_IS_STRING: ZVAL_STRINGL(z_zadd_args[i+1], val, (int)val_len-1, 0); /* we have to remove 1 because it is an array key. */ break; case HASH_KEY_IS_LONG: ZVAL_LONG(z_zadd_args[i+1], (long)idx); break; default: return -1; /* Todo: log error */ break; } i += 2; } /* run ZADD on target */ ZVAL_STRINGL(&z_fun_zadd, "ZADD", 4, 0); MAKE_STD_ZVAL(z_zadd_args[0]); ZVAL_STRINGL(z_zadd_args[0], key, key_len, 0); call_user_function(&redis_ce->function_table, &z_to, &z_fun_zadd, &z_ret, 1 + 2 * count, z_zadd_args TSRMLS_CC); /* Expire if needed */ ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); /* cleanup */ for(i = 0; i < 1 + 2 * count; ++i) { efree(z_zadd_args[i]); } return 1; } static zend_bool ra_move_string(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { zval z_fun_get, z_fun_set, z_ret, *z_args[3]; /* run GET on source */ MAKE_STD_ZVAL(z_args[0]); ZVAL_STRINGL(&z_fun_get, "GET", 3, 0); ZVAL_STRINGL(z_args[0], key, key_len, 0); call_user_function(&redis_ce->function_table, &z_from, &z_fun_get, &z_ret, 1, z_args TSRMLS_CC); if(Z_TYPE(z_ret) != IS_STRING) { /* key not found or replaced */ /* TODO: report? */ efree(z_args[0]); return 0; } /* run SET on target */ MAKE_STD_ZVAL(z_args[1]); if (ttl > 0) { MAKE_STD_ZVAL(z_args[2]); ZVAL_STRINGL(&z_fun_set, "SETEX", 5, 0); ZVAL_STRINGL(z_args[0], key, key_len, 0); ZVAL_LONG(z_args[1], ttl); ZVAL_STRINGL(z_args[2], Z_STRVAL(z_ret), Z_STRLEN(z_ret), 1); /* copy z_ret to arg 1 */ zval_dtor(&z_ret); /* free memory from our previous call */ call_user_function(&redis_ce->function_table, &z_to, &z_fun_set, &z_ret, 3, z_args TSRMLS_CC); /* cleanup */ efree(z_args[1]); zval_dtor(z_args[2]); efree(z_args[2]); } else { ZVAL_STRINGL(&z_fun_set, "SET", 3, 0); ZVAL_STRINGL(z_args[0], key, key_len, 0); ZVAL_STRINGL(z_args[1], Z_STRVAL(z_ret), Z_STRLEN(z_ret), 1); /* copy z_ret to arg 1 */ zval_dtor(&z_ret); /* free memory from our previous return value */ call_user_function(&redis_ce->function_table, &z_to, &z_fun_set, &z_ret, 2, z_args TSRMLS_CC); /* cleanup */ zval_dtor(z_args[1]); efree(z_args[1]); } /* cleanup */ efree(z_args[0]); return 1; } static zend_bool ra_move_hash(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { zval z_fun_hgetall, z_fun_hmset, z_ret, *z_args[2]; /* run HGETALL on source */ MAKE_STD_ZVAL(z_args[0]); ZVAL_STRINGL(&z_fun_hgetall, "HGETALL", 7, 0); ZVAL_STRINGL(z_args[0], key, key_len, 0); call_user_function(&redis_ce->function_table, &z_from, &z_fun_hgetall, &z_ret, 1, z_args TSRMLS_CC); if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */ /* TODO: report? */ efree(z_args[0]); return 0; } /* run HMSET on target */ ZVAL_STRINGL(&z_fun_hmset, "HMSET", 5, 0); ZVAL_STRINGL(z_args[0], key, key_len, 0); z_args[1] = &z_ret; /* copy z_ret to arg 1 */ call_user_function(&redis_ce->function_table, &z_to, &z_fun_hmset, &z_ret, 2, z_args TSRMLS_CC); /* Expire if needed */ ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); /* cleanup */ efree(z_args[0]); return 1; } static zend_bool ra_move_collection(const char *key, int key_len, zval *z_from, zval *z_to, int list_count, const char **cmd_list, int add_count, const char **cmd_add, long ttl TSRMLS_DC) { zval z_fun_retrieve, z_fun_sadd, z_ret, **z_retrieve_args, **z_sadd_args, **z_data_pp; int count, i; HashTable *h_set_vals; /* run retrieval command on source */ z_retrieve_args = emalloc((1+list_count) * sizeof(zval*)); ZVAL_STRING(&z_fun_retrieve, cmd_list[0], 0); /* set the command */ /* set the key */ MAKE_STD_ZVAL(z_retrieve_args[0]); ZVAL_STRINGL(z_retrieve_args[0], key, key_len, 0); /* possibly add some other args if they were provided. */ for(i = 1; i < list_count; ++i) { MAKE_STD_ZVAL(z_retrieve_args[i]); ZVAL_STRING(z_retrieve_args[i], cmd_list[i], 0); } call_user_function(&redis_ce->function_table, &z_from, &z_fun_retrieve, &z_ret, list_count, z_retrieve_args TSRMLS_CC); /* cleanup */ for(i = 0; i < list_count; ++i) { efree(z_retrieve_args[i]); } efree(z_retrieve_args); if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */ /* TODO: report? */ return 0; } /* run SADD/RPUSH on target */ h_set_vals = Z_ARRVAL(z_ret); count = zend_hash_num_elements(h_set_vals); z_sadd_args = emalloc((1 + count) * sizeof(zval*)); ZVAL_STRING(&z_fun_sadd, cmd_add[0], 0); MAKE_STD_ZVAL(z_sadd_args[0]); /* add key */ ZVAL_STRINGL(z_sadd_args[0], key, key_len, 0); for(i = 0, zend_hash_internal_pointer_reset(h_set_vals); zend_hash_has_more_elements(h_set_vals) == SUCCESS; zend_hash_move_forward(h_set_vals), i++) { if(zend_hash_get_current_data(h_set_vals, (void**)&z_data_pp) == FAILURE) { continue; } /* add set elements */ MAKE_STD_ZVAL(z_sadd_args[i+1]); *(z_sadd_args[i+1]) = **z_data_pp; zval_copy_ctor(z_sadd_args[i+1]); } call_user_function(&redis_ce->function_table, &z_to, &z_fun_sadd, &z_ret, count+1, z_sadd_args TSRMLS_CC); /* Expire if needed */ ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); /* cleanup */ efree(z_sadd_args[0]); /* no dtor at [0] */ for(i = 0; i < count; ++i) { zval_dtor(z_sadd_args[i + 1]); efree(z_sadd_args[i + 1]); } efree(z_sadd_args); return 1; } static zend_bool ra_move_set(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { const char *cmd_list[] = {"SMEMBERS"}; const char *cmd_add[] = {"SADD"}; return ra_move_collection(key, key_len, z_from, z_to, 1, cmd_list, 1, cmd_add, ttl TSRMLS_CC); } static zend_bool ra_move_list(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { const char *cmd_list[] = {"LRANGE", "0", "-1"}; const char *cmd_add[] = {"RPUSH"}; return ra_move_collection(key, key_len, z_from, z_to, 3, cmd_list, 1, cmd_add, ttl TSRMLS_CC); } void ra_move_key(const char *key, int key_len, zval *z_from, zval *z_to TSRMLS_DC) { long res[2], type, ttl; zend_bool success = 0; if (ra_get_key_type(z_from, key, key_len, z_from, res TSRMLS_CC)) { type = res[0]; ttl = res[1]; /* open transaction on target server */ ra_index_multi(z_to, MULTI TSRMLS_CC); switch(type) { case REDIS_STRING: success = ra_move_string(key, key_len, z_from, z_to, ttl TSRMLS_CC); break; case REDIS_SET: success = ra_move_set(key, key_len, z_from, z_to, ttl TSRMLS_CC); break; case REDIS_LIST: success = ra_move_list(key, key_len, z_from, z_to, ttl TSRMLS_CC); break; case REDIS_ZSET: success = ra_move_zset(key, key_len, z_from, z_to, ttl TSRMLS_CC); break; case REDIS_HASH: success = ra_move_hash(key, key_len, z_from, z_to, ttl TSRMLS_CC); break; default: /* TODO: report? */ break; } } if(success) { ra_del_key(key, key_len, z_from TSRMLS_CC); ra_index_key(key, key_len, z_to TSRMLS_CC); } /* close transaction */ ra_index_exec(z_to, NULL, 0 TSRMLS_CC); } /* callback with the current progress, with hostname and count */ static void zval_rehash_callback(zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache, const char *hostname, long count TSRMLS_DC) { zval *z_ret = NULL, **z_args[2]; zval *z_host, *z_count; z_cb->retval_ptr_ptr = &z_ret; z_cb->params = (struct _zval_struct ***)&z_args; z_cb->param_count = 2; z_cb->no_separation = 0; /* run cb(hostname, count) */ MAKE_STD_ZVAL(z_host); ZVAL_STRING(z_host, hostname, 0); z_args[0] = &z_host; MAKE_STD_ZVAL(z_count); ZVAL_LONG(z_count, count); z_args[1] = &z_count; zend_call_function(z_cb, z_cb_cache TSRMLS_CC); /* cleanup */ efree(z_host); efree(z_count); if(z_ret) efree(z_ret); } static void ra_rehash_server(RedisArray *ra, zval *z_redis, const char *hostname, zend_bool b_index, zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache TSRMLS_DC) { char **keys; int *key_lens; long count, i; int target_pos; zval *z_target; /* list all keys */ if(b_index) { count = ra_rehash_scan_index(z_redis, &keys, &key_lens TSRMLS_CC); } else { count = ra_rehash_scan_keys(z_redis, &keys, &key_lens TSRMLS_CC); } /* callback */ if(z_cb && z_cb_cache) { zval_rehash_callback(z_cb, z_cb_cache, hostname, count TSRMLS_CC); } /* for each key, redistribute */ for(i = 0; i < count; ++i) { /* check that we're not moving to the same node. */ z_target = ra_find_node(ra, keys[i], key_lens[i], &target_pos TSRMLS_CC); if(strcmp(hostname, ra->hosts[target_pos])) { /* different host */ /* php_printf("move [%s] from [%s] to [%s]\n", keys[i], hostname, ra->hosts[target_pos]); */ ra_move_key(keys[i], key_lens[i], z_redis, z_target TSRMLS_CC); } } /* cleanup */ for(i = 0; i < count; ++i) { efree(keys[i]); } efree(keys); efree(key_lens); } void ra_rehash(RedisArray *ra, zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache TSRMLS_DC) { int i; /* redistribute the data, server by server. */ if(!ra->prev) return; /* TODO: compare the two rings for equality */ for(i = 0; i < ra->prev->count; ++i) { ra_rehash_server(ra, ra->prev->redis[i], ra->prev->hosts[i], ra->index, z_cb, z_cb_cache TSRMLS_CC); } } #ifndef REDIS_ARRAY_IMPL_H #define REDIS_ARRAY_IMPL_H #ifdef PHP_WIN32 #include "win32/php_stdint.h" #else #include <stdint.h> #endif #include "common.h" #include "redis_array.h" RedisArray *ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC); RedisArray *ra_load_array(const char *name TSRMLS_DC); RedisArray *ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout TSRMLS_DC); zval *ra_find_node_by_name(RedisArray *ra, const char *host, int host_len TSRMLS_DC); zval *ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_DC); void ra_init_function_table(RedisArray *ra); void ra_move_key(const char *key, int key_len, zval *z_from, zval *z_to TSRMLS_DC); char * ra_find_key(RedisArray *ra, zval *z_args, const char *cmd, int *key_len); void ra_index_multi(zval *z_redis, long multi_value TSRMLS_DC); void ra_index_key(const char *key, int key_len, zval *z_redis TSRMLS_DC); void ra_index_keys(zval *z_pairs, zval *z_redis TSRMLS_DC); void ra_index_del(zval *z_keys, zval *z_redis TSRMLS_DC); void ra_index_exec(zval *z_redis, zval *return_value, int keep_all TSRMLS_DC); void ra_index_discard(zval *z_redis, zval *return_value TSRMLS_DC); void ra_index_unwatch(zval *z_redis, zval *return_value TSRMLS_DC); zend_bool ra_is_write_cmd(RedisArray *ra, const char *cmd, int cmd_len); void ra_rehash(RedisArray *ra, zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache TSRMLS_DC); #endif /* -*- Mode: C; tab-width: 4 -*- */ /* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2009 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Original author: Alfonso Jimenez <yo@alfonsojimenez.com> | | Maintainer: Nicolas Favre-Felix <n.favre-felix@owlient.eu> | | Maintainer: Nasreddine Bouafif <n.bouafif@owlient.eu> | | Maintainer: Michael Grunder <michael.grunder@gmail.com> | +----------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "common.h" #include "ext/standard/info.h" #include "php_ini.h" #include "php_redis.h" #include "redis_array.h" #include <zend_exceptions.h> #ifdef PHP_SESSION #include "ext/session/php_session.h" #endif #include <ext/standard/php_smart_str.h> #include <ext/standard/php_var.h> #include <ext/standard/php_math.h> #include "library.h" #define R_SUB_CALLBACK_CLASS_TYPE 1 #define R_SUB_CALLBACK_FT_TYPE 2 #define R_SUB_CLOSURE_TYPE 3 int le_redis_sock; extern int le_redis_array; #ifdef PHP_SESSION extern ps_module ps_mod_redis; #endif extern zend_class_entry *redis_array_ce; zend_class_entry *redis_ce; zend_class_entry *redis_exception_ce; zend_class_entry *spl_ce_RuntimeException = NULL; extern zend_function_entry redis_array_functions[]; PHP_INI_BEGIN() /* redis arrays */ PHP_INI_ENTRY("redis.arrays.names", "", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.arrays.hosts", "", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.arrays.previous", "", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.arrays.functions", "", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.arrays.index", "", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.arrays.autorehash", "", PHP_INI_ALL, NULL) PHP_INI_END() /** * Argument info for the SCAN proper */ ZEND_BEGIN_ARG_INFO_EX(arginfo_scan, 0, 0, 1) ZEND_ARG_INFO(1, i_iterator) ZEND_ARG_INFO(0, str_pattern) ZEND_ARG_INFO(0, i_count) ZEND_END_ARG_INFO(); /** * Argument info for key scanning */ ZEND_BEGIN_ARG_INFO_EX(arginfo_kscan, 0, 0, 2) ZEND_ARG_INFO(0, str_key) ZEND_ARG_INFO(1, i_iterator) ZEND_ARG_INFO(0, str_pattern) ZEND_ARG_INFO(0, i_count) ZEND_END_ARG_INFO(); #ifdef ZTS ZEND_DECLARE_MODULE_GLOBALS(redis) #endif static zend_function_entry redis_functions[] = { PHP_ME(Redis, __construct, NULL, ZEND_ACC_CTOR | ZEND_ACC_PUBLIC) PHP_ME(Redis, __destruct, NULL, ZEND_ACC_DTOR | ZEND_ACC_PUBLIC) PHP_ME(Redis, connect, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pconnect, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, close, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, ping, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, echo, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, get, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, set, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, setex, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, psetex, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, setnx, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getSet, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, randomKey, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, renameKey, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, renameNx, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getMultiple, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, exists, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, delete, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, incr, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, incrBy, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, incrByFloat, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, decr, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, decrBy, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, type, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, append, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getRange, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, setRange, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getBit, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, setBit, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, strlen, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getKeys, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sort, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sortAsc, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sortAscAlpha, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sortDesc, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sortDescAlpha, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lPush, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, rPush, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lPushx, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, rPushx, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lPop, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, rPop, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, blPop, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, brPop, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lSize, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lRemove, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, listTrim, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lGet, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lGetRange, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lSet, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lInsert, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sAdd, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sSize, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sRemove, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sMove, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sPop, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sRandMember, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sContains, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sMembers, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sInter, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sInterStore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sUnion, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sUnionStore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sDiff, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sDiffStore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, setTimeout, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, save, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, bgSave, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lastSave, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, flushDB, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, flushAll, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, dbSize, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, auth, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, ttl, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pttl, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, persist, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, info, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, resetStat, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, select, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, move, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, bgrewriteaof, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, slaveof, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, object, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, bitop, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, bitcount, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, bitpos, NULL, ZEND_ACC_PUBLIC) /* 1.1 */ PHP_ME(Redis, mset, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, msetnx, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, rpoplpush, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, brpoplpush, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zAdd, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zDelete, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRange, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zReverseRange, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRangeByScore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRevRangeByScore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRangeByLex, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zCount, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zDeleteRangeByScore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zDeleteRangeByRank, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zCard, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zScore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRank, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRevRank, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zInter, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zUnion, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zIncrBy, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, expireAt, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pexpire, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pexpireAt, NULL, ZEND_ACC_PUBLIC) /* 1.2 */ PHP_ME(Redis, hGet, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hSet, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hSetNx, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hDel, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hLen, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hKeys, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hVals, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hGetAll, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hExists, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hIncrBy, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hIncrByFloat, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hMset, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hMget, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, multi, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, discard, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, exec, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pipeline, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, watch, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, unwatch, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, publish, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, subscribe, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, psubscribe, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, unsubscribe, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, punsubscribe, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, time, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, eval, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, evalsha, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, script, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, debug, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, dump, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, restore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, migrate, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getLastError, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, clearLastError, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, _prefix, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, _serialize, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, _unserialize, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, client, NULL, ZEND_ACC_PUBLIC) /* SCAN and friends */ PHP_ME(Redis, scan, arginfo_scan, ZEND_ACC_PUBLIC) PHP_ME(Redis, hscan, arginfo_kscan, ZEND_ACC_PUBLIC) PHP_ME(Redis, zscan, arginfo_kscan, ZEND_ACC_PUBLIC) PHP_ME(Redis, sscan, arginfo_kscan, ZEND_ACC_PUBLIC) /* HyperLogLog commands */ PHP_ME(Redis, pfadd, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pfcount, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pfmerge, NULL, ZEND_ACC_PUBLIC) /* options */ PHP_ME(Redis, getOption, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, setOption, NULL, ZEND_ACC_PUBLIC) /* config */ PHP_ME(Redis, config, NULL, ZEND_ACC_PUBLIC) /* slowlog */ PHP_ME(Redis, slowlog, NULL, ZEND_ACC_PUBLIC) /* Send a raw command and read raw results */ PHP_ME(Redis, rawCommand, NULL, ZEND_ACC_PUBLIC) /* introspection */ PHP_ME(Redis, getHost, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getPort, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getDBNum, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getTimeout, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getReadTimeout, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getPersistentID, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getAuth, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, isConnected, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getMode, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, wait, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pubsub, NULL, ZEND_ACC_PUBLIC) /* aliases */ PHP_MALIAS(Redis, open, connect, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, popen, pconnect, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, lLen, lSize, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, sGetMembers, sMembers, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, mget, getMultiple, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, expire, setTimeout, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zunionstore, zUnion, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zinterstore, zInter, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zRemove, zDelete, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zRem, zDelete, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zRemoveRangeByScore, zDeleteRangeByScore, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zRemRangeByScore, zDeleteRangeByScore, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zRemRangeByRank, zDeleteRangeByRank, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zSize, zCard, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, substr, getRange, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, rename, renameKey, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, del, delete, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, keys, getKeys, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, lrem, lRemove, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, ltrim, listTrim, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, lindex, lGet, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, lrange, lGetRange, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, scard, sSize, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, srem, sRemove, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, sismember, sContains, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zrevrange, zReverseRange, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, sendEcho, echo, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, evaluate, eval, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, evaluateSha, evalsha, NULL, ZEND_ACC_PUBLIC) {NULL, NULL, NULL} }; zend_module_entry redis_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif "redis", NULL, PHP_MINIT(redis), PHP_MSHUTDOWN(redis), PHP_RINIT(redis), PHP_RSHUTDOWN(redis), PHP_MINFO(redis), #if ZEND_MODULE_API_NO >= 20010901 PHP_REDIS_VERSION, #endif STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_REDIS ZEND_GET_MODULE(redis) #endif PHP_REDIS_API zend_class_entry *redis_get_exception_base(int root TSRMLS_DC) { #if HAVE_SPL if (!root) { if (!spl_ce_RuntimeException) { zend_class_entry **pce; if (zend_hash_find(CG(class_table), "runtimeexception", sizeof("RuntimeException"), (void **) &pce) == SUCCESS) { spl_ce_RuntimeException = *pce; return *pce; } } else { return spl_ce_RuntimeException; } } #endif #if (PHP_MAJOR_VERSION == 5) && (PHP_MINOR_VERSION < 2) return zend_exception_get_default(); #else return zend_exception_get_default(TSRMLS_C); #endif } /** * Send a static DISCARD in case we're in MULTI mode. */ static int send_discard_static(RedisSock *redis_sock TSRMLS_DC) { int result = FAILURE; char *cmd, *response; int response_len, cmd_len; /* format our discard command */ cmd_len = redis_cmd_format_static(&cmd, "DISCARD", ""); /* send our DISCARD command */ if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) >= 0 && (response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) != NULL) { /* success if we get OK */ result = (response_len == 3 && strncmp(response,"+OK", 3) == 0) ? SUCCESS : FAILURE; /* free our response */ efree(response); } /* free our command */ efree(cmd); /* return success/failure */ return result; } /** * redis_destructor_redis_sock */ static void redis_destructor_redis_sock(zend_rsrc_list_entry * rsrc TSRMLS_DC) { RedisSock *redis_sock = (RedisSock *) rsrc->ptr; redis_sock_disconnect(redis_sock TSRMLS_CC); redis_free_socket(redis_sock); } /** * redis_sock_get */ PHP_REDIS_API int redis_sock_get(zval *id, RedisSock **redis_sock TSRMLS_DC, int no_throw) { zval **socket; int resource_type; if (Z_TYPE_P(id) != IS_OBJECT || zend_hash_find(Z_OBJPROP_P(id), "socket", sizeof("socket"), (void **) &socket) == FAILURE) { /* Throw an exception unless we've been requested not to */ if(!no_throw) { zend_throw_exception(redis_exception_ce, "Redis server went away", 0 TSRMLS_CC); } return -1; } *redis_sock = (RedisSock *) zend_list_find(Z_LVAL_PP(socket), &resource_type); if (!*redis_sock || resource_type != le_redis_sock) { /* Throw an exception unless we've been requested not to */ if(!no_throw) { zend_throw_exception(redis_exception_ce, "Redis server went away", 0 TSRMLS_CC); } return -1; } if ((*redis_sock)->lazy_connect) { (*redis_sock)->lazy_connect = 0; if (redis_sock_server_open(*redis_sock, 1 TSRMLS_CC) < 0) { return -1; } } return Z_LVAL_PP(socket); } /** * redis_sock_get_direct * Returns our attached RedisSock pointer if we're connected */ PHP_REDIS_API RedisSock *redis_sock_get_connected(INTERNAL_FUNCTION_PARAMETERS) { zval *object; RedisSock *redis_sock; /* If we can't grab our object, or get a socket, or we're not connected, return NULL */ if((zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) || (redis_sock_get(object, &redis_sock TSRMLS_CC, 1) < 0) || redis_sock->status != REDIS_SOCK_STATUS_CONNECTED) { return NULL; } /* Return our socket */ return redis_sock; } /** * PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(redis) { zend_class_entry redis_class_entry; zend_class_entry redis_array_class_entry; zend_class_entry redis_exception_class_entry; REGISTER_INI_ENTRIES(); /* Redis class */ INIT_CLASS_ENTRY(redis_class_entry, "Redis", redis_functions); redis_ce = zend_register_internal_class(&redis_class_entry TSRMLS_CC); /* RedisArray class */ INIT_CLASS_ENTRY(redis_array_class_entry, "RedisArray", redis_array_functions); redis_array_ce = zend_register_internal_class(&redis_array_class_entry TSRMLS_CC); le_redis_array = zend_register_list_destructors_ex( redis_destructor_redis_array, NULL, "Redis Array", module_number ); /* RedisException class */ INIT_CLASS_ENTRY(redis_exception_class_entry, "RedisException", NULL); redis_exception_ce = zend_register_internal_class_ex( &redis_exception_class_entry, redis_get_exception_base(0 TSRMLS_CC), NULL TSRMLS_CC ); le_redis_sock = zend_register_list_destructors_ex( redis_destructor_redis_sock, NULL, redis_sock_name, module_number ); add_constant_long(redis_ce, "REDIS_NOT_FOUND", REDIS_NOT_FOUND); add_constant_long(redis_ce, "REDIS_STRING", REDIS_STRING); add_constant_long(redis_ce, "REDIS_SET", REDIS_SET); add_constant_long(redis_ce, "REDIS_LIST", REDIS_LIST); add_constant_long(redis_ce, "REDIS_ZSET", REDIS_ZSET); add_constant_long(redis_ce, "REDIS_HASH", REDIS_HASH); add_constant_long(redis_ce, "ATOMIC", ATOMIC); add_constant_long(redis_ce, "MULTI", MULTI); add_constant_long(redis_ce, "PIPELINE", PIPELINE); /* options */ add_constant_long(redis_ce, "OPT_SERIALIZER", REDIS_OPT_SERIALIZER); add_constant_long(redis_ce, "OPT_PREFIX", REDIS_OPT_PREFIX); add_constant_long(redis_ce, "OPT_READ_TIMEOUT", REDIS_OPT_READ_TIMEOUT); /* serializer */ add_constant_long(redis_ce, "SERIALIZER_NONE", REDIS_SERIALIZER_NONE); add_constant_long(redis_ce, "SERIALIZER_PHP", REDIS_SERIALIZER_PHP); /* scan options*/ add_constant_long(redis_ce, "OPT_SCAN", REDIS_OPT_SCAN); add_constant_long(redis_ce, "SCAN_RETRY", REDIS_SCAN_RETRY); add_constant_long(redis_ce, "SCAN_NORETRY", REDIS_SCAN_NORETRY); #ifdef HAVE_REDIS_IGBINARY add_constant_long(redis_ce, "SERIALIZER_IGBINARY", REDIS_SERIALIZER_IGBINARY); #endif zend_declare_class_constant_stringl(redis_ce, "AFTER", 5, "after", 5 TSRMLS_CC); zend_declare_class_constant_stringl(redis_ce, "BEFORE", 6, "before", 6 TSRMLS_CC); #ifdef PHP_SESSION /* declare session handler */ php_session_register_module(&ps_mod_redis); #endif return SUCCESS; } /** * PHP_MSHUTDOWN_FUNCTION */ PHP_MSHUTDOWN_FUNCTION(redis) { return SUCCESS; } /** * PHP_RINIT_FUNCTION */ PHP_RINIT_FUNCTION(redis) { return SUCCESS; } /** * PHP_RSHUTDOWN_FUNCTION */ PHP_RSHUTDOWN_FUNCTION(redis) { return SUCCESS; } /** * PHP_MINFO_FUNCTION */ PHP_MINFO_FUNCTION(redis) { php_info_print_table_start(); php_info_print_table_header(2, "Redis Support", "enabled"); php_info_print_table_row(2, "Redis Version", PHP_REDIS_VERSION); php_info_print_table_end(); } /* {{{ proto Redis Redis::__construct() Public constructor */ PHP_METHOD(Redis, __construct) { if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) { RETURN_FALSE; } } /* }}} */ /* {{{ proto Redis Redis::__destruct() Public Destructor */ PHP_METHOD(Redis,__destruct) { RedisSock *redis_sock; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) { RETURN_FALSE; } /* Grab our socket */ if (redis_sock_get(getThis(), &redis_sock TSRMLS_CC, 1) < 0) { RETURN_FALSE; } /* If we think we're in MULTI mode, send a discard */ if(redis_sock->mode == MULTI) { /* Discard any multi commands, and free any callbacks that have been queued */ send_discard_static(redis_sock TSRMLS_CC); free_reply_callbacks(getThis(), redis_sock); } } /* {{{ proto boolean Redis::connect(string host, int port [, double timeout [, long retry_interval]]) */ PHP_METHOD(Redis, connect) { if (redis_connect(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0) == FAILURE) { RETURN_FALSE; } else { RETURN_TRUE; } } /* }}} */ /* {{{ proto boolean Redis::pconnect(string host, int port [, double timeout]) */ PHP_METHOD(Redis, pconnect) { if (redis_connect(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1) == FAILURE) { RETURN_FALSE; } else { /* reset multi/exec state if there is one. */ RedisSock *redis_sock; if (redis_sock_get(getThis(), &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } RETURN_TRUE; } } /* }}} */ PHP_REDIS_API int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) { zval *object; zval **socket; int host_len, id; char *host = NULL; long port = -1; long retry_interval = 0; char *persistent_id = NULL; int persistent_id_len = -1; double timeout = 0.0; RedisSock *redis_sock = NULL; #ifdef ZTS /* not sure how in threaded mode this works so disabled persistents at first */ persistent = 0; #endif if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|ldsl", &object, redis_ce, &host, &host_len, &port, &timeout, &persistent_id, &persistent_id_len, &retry_interval) == FAILURE) { return FAILURE; } if (timeout < 0L || timeout > INT_MAX) { zend_throw_exception(redis_exception_ce, "Invalid timeout", 0 TSRMLS_CC); return FAILURE; } if (retry_interval < 0L || retry_interval > INT_MAX) { zend_throw_exception(redis_exception_ce, "Invalid retry interval", 0 TSRMLS_CC); return FAILURE; } if(port == -1 && host_len && host[0] != '/') { /* not unix socket, set to default value */ port = 6379; } /* if there is a redis sock already we have to remove it from the list */ if (redis_sock_get(object, &redis_sock TSRMLS_CC, 1) > 0) { if (zend_hash_find(Z_OBJPROP_P(object), "socket", sizeof("socket"), (void **) &socket) == FAILURE) { /* maybe there is a socket but the id isn't known.. what to do? */ } else { zend_list_delete(Z_LVAL_PP(socket)); /* the refcount should be decreased and the detructor called */ } } redis_sock = redis_sock_create(host, host_len, port, timeout, persistent, persistent_id, retry_interval, 0); if (redis_sock_server_open(redis_sock, 1 TSRMLS_CC) < 0) { redis_free_socket(redis_sock); return FAILURE; } #if PHP_VERSION_ID >= 50400 id = zend_list_insert(redis_sock, le_redis_sock TSRMLS_CC); #else id = zend_list_insert(redis_sock, le_redis_sock); #endif add_property_resource(object, "socket", id); return SUCCESS; } /* {{{ proto boolean Redis::bitop(string op, string key, ...) */ PHP_METHOD(Redis, bitop) { char *cmd; int cmd_len; zval **z_args; char **keys; int *keys_len; int argc = ZEND_NUM_ARGS(), i; RedisSock *redis_sock = NULL; smart_str buf = {0}; int key_free = 0; /* get redis socket */ if (redis_sock_get(getThis(), &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* fetch args */ z_args = emalloc(argc * sizeof(zval*)); if(zend_get_parameters_array(ht, argc, z_args) == FAILURE || argc < 3 /* 3 args min. */ || Z_TYPE_P(z_args[0]) != IS_STRING /* operation must be a string. */ ) { efree(z_args); RETURN_FALSE; } keys = emalloc(argc * sizeof(char*)); keys_len = emalloc(argc * sizeof(int)); /* prefix keys */ for(i = 0; i < argc; ++i) { convert_to_string(z_args[i]); keys[i] = Z_STRVAL_P(z_args[i]); keys_len[i] = Z_STRLEN_P(z_args[i]); if(i != 0) key_free = redis_key_prefix(redis_sock, &keys[i], &keys_len[i] TSRMLS_CC); } /* start building the command */ smart_str_appendc(&buf, '*'); smart_str_append_long(&buf, argc + 1); /* +1 for BITOP command */ smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); /* add command name */ smart_str_appendc(&buf, '$'); smart_str_append_long(&buf, 5); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); smart_str_appendl(&buf, "BITOP", 5); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); /* add keys */ for(i = 0; i < argc; ++i) { smart_str_appendc(&buf, '$'); smart_str_append_long(&buf, keys_len[i]); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); smart_str_appendl(&buf, keys[i], keys_len[i]); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); } /* end string */ smart_str_0(&buf); cmd = buf.c; cmd_len = buf.len; /* cleanup */ if(key_free) for(i = 1; i < argc; ++i) { efree(keys[i]); } efree(keys); efree(keys_len); efree(z_args); /* send */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::bitcount(string key, [int start], [int end]) */ PHP_METHOD(Redis, bitcount) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; long start = 0, end = -1; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|ll", &object, redis_ce, &key, &key_len, &start, &end) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* BITCOUNT key start end */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "BITCOUNT", "sdd", key, key_len, (int)start, (int)end); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto integer Redis::bitpos(string key, int bit, [int start], [int end]) */ PHP_METHOD(Redis, bitpos) { zval *object; RedisSock *redis_sock; char *key, *cmd; int key_len, cmd_len, argc, key_free=0; long bit, start, end; argc = ZEND_NUM_ARGS(); if(zend_parse_method_parameters(argc TSRMLS_CC, getThis(), "Osl|ll", &object, redis_ce, &key, &key_len, &bit, &start, &end)==FAILURE) { RETURN_FALSE; } if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* We can prevalidate the first argument */ if(bit != 0 && bit != 1) { RETURN_FALSE; } /* Prefix our key */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); /* Various command semantics */ if(argc == 2) { cmd_len = redis_cmd_format_static(&cmd, "BITPOS", "sd", key, key_len, bit); } else if(argc == 3) { cmd_len = redis_cmd_format_static(&cmd, "BITPOS", "sdd", key, key_len, bit, start); } else { cmd_len = redis_cmd_format_static(&cmd, "BITPOS", "sddd", key, key_len, bit, start, end); } /* Free our key if it was prefixed */ if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::close() */ PHP_METHOD(Redis, close) { zval *object; RedisSock *redis_sock = NULL; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } if (redis_sock_disconnect(redis_sock TSRMLS_CC)) { RETURN_TRUE; } RETURN_FALSE; } /* }}} */ /* {{{ proto boolean Redis::set(string key, mixed value, long timeout | array options) */ PHP_METHOD(Redis, set) { zval *object; RedisSock *redis_sock; char *key = NULL, *val = NULL, *cmd, *exp_type = NULL, *set_type = NULL; int key_len, val_len, cmd_len; long expire = -1; int val_free = 0, key_free = 0; zval *z_value, *z_opts = NULL; /* Make sure the arguments are correct */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz|z", &object, redis_ce, &key, &key_len, &z_value, &z_opts) == FAILURE) { RETURN_FALSE; } /* Ensure we can grab our redis socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Our optional argument can either be a long (to support legacy SETEX */ /* redirection), or an array with Redis >= 2.6.12 set options */ if(z_opts && Z_TYPE_P(z_opts) != IS_LONG && Z_TYPE_P(z_opts) != IS_ARRAY && Z_TYPE_P(z_opts) != IS_NULL) { RETURN_FALSE; } /* Serialization, key prefixing */ val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); if(z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) { HashTable *kt = Z_ARRVAL_P(z_opts); int type; unsigned int ht_key_len; unsigned long idx; char *k; zval **v; /* Iterate our option array */ for(zend_hash_internal_pointer_reset(kt); zend_hash_has_more_elements(kt) == SUCCESS; zend_hash_move_forward(kt)) { /* Grab key and value */ type = zend_hash_get_current_key_ex(kt, &k, &ht_key_len, &idx, 0, NULL); zend_hash_get_current_data(kt, (void**)&v); if(type == HASH_KEY_IS_STRING && (Z_TYPE_PP(v) == IS_LONG) && (Z_LVAL_PP(v) > 0) && IS_EX_PX_ARG(k)) { exp_type = k; expire = Z_LVAL_PP(v); } else if(Z_TYPE_PP(v) == IS_STRING && IS_NX_XX_ARG(Z_STRVAL_PP(v))) { set_type = Z_STRVAL_PP(v); } } } else if(z_opts && Z_TYPE_P(z_opts) == IS_LONG) { expire = Z_LVAL_P(z_opts); } /* Now let's construct the command we want */ if(exp_type && set_type) { /* SET <key> <value> NX|XX PX|EX <timeout> */ cmd_len = redis_cmd_format_static(&cmd, "SET", "ssssl", key, key_len, val, val_len, set_type, 2, exp_type, 2, expire); } else if(exp_type) { /* SET <key> <value> PX|EX <timeout> */ cmd_len = redis_cmd_format_static(&cmd, "SET", "sssl", key, key_len, val, val_len, exp_type, 2, expire); } else if(set_type) { /* SET <key> <value> NX|XX */ cmd_len = redis_cmd_format_static(&cmd, "SET", "sss", key, key_len, val, val_len, set_type, 2); } else if(expire > 0) { /* Backward compatible SETEX redirection */ cmd_len = redis_cmd_format_static(&cmd, "SETEX", "sls", key, key_len, expire, val, val_len); } else { /* SET <key> <value> */ cmd_len = redis_cmd_format_static(&cmd, "SET", "ss", key, key_len, val, val_len); } /* Free our key or value if we prefixed/serialized */ if(key_free) efree(key); if(val_free) STR_FREE(val); /* Kick off the command */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } PHP_REDIS_API void redis_generic_setex(INTERNAL_FUNCTION_PARAMETERS, char *keyword) { zval *object; RedisSock *redis_sock; char *key = NULL, *val = NULL, *cmd; int key_len, val_len, cmd_len; long expire; int val_free = 0, key_free = 0; zval *z_value; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oslz", &object, redis_ce, &key, &key_len, &expire, &z_value) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, keyword, "sls", key, key_len, expire, val, val_len); if(val_free) STR_FREE(val); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } /* {{{ proto boolean Redis::setex(string key, long expire, string value) */ PHP_METHOD(Redis, setex) { redis_generic_setex(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SETEX"); } /* {{{ proto boolean Redis::psetex(string key, long expire, string value) */ PHP_METHOD(Redis, psetex) { redis_generic_setex(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PSETEX"); } /* {{{ proto boolean Redis::setnx(string key, string value) */ PHP_METHOD(Redis, setnx) { zval *object; RedisSock *redis_sock; char *key = NULL, *val = NULL, *cmd; int key_len, val_len, cmd_len; int val_free = 0, key_free = 0; zval *z_value; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz", &object, redis_ce, &key, &key_len, &z_value) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "SETNX", "ss", key, key_len, val, val_len); if(val_free) STR_FREE(val); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_1_response); } /* }}} */ /* {{{ proto string Redis::getSet(string key, string value) */ PHP_METHOD(Redis, getSet) { zval *object; RedisSock *redis_sock; char *key = NULL, *val = NULL, *cmd; int key_len, val_len, cmd_len; int val_free = 0, key_free = 0; zval *z_value; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz", &object, redis_ce, &key, &key_len, &z_value) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "GETSET", "ss", key, key_len, val, val_len); if(val_free) STR_FREE(val); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_string_response); } /* }}} */ /* {{{ proto string Redis::randomKey() */ PHP_METHOD(Redis, randomKey) { zval *object; RedisSock *redis_sock; char *cmd; int cmd_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } cmd_len = redis_cmd_format(&cmd, "*1" _NL "$9" _NL "RANDOMKEY" _NL); /* TODO: remove prefix from key */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_ping_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_ping_response); } /* }}} */ /* {{{ proto string Redis::echo(string key) */ PHP_METHOD(Redis, echo) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len; int key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "ECHO", "s", key, key_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_string_response); } /* }}} */ /* {{{ proto string Redis::renameKey(string key_src, string key_dst) */ PHP_METHOD(Redis, renameKey) { zval *object; RedisSock *redis_sock; char *cmd, *src, *dst; int cmd_len, src_len, dst_len; int src_free, dst_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", &object, redis_ce, &src, &src_len, &dst, &dst_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } src_free = redis_key_prefix(redis_sock, &src, &src_len TSRMLS_CC); dst_free = redis_key_prefix(redis_sock, &dst, &dst_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "RENAME", "ss", src, src_len, dst, dst_len); if(src_free) efree(src); if(dst_free) efree(dst); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } /* }}} */ /* {{{ proto string Redis::renameNx(string key_src, string key_dst) */ PHP_METHOD(Redis, renameNx) { zval *object; RedisSock *redis_sock; char *cmd, *src, *dst; int cmd_len, src_len, dst_len; int src_free, dst_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", &object, redis_ce, &src, &src_len, &dst, &dst_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } src_free = redis_key_prefix(redis_sock, &src, &src_len TSRMLS_CC); dst_free = redis_key_prefix(redis_sock, &dst, &dst_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "RENAMENX", "ss", src, src_len, dst, dst_len); if(src_free) efree(src); if(dst_free) efree(dst); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_1_response); } /* }}} */ /* {{{ proto string Redis::get(string key) */ PHP_METHOD(Redis, get) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len; int key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "GET", "s", key, key_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_string_response); } /* }}} */ /* {{{ proto string Redis::ping() */ PHP_METHOD(Redis, ping) { zval *object; RedisSock *redis_sock; char *cmd; int cmd_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } cmd_len = redis_cmd_format_static(&cmd, "PING", ""); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_ping_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_ping_response); } /* }}} */ PHP_REDIS_API void redis_atomic_increment(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int count) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len; long val = 1; int key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", &object, redis_ce, &key, &key_len, &val) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); if (val == 1) { cmd_len = redis_cmd_format_static(&cmd, keyword, "s", key, key_len); } else { cmd_len = redis_cmd_format_static(&cmd, keyword, "sl", key, key_len, val); } if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* {{{ proto boolean Redis::incr(string key [,int value]) */ PHP_METHOD(Redis, incr){ zval *object; char *key = NULL; int key_len; long val = 1; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", &object, redis_ce, &key, &key_len, &val) == FAILURE) { RETURN_FALSE; } if(val == 1) { redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "INCR", 1); } else { redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "INCRBY", val); } } /* }}} */ /* {{{ proto boolean Redis::incrBy(string key ,int value) */ PHP_METHOD(Redis, incrBy){ zval *object; char *key = NULL; int key_len; long val = 1; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osl", &object, redis_ce, &key, &key_len, &val) == FAILURE) { RETURN_FALSE; } if(val == 1) { redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "INCR", 1); } else { redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "INCRBY", val); } } /* }}} */ /* {{{ proto float Redis::incrByFloat(string key, float value) */ PHP_METHOD(Redis, incrByFloat) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; double val; if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osd", &object, redis_ce, &key, &key_len, &val) == FAILURE) { RETURN_FALSE; } if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Prefix key, format command, free old key if necissary */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "INCRBYFLOAT", "sf", key, key_len, val); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_bulk_double_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_bulk_double_response); } /* {{{ proto boolean Redis::decr(string key [,int value]) */ PHP_METHOD(Redis, decr) { zval *object; char *key = NULL; int key_len; long val = 1; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", &object, redis_ce, &key, &key_len, &val) == FAILURE) { RETURN_FALSE; } if(val == 1) { redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DECR", 1); } else { redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DECRBY", val); } } /* }}} */ /* {{{ proto boolean Redis::decrBy(string key ,int value) */ PHP_METHOD(Redis, decrBy){ zval *object; char *key = NULL; int key_len; long val = 1; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osl", &object, redis_ce, &key, &key_len, &val) == FAILURE) { RETURN_FALSE; } if(val == 1) { redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DECR", 1); } else { redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DECRBY", val); } } /* }}} */ /* {{{ proto array Redis::getMultiple(array keys) */ PHP_METHOD(Redis, getMultiple) { zval *object, *z_args, **z_ele; HashTable *hash; HashPosition ptr; RedisSock *redis_sock; smart_str cmd = {0}; int arg_count; /* Make sure we have proper arguments */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", &object, redis_ce, &z_args) == FAILURE) { RETURN_FALSE; } /* We'll need the socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Grab our array */ hash = Z_ARRVAL_P(z_args); /* We don't need to do anything if there aren't any keys */ if((arg_count = zend_hash_num_elements(hash)) == 0) { RETURN_FALSE; } /* Build our command header */ redis_cmd_init_sstr(&cmd, arg_count, "MGET", 4); /* Iterate through and grab our keys */ for(zend_hash_internal_pointer_reset_ex(hash, &ptr); zend_hash_get_current_data_ex(hash, (void**)&z_ele, &ptr) == SUCCESS; zend_hash_move_forward_ex(hash, &ptr)) { char *key; int key_len, key_free; zval *z_tmp = NULL; /* If the key isn't a string, turn it into one */ if(Z_TYPE_PP(z_ele) == IS_STRING) { key = Z_STRVAL_PP(z_ele); key_len = Z_STRLEN_PP(z_ele); } else { MAKE_STD_ZVAL(z_tmp); *z_tmp = **z_ele; zval_copy_ctor(z_tmp); convert_to_string(z_tmp); key = Z_STRVAL_P(z_tmp); key_len = Z_STRLEN_P(z_tmp); } /* Apply key prefix if necissary */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); /* Append this key to our command */ redis_cmd_append_sstr(&cmd, key, key_len); /* Free our key if it was prefixed */ if(key_free) efree(key); /* Free oour temporary ZVAL if we converted from a non-string */ if(z_tmp) { zval_dtor(z_tmp); efree(z_tmp); z_tmp = NULL; } } /* Kick off our command */ REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); IF_ATOMIC() { if(redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } /* {{{ proto boolean Redis::exists(string key) */ PHP_METHOD(Redis, exists) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len; int key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "EXISTS", "s", key, key_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_1_response); } /* }}} */ /* {{{ proto boolean Redis::delete(string key) */ PHP_METHOD(Redis, delete) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DEL", sizeof("DEL") - 1, 1, &redis_sock, 0, 1, 1)) return; IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ PHP_REDIS_API void redis_set_watch(RedisSock *redis_sock) { redis_sock->watching = 1; } PHP_REDIS_API void redis_watch_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, ctx, redis_set_watch); } /* {{{ proto boolean Redis::watch(string key1, string key2...) */ PHP_METHOD(Redis, watch) { RedisSock *redis_sock; generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "WATCH", sizeof("WATCH") - 1, 1, &redis_sock, 0, 1, 1); redis_sock->watching = 1; IF_ATOMIC() { redis_watch_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_watch_response); } /* }}} */ PHP_REDIS_API void redis_clear_watch(RedisSock *redis_sock) { redis_sock->watching = 0; } PHP_REDIS_API void redis_unwatch_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, ctx, redis_clear_watch); } /* {{{ proto boolean Redis::unwatch() */ PHP_METHOD(Redis, unwatch) { char cmd[] = "*1" _NL "$7" _NL "UNWATCH" _NL; generic_empty_cmd_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, estrdup(cmd), sizeof(cmd)-1, redis_unwatch_response); } /* }}} */ /* {{{ proto array Redis::getKeys(string pattern) */ PHP_METHOD(Redis, getKeys) { zval *object; RedisSock *redis_sock; char *pattern = NULL, *cmd; int pattern_len, cmd_len, pattern_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &pattern, &pattern_len) == FAILURE) { RETURN_NULL(); } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } pattern_free = redis_key_prefix(redis_sock, &pattern, &pattern_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "KEYS", "s", pattern, pattern_len); if(pattern_free) efree(pattern); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { if (redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_mbulk_reply_raw); } /* }}} */ /* {{{ proto int Redis::type(string key) */ PHP_METHOD(Redis, type) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_NULL(); } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "TYPE", "s", key, key_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_type_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_type_response); } /* }}} */ PHP_METHOD(Redis, append) { zval *object; RedisSock *redis_sock; char *cmd; int cmd_len, key_len, val_len, key_free; char *key, *val; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", &object, redis_ce, &key, &key_len, &val, &val_len) == FAILURE) { RETURN_NULL(); } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "APPEND", "ss", key, key_len, val, val_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } PHP_METHOD(Redis, getRange) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; long start, end; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osll", &object, redis_ce, &key, &key_len, &start, &end) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "GETRANGE", "sdd", key, key_len, (int)start, (int)end); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_string_response); } PHP_METHOD(Redis, setRange) { zval *object; RedisSock *redis_sock; char *key = NULL, *val, *cmd; int key_len, val_len, cmd_len, key_free; long offset; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osls", &object, redis_ce, &key, &key_len, &offset, &val, &val_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "SETRANGE", "sds", key, key_len, (int)offset, val, val_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } PHP_METHOD(Redis, getBit) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; long offset; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osl", &object, redis_ce, &key, &key_len, &offset) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* GETBIT and SETBIT only work for 0 - 2^32-1 */ if(offset < BITOP_MIN_OFFSET || offset > BITOP_MAX_OFFSET) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "GETBIT", "sd", key, key_len, (int)offset); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } PHP_METHOD(Redis, setBit) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; long offset; zend_bool val; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oslb", &object, redis_ce, &key, &key_len, &offset, &val) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* GETBIT and SETBIT only work for 0 - 2^32-1 */ if(offset < BITOP_MIN_OFFSET || offset > BITOP_MAX_OFFSET) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "SETBIT", "sdd", key, key_len, (int)offset, (int)val); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } PHP_METHOD(Redis, strlen) { zval *object; RedisSock *redis_sock; char *cmd; int cmd_len, key_len, key_free; char *key; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_NULL(); } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "STRLEN", "s", key, key_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } PHP_REDIS_API void generic_push_function(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len) { zval *object; RedisSock *redis_sock; char *cmd, *key, *val; int cmd_len, key_len, val_len; zval *z_value; int val_free, key_free = 0; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz", &object, redis_ce, &key, &key_len, &z_value) == FAILURE) { RETURN_NULL(); } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, keyword, "ss", key, key_len, val, val_len); if(val_free) STR_FREE(val); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* {{{ proto boolean Redis::lPush(string key , string value) */ PHP_METHOD(Redis, lPush) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "LPUSH", sizeof("LPUSH") - 1, 2, &redis_sock, 0, 0, 1)) return; IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::rPush(string key , string value) */ PHP_METHOD(Redis, rPush) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "RPUSH", sizeof("RPUSH") - 1, 2, &redis_sock, 0, 0, 1)) return; IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ PHP_METHOD(Redis, lInsert) { zval *object; RedisSock *redis_sock; char *pivot, *position, *key, *val, *cmd; int pivot_len, position_len, key_len, val_len, cmd_len; int val_free, pivot_free, key_free; zval *z_value, *z_pivot; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osszz", &object, redis_ce, &key, &key_len, &position, &position_len, &z_pivot, &z_value) == FAILURE) { RETURN_NULL(); } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } if(strncasecmp(position, "after", 5) == 0 || strncasecmp(position, "before", 6) == 0) { key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); pivot_free = redis_serialize(redis_sock, z_pivot, &pivot, &pivot_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "LINSERT", "ssss", key, key_len, position, position_len, pivot, pivot_len, val, val_len); if(val_free) STR_FREE(val); if(key_free) efree(key); if(pivot_free) STR_FREE(pivot); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } else { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Error on position"); } } PHP_METHOD(Redis, lPushx) { generic_push_function(INTERNAL_FUNCTION_PARAM_PASSTHRU, "LPUSHX", sizeof("LPUSHX")-1); } PHP_METHOD(Redis, rPushx) { generic_push_function(INTERNAL_FUNCTION_PARAM_PASSTHRU, "RPUSHX", sizeof("RPUSHX")-1); } PHP_REDIS_API void generic_pop_function(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, keyword, "s", key, key_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_string_response); } /* {{{ proto string Redis::lPOP(string key) */ PHP_METHOD(Redis, lPop) { generic_pop_function(INTERNAL_FUNCTION_PARAM_PASSTHRU, "LPOP", sizeof("LPOP")-1); } /* }}} */ /* {{{ proto string Redis::rPOP(string key) */ PHP_METHOD(Redis, rPop) { generic_pop_function(INTERNAL_FUNCTION_PARAM_PASSTHRU, "RPOP", sizeof("RPOP")-1); } /* }}} */ /* {{{ proto string Redis::blPop(string key1, string key2, ..., int timeout) */ PHP_METHOD(Redis, blPop) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "BLPOP", sizeof("BLPOP") - 1, 2, &redis_sock, 1, 1, 1)) return; IF_ATOMIC() { if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } /* }}} */ /* {{{ proto string Redis::brPop(string key1, string key2, ..., int timeout) */ PHP_METHOD(Redis, brPop) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "BRPOP", sizeof("BRPOP") - 1, 2, &redis_sock, 1, 1, 1)) return; IF_ATOMIC() { if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } /* }}} */ /* {{{ proto int Redis::lSize(string key) */ PHP_METHOD(Redis, lSize) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "LLEN", "s", key, key_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::lRemove(string list, string value, int count = 0) */ PHP_METHOD(Redis, lRemove) { zval *object; RedisSock *redis_sock; char *cmd; int cmd_len, key_len, val_len; char *key, *val; long count = 0; zval *z_value; int val_free, key_free = 0; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz|l", &object, redis_ce, &key, &key_len, &z_value, &count) == FAILURE) { RETURN_NULL(); } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* LREM key count value */ val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "LREM", "sds", key, key_len, count, val, val_len); if(val_free) STR_FREE(val); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::listTrim(string key , int start , int end) */ PHP_METHOD(Redis, listTrim) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; long start, end; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osll", &object, redis_ce, &key, &key_len, &start, &end) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "LTRIM", "sdd", key, key_len, (int)start, (int)end); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } /* }}} */ /* {{{ proto string Redis::lGet(string key , int index) */ PHP_METHOD(Redis, lGet) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; long index; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osl", &object, redis_ce, &key, &key_len, &index) == FAILURE) { RETURN_NULL(); } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* LINDEX key pos */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "LINDEX", "sd", key, key_len, (int)index); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_string_response); } /* }}} */ /* {{{ proto array Redis::lGetRange(string key, int start , int end) */ PHP_METHOD(Redis, lGetRange) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; long start, end; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osll", &object, redis_ce, &key, &key_len, &start, &end) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* LRANGE key start end */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "LRANGE", "sdd", key, key_len, (int)start, (int)end); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } /* }}} */ /* {{{ proto boolean Redis::sAdd(string key , mixed value) */ PHP_METHOD(Redis, sAdd) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SADD", sizeof("SADD") - 1, 2, &redis_sock, 0, 0, 1)) return; IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto int Redis::sSize(string key) */ PHP_METHOD(Redis, sSize) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "SCARD", "s", key, key_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::sRemove(string set, string value) */ PHP_METHOD(Redis, sRemove) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SREM", sizeof("SREM") - 1, 2, &redis_sock, 0, 0, 1)) return; IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::sMove(string set_src, string set_dst, mixed value) */ PHP_METHOD(Redis, sMove) { zval *object; RedisSock *redis_sock; char *src = NULL, *dst = NULL, *val = NULL, *cmd; int src_len, dst_len, val_len, cmd_len; int val_free, src_free, dst_free; zval *z_value; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ossz", &object, redis_ce, &src, &src_len, &dst, &dst_len, &z_value) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); src_free = redis_key_prefix(redis_sock, &src, &src_len TSRMLS_CC); dst_free = redis_key_prefix(redis_sock, &dst, &dst_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "SMOVE", "sss", src, src_len, dst, dst_len, val, val_len); if(val_free) STR_FREE(val); if(src_free) efree(src); if(dst_free) efree(dst); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_1_response); } /* }}} */ /* }}} */ /* {{{ proto string Redis::sPop(string key) */ PHP_METHOD(Redis, sPop) { generic_pop_function(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SPOP", 4); } /* }}} */ /* }}} */ /* {{{ proto string Redis::sRandMember(string key [int count]) */ PHP_METHOD(Redis, sRandMember) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free = 0; long count; /* Parse our params */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", &object, redis_ce, &key, &key_len, &count) == FAILURE) { RETURN_FALSE; } /* Get our redis socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Prefix our key if necissary */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); /* If we have two arguments, we're running with an optional COUNT, which will return */ /* a multibulk reply. Without the argument we'll return a string response */ if(ZEND_NUM_ARGS() == 2) { /* Construct our command with count */ cmd_len = redis_cmd_format_static(&cmd, "SRANDMEMBER", "sl", key, key_len, count); } else { /* Construct our command */ cmd_len = redis_cmd_format_static(&cmd, "SRANDMEMBER", "s", key, key_len); } /* Free our key if we prefixed it */ if(key_free) efree(key); /* Process our command */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); /* Either bulk or multi-bulk depending on argument count */ if(ZEND_NUM_ARGS() == 2) { IF_ATOMIC() { if(redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } else { IF_ATOMIC() { redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_string_response); } } /* }}} */ /* {{{ proto boolean Redis::sContains(string set, string value) */ PHP_METHOD(Redis, sContains) { zval *object; RedisSock *redis_sock; char *key = NULL, *val = NULL, *cmd; int key_len, val_len, cmd_len; int val_free, key_free = 0; zval *z_value; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz", &object, redis_ce, &key, &key_len, &z_value) == FAILURE) { return; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "SISMEMBER", "ss", key, key_len, val, val_len); if(val_free) STR_FREE(val); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_1_response); } /* }}} */ /* {{{ proto array Redis::sMembers(string set) */ PHP_METHOD(Redis, sMembers) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "SMEMBERS", "s", key, key_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } /* }}} */ PHP_REDIS_API int generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len, int min_argc, RedisSock **out_sock, int has_timeout, int all_keys, int can_serialize) { zval **z_args, *z_array; char **keys, *cmd; int cmd_len, *keys_len, *keys_to_free; int i, j, argc = ZEND_NUM_ARGS(), real_argc = 0; int single_array = 0; int timeout = 0; int pos; int array_size; RedisSock *redis_sock; if(argc < min_argc) { zend_wrong_param_count(TSRMLS_C); ZVAL_BOOL(return_value, 0); return FAILURE; } /* get redis socket */ if (redis_sock_get(getThis(), out_sock TSRMLS_CC, 0) < 0) { ZVAL_BOOL(return_value, 0); return FAILURE; } redis_sock = *out_sock; z_args = emalloc(argc * sizeof(zval*)); if(zend_get_parameters_array(ht, argc, z_args) == FAILURE) { efree(z_args); ZVAL_BOOL(return_value, 0); return FAILURE; } /* case of a single array */ if(has_timeout == 0) { if(argc == 1 && Z_TYPE_P(z_args[0]) == IS_ARRAY) { single_array = 1; z_array = z_args[0]; efree(z_args); z_args = NULL; /* new count */ argc = zend_hash_num_elements(Z_ARRVAL_P(z_array)); } } else if(has_timeout == 1) { if(argc == 2 && Z_TYPE_P(z_args[0]) == IS_ARRAY && Z_TYPE_P(z_args[1]) == IS_LONG) { single_array = 1; z_array = z_args[0]; timeout = Z_LVAL_P(z_args[1]); efree(z_args); z_args = NULL; /* new count */ argc = zend_hash_num_elements(Z_ARRVAL_P(z_array)); } } /* prepare an array for the keys, one for their lengths, one to mark the keys to free. */ array_size = argc; if(has_timeout) array_size++; keys = emalloc(array_size * sizeof(char*)); keys_len = emalloc(array_size * sizeof(int)); keys_to_free = emalloc(array_size * sizeof(int)); memset(keys_to_free, 0, array_size * sizeof(int)); cmd_len = 1 + integer_length(keyword_len) + 2 +keyword_len + 2; /* start computing the command length */ if(single_array) { /* loop over the array */ HashTable *keytable = Z_ARRVAL_P(z_array); for(j = 0, zend_hash_internal_pointer_reset(keytable); zend_hash_has_more_elements(keytable) == SUCCESS; zend_hash_move_forward(keytable)) { char *key; unsigned int key_len; unsigned long idx; zval **z_value_pp; zend_hash_get_current_key_ex(keytable, &key, &key_len, &idx, 0, NULL); if(zend_hash_get_current_data(keytable, (void**)&z_value_pp) == FAILURE) { continue; /* this should never happen, according to the PHP people. */ } if(!all_keys && j != 0) { /* not just operating on keys */ if(can_serialize) { keys_to_free[j] = redis_serialize(redis_sock, *z_value_pp, &keys[j], &keys_len[j] TSRMLS_CC); } else { convert_to_string(*z_value_pp); keys[j] = Z_STRVAL_PP(z_value_pp); keys_len[j] = Z_STRLEN_PP(z_value_pp); keys_to_free[j] = 0; } } else { /* only accept strings */ if(Z_TYPE_PP(z_value_pp) != IS_STRING) { convert_to_string(*z_value_pp); } /* get current value */ keys[j] = Z_STRVAL_PP(z_value_pp); keys_len[j] = Z_STRLEN_PP(z_value_pp); keys_to_free[j] = redis_key_prefix(redis_sock, &keys[j], &keys_len[j] TSRMLS_CC); /* add optional prefix */ } cmd_len += 1 + integer_length(keys_len[j]) + 2 + keys_len[j] + 2; /* $ + size + NL + string + NL */ j++; real_argc++; } if(has_timeout) { keys_len[j] = spprintf(&keys[j], 0, "%d", timeout); cmd_len += 1 + integer_length(keys_len[j]) + 2 + keys_len[j] + 2; /* $ + size + NL + string + NL */ j++; real_argc++; } } else { if(has_timeout && Z_TYPE_P(z_args[argc - 1]) != IS_LONG) { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Syntax error on timeout"); } for(i = 0, j = 0; i < argc; ++i) { /* store each key */ if(!all_keys && j != 0) { /* not just operating on keys */ if(can_serialize) { keys_to_free[j] = redis_serialize(redis_sock, z_args[i], &keys[j], &keys_len[j] TSRMLS_CC); } else { convert_to_string(z_args[i]); keys[j] = Z_STRVAL_P(z_args[i]); keys_len[j] = Z_STRLEN_P(z_args[i]); keys_to_free[j] = 0; } } else { if(Z_TYPE_P(z_args[i]) != IS_STRING) { convert_to_string(z_args[i]); } keys[j] = Z_STRVAL_P(z_args[i]); keys_len[j] = Z_STRLEN_P(z_args[i]); /* If we have a timeout it should be the last argument, which we do not want to prefix */ if(!has_timeout || i < argc-1) { keys_to_free[j] = redis_key_prefix(redis_sock, &keys[j], &keys_len[j] TSRMLS_CC); /* add optional prefix TSRMLS_CC*/ } } cmd_len += 1 + integer_length(keys_len[j]) + 2 + keys_len[j] + 2; /* $ + size + NL + string + NL */ j++; real_argc++; } } cmd_len += 1 + integer_length(real_argc+1) + 2; /* *count NL */ cmd = emalloc(cmd_len+1); sprintf(cmd, "*%d" _NL "$%d" _NL "%s" _NL, 1+real_argc, keyword_len, keyword); pos = 1 +integer_length(real_argc + 1) + 2 + 1 + integer_length(keyword_len) + 2 + keyword_len + 2; /* copy each key to its destination */ for(i = 0; i < real_argc; ++i) { sprintf(cmd + pos, "$%d" _NL, keys_len[i]); /* size */ pos += 1 + integer_length(keys_len[i]) + 2; memcpy(cmd + pos, keys[i], keys_len[i]); pos += keys_len[i]; memcpy(cmd + pos, _NL, 2); pos += 2; } /* cleanup prefixed keys. */ for(i = 0; i < real_argc + (has_timeout?-1:0); ++i) { if(keys_to_free[i]) STR_FREE(keys[i]); } if(single_array && has_timeout) { /* cleanup string created to contain timeout value */ efree(keys[real_argc-1]); } efree(keys); efree(keys_len); efree(keys_to_free); if(z_args) efree(z_args); /* call REDIS_PROCESS_REQUEST and skip void returns */ IF_MULTI_OR_ATOMIC() { if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); return FAILURE; } efree(cmd); } IF_PIPELINE() { PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len); efree(cmd); } return SUCCESS; } /* {{{ proto array Redis::sInter(string key0, ... string keyN) */ PHP_METHOD(Redis, sInter) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SINTER", sizeof("SINTER") - 1, 0, &redis_sock, 0, 1, 1)) return; IF_ATOMIC() { if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } /* }}} */ /* {{{ proto array Redis::sInterStore(string destination, string key0, ... string keyN) */ PHP_METHOD(Redis, sInterStore) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SINTERSTORE", sizeof("SINTERSTORE") - 1, 1, &redis_sock, 0, 1, 1)) return; IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto array Redis::sUnion(string key0, ... string keyN) */ PHP_METHOD(Redis, sUnion) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SUNION", sizeof("SUNION") - 1, 0, &redis_sock, 0, 1, 1)) return; IF_ATOMIC() { if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } /* }}} */ /* {{{ proto array Redis::sUnionStore(string destination, string key0, ... string keyN) */ PHP_METHOD(Redis, sUnionStore) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SUNIONSTORE", sizeof("SUNIONSTORE") - 1, 1, &redis_sock, 0, 1, 1)) return; IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto array Redis::sDiff(string key0, ... string keyN) */ PHP_METHOD(Redis, sDiff) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SDIFF", sizeof("SDIFF") - 1, 0, &redis_sock, 0, 1, 1)) return; IF_ATOMIC() { /* read multibulk reply */ if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } /* }}} */ /* {{{ proto array Redis::sDiffStore(string destination, string key0, ... string keyN) */ PHP_METHOD(Redis, sDiffStore) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SDIFFSTORE", sizeof("SDIFFSTORE") - 1, 1, &redis_sock, 0, 1, 1)) return; IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ PHP_METHOD(Redis, sort) { zval *object = getThis(), *z_array = NULL, **z_cur; char *cmd, *old_cmd = NULL, *key; int cmd_len, elements = 2, key_len, key_free; int using_store = 0; RedisSock *redis_sock; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|a", &object, redis_ce, &key, &key_len, &z_array) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format(&cmd, "$4" _NL "SORT" _NL "$%d" _NL "%s" _NL, key_len, key, key_len); if(key_free) efree(key); if(z_array) { if ((zend_hash_find(Z_ARRVAL_P(z_array), "by", sizeof("by"), (void **) &z_cur) == SUCCESS || zend_hash_find(Z_ARRVAL_P(z_array), "BY", sizeof("BY"), (void **) &z_cur) == SUCCESS) && Z_TYPE_PP(z_cur) == IS_STRING) { old_cmd = cmd; cmd_len = redis_cmd_format(&cmd, "%s" "$2" _NL "BY" _NL "$%d" _NL "%s" _NL , cmd, cmd_len , Z_STRLEN_PP(z_cur), Z_STRVAL_PP(z_cur), Z_STRLEN_PP(z_cur)); elements += 2; efree(old_cmd); } if ((zend_hash_find(Z_ARRVAL_P(z_array), "sort", sizeof("sort"), (void **) &z_cur) == SUCCESS || zend_hash_find(Z_ARRVAL_P(z_array), "SORT", sizeof("SORT"), (void **) &z_cur) == SUCCESS) && Z_TYPE_PP(z_cur) == IS_STRING) { old_cmd = cmd; cmd_len = redis_cmd_format(&cmd, "%s" "$%d" _NL "%s" _NL , cmd, cmd_len , Z_STRLEN_PP(z_cur), Z_STRVAL_PP(z_cur), Z_STRLEN_PP(z_cur)); elements += 1; efree(old_cmd); } if ((zend_hash_find(Z_ARRVAL_P(z_array), "store", sizeof("store"), (void **) &z_cur) == SUCCESS || zend_hash_find(Z_ARRVAL_P(z_array), "STORE", sizeof("STORE"), (void **) &z_cur) == SUCCESS) && Z_TYPE_PP(z_cur) == IS_STRING) { using_store = 1; old_cmd = cmd; cmd_len = redis_cmd_format(&cmd, "%s" "$5" _NL "STORE" _NL "$%d" _NL "%s" _NL , cmd, cmd_len , Z_STRLEN_PP(z_cur), Z_STRVAL_PP(z_cur), Z_STRLEN_PP(z_cur)); elements += 2; efree(old_cmd); } if ((zend_hash_find(Z_ARRVAL_P(z_array), "get", sizeof("get"), (void **) &z_cur) == SUCCESS || zend_hash_find(Z_ARRVAL_P(z_array), "GET", sizeof("GET"), (void **) &z_cur) == SUCCESS) && (Z_TYPE_PP(z_cur) == IS_STRING || Z_TYPE_PP(z_cur) == IS_ARRAY)) { if(Z_TYPE_PP(z_cur) == IS_STRING) { old_cmd = cmd; cmd_len = redis_cmd_format(&cmd, "%s" "$3" _NL "GET" _NL "$%d" _NL "%s" _NL , cmd, cmd_len , Z_STRLEN_PP(z_cur), Z_STRVAL_PP(z_cur), Z_STRLEN_PP(z_cur)); elements += 2; efree(old_cmd); } else if(Z_TYPE_PP(z_cur) == IS_ARRAY) { /* loop over the strings in that array and add them as patterns */ HashTable *keytable = Z_ARRVAL_PP(z_cur); for(zend_hash_internal_pointer_reset(keytable); zend_hash_has_more_elements(keytable) == SUCCESS; zend_hash_move_forward(keytable)) { char *key; unsigned int key_len; unsigned long idx; zval **z_value_pp; zend_hash_get_current_key_ex(keytable, &key, &key_len, &idx, 0, NULL); if(zend_hash_get_current_data(keytable, (void**)&z_value_pp) == FAILURE) { continue; /* this should never happen, according to the PHP people. */ } if(Z_TYPE_PP(z_value_pp) == IS_STRING) { old_cmd = cmd; cmd_len = redis_cmd_format(&cmd, "%s" "$3" _NL "GET" _NL "$%d" _NL "%s" _NL , cmd, cmd_len , Z_STRLEN_PP(z_value_pp), Z_STRVAL_PP(z_value_pp), Z_STRLEN_PP(z_value_pp)); elements += 2; efree(old_cmd); } } } } if ((zend_hash_find(Z_ARRVAL_P(z_array), "alpha", sizeof("alpha"), (void **) &z_cur) == SUCCESS || zend_hash_find(Z_ARRVAL_P(z_array), "ALPHA", sizeof("ALPHA"), (void **) &z_cur) == SUCCESS) && Z_TYPE_PP(z_cur) == IS_BOOL && Z_BVAL_PP(z_cur) == 1) { old_cmd = cmd; cmd_len = redis_cmd_format(&cmd, "%s" "$5" _NL "ALPHA" _NL , cmd, cmd_len); elements += 1; efree(old_cmd); } if ((zend_hash_find(Z_ARRVAL_P(z_array), "limit", sizeof("limit"), (void **) &z_cur) == SUCCESS || zend_hash_find(Z_ARRVAL_P(z_array), "LIMIT", sizeof("LIMIT"), (void **) &z_cur) == SUCCESS) && Z_TYPE_PP(z_cur) == IS_ARRAY) { if(zend_hash_num_elements(Z_ARRVAL_PP(z_cur)) == 2) { zval **z_offset_pp, **z_count_pp; /* get the two values from the table, check that they are indeed of LONG type */ if(SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(z_cur), 0, (void**)&z_offset_pp) && SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(z_cur), 1, (void**)&z_count_pp)) { long limit_low, limit_high; if((Z_TYPE_PP(z_offset_pp) == IS_LONG || Z_TYPE_PP(z_offset_pp) == IS_STRING) && (Z_TYPE_PP(z_count_pp) == IS_LONG || Z_TYPE_PP(z_count_pp) == IS_STRING)) { if(Z_TYPE_PP(z_offset_pp) == IS_LONG) { limit_low = Z_LVAL_PP(z_offset_pp); } else { limit_low = atol(Z_STRVAL_PP(z_offset_pp)); } if(Z_TYPE_PP(z_count_pp) == IS_LONG) { limit_high = Z_LVAL_PP(z_count_pp); } else { limit_high = atol(Z_STRVAL_PP(z_count_pp)); } old_cmd = cmd; cmd_len = redis_cmd_format(&cmd, "%s" "$5" _NL "LIMIT" _NL "$%d" _NL "%d" _NL "$%d" _NL "%d" _NL , cmd, cmd_len , integer_length(limit_low), limit_low , integer_length(limit_high), limit_high); elements += 3; efree(old_cmd); } } } } } /* complete with prefix */ old_cmd = cmd; cmd_len = redis_cmd_format(&cmd, "*%d" _NL "%s", elements, cmd, cmd_len); efree(old_cmd); /* run command */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); if(using_store) { IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } else { IF_ATOMIC() { if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } } PHP_REDIS_API void generic_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sort, int use_alpha) { zval *object; RedisSock *redis_sock; char *key = NULL, *pattern = NULL, *get = NULL, *store = NULL, *cmd; int key_len, pattern_len = -1, get_len = -1, store_len = -1, cmd_len, key_free; long sort_start = -1, sort_count = -1; int cmd_elements; char *cmd_lines[30]; int cmd_sizes[30]; int sort_len; int i, pos; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|sslls", &object, redis_ce, &key, &key_len, &pattern, &pattern_len, &get, &get_len, &sort_start, &sort_count, &store, &store_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } if(key_len == 0) { RETURN_FALSE; } /* first line, sort. */ cmd_lines[1] = estrdup("$4"); cmd_sizes[1] = 2; cmd_lines[2] = estrdup("SORT"); cmd_sizes[2] = 4; /* Prefix our key if we need to */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); /* second line, key */ cmd_sizes[3] = redis_cmd_format(&cmd_lines[3], "$%d", key_len); cmd_lines[4] = emalloc(key_len + 1); memcpy(cmd_lines[4], key, key_len); cmd_lines[4][key_len] = 0; cmd_sizes[4] = key_len; /* If we prefixed our key, free it */ if(key_free) efree(key); cmd_elements = 5; if(pattern && pattern_len) { /* BY */ cmd_lines[cmd_elements] = estrdup("$2"); cmd_sizes[cmd_elements] = 2; cmd_elements++; cmd_lines[cmd_elements] = estrdup("BY"); cmd_sizes[cmd_elements] = 2; cmd_elements++; /* pattern */ cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], "$%d", pattern_len); cmd_elements++; cmd_lines[cmd_elements] = emalloc(pattern_len + 1); memcpy(cmd_lines[cmd_elements], pattern, pattern_len); cmd_lines[cmd_elements][pattern_len] = 0; cmd_sizes[cmd_elements] = pattern_len; cmd_elements++; } if(sort_start >= 0 && sort_count >= 0) { /* LIMIT */ cmd_lines[cmd_elements] = estrdup("$5"); cmd_sizes[cmd_elements] = 2; cmd_elements++; cmd_lines[cmd_elements] = estrdup("LIMIT"); cmd_sizes[cmd_elements] = 5; cmd_elements++; /* start */ cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], "$%d", integer_length(sort_start)); cmd_elements++; cmd_sizes[cmd_elements] = spprintf(&cmd_lines[cmd_elements], 0, "%d", (int)sort_start); cmd_elements++; /* count */ cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], "$%d", integer_length(sort_count)); cmd_elements++; cmd_sizes[cmd_elements] = spprintf(&cmd_lines[cmd_elements], 0, "%d", (int)sort_count); cmd_elements++; } if(get && get_len) { /* GET */ cmd_lines[cmd_elements] = estrdup("$3"); cmd_sizes[cmd_elements] = 2; cmd_elements++; cmd_lines[cmd_elements] = estrdup("GET"); cmd_sizes[cmd_elements] = 3; cmd_elements++; /* pattern */ cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], "$%d", get_len); cmd_elements++; cmd_lines[cmd_elements] = emalloc(get_len + 1); memcpy(cmd_lines[cmd_elements], get, get_len); cmd_lines[cmd_elements][get_len] = 0; cmd_sizes[cmd_elements] = get_len; cmd_elements++; } /* add ASC or DESC */ sort_len = strlen(sort); cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], "$%d", sort_len); cmd_elements++; cmd_lines[cmd_elements] = emalloc(sort_len + 1); memcpy(cmd_lines[cmd_elements], sort, sort_len); cmd_lines[cmd_elements][sort_len] = 0; cmd_sizes[cmd_elements] = sort_len; cmd_elements++; if(use_alpha) { /* ALPHA */ cmd_lines[cmd_elements] = estrdup("$5"); cmd_sizes[cmd_elements] = 2; cmd_elements++; cmd_lines[cmd_elements] = estrdup("ALPHA"); cmd_sizes[cmd_elements] = 5; cmd_elements++; } if(store && store_len) { /* STORE */ cmd_lines[cmd_elements] = estrdup("$5"); cmd_sizes[cmd_elements] = 2; cmd_elements++; cmd_lines[cmd_elements] = estrdup("STORE"); cmd_sizes[cmd_elements] = 5; cmd_elements++; /* store key */ cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], "$%d", store_len); cmd_elements++; cmd_lines[cmd_elements] = emalloc(store_len + 1); memcpy(cmd_lines[cmd_elements], store, store_len); cmd_lines[cmd_elements][store_len] = 0; cmd_sizes[cmd_elements] = store_len; cmd_elements++; } /* first line has the star */ cmd_sizes[0] = spprintf(&cmd_lines[0], 0, "*%d", (cmd_elements-1)/2); /* compute the command size */ cmd_len = 0; for(i = 0; i < cmd_elements; ++i) { cmd_len += cmd_sizes[i] + sizeof(_NL) - 1; /* each line followeb by _NL */ } /* copy all lines into the final command. */ cmd = emalloc(1 + cmd_len); pos = 0; for(i = 0; i < cmd_elements; ++i) { memcpy(cmd + pos, cmd_lines[i], cmd_sizes[i]); pos += cmd_sizes[i]; memcpy(cmd + pos, _NL, sizeof(_NL) - 1); pos += sizeof(_NL) - 1; /* free every line */ efree(cmd_lines[i]); } REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } /* {{{ proto array Redis::sortAsc(string key, string pattern, string get, int start, int end, bool getList]) */ PHP_METHOD(Redis, sortAsc) { generic_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ASC", 0); } /* }}} */ /* {{{ proto array Redis::sortAscAlpha(string key, string pattern, string get, int start, int end, bool getList]) */ PHP_METHOD(Redis, sortAscAlpha) { generic_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ASC", 1); } /* }}} */ /* {{{ proto array Redis::sortDesc(string key, string pattern, string get, int start, int end, bool getList]) */ PHP_METHOD(Redis, sortDesc) { generic_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DESC", 0); } /* }}} */ /* {{{ proto array Redis::sortDescAlpha(string key, string pattern, string get, int start, int end, bool getList]) */ PHP_METHOD(Redis, sortDescAlpha) { generic_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DESC", 1); } /* }}} */ PHP_REDIS_API void generic_expire_cmd(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd, *t; int key_len, cmd_len, key_free, t_len; int i; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", &object, redis_ce, &key, &key_len, &t, &t_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* check that we have a number */ for(i = 0; i < t_len; ++i) if(t[i] < '0' || t[i] > '9') RETURN_FALSE; key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, keyword, "ss", key, key_len, t, t_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_1_response); } /* {{{ proto array Redis::setTimeout(string key, int timeout) */ PHP_METHOD(Redis, setTimeout) { generic_expire_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "EXPIRE", sizeof("EXPIRE")-1); } PHP_METHOD(Redis, pexpire) { generic_expire_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PEXPIRE", sizeof("PEXPIRE")-1); } /* }}} */ /* {{{ proto array Redis::expireAt(string key, int timestamp) */ PHP_METHOD(Redis, expireAt) { generic_expire_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "EXPIREAT", sizeof("EXPIREAT")-1); } /* }}} */ /* {{{ proto array Redis::pexpireAt(string key, int timestamp) */ PHP_METHOD(Redis, pexpireAt) { generic_expire_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PEXPIREAT", sizeof("PEXPIREAT")-1); } /* }}} */ /* {{{ proto array Redis::lSet(string key, int index, string value) */ PHP_METHOD(Redis, lSet) { zval *object; RedisSock *redis_sock; char *cmd; int cmd_len, key_len, val_len; long index; char *key, *val; int val_free, key_free = 0; zval *z_value; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oslz", &object, redis_ce, &key, &key_len, &index, &z_value) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "LSET", "sds", key, key_len, index, val, val_len); if(val_free) STR_FREE(val); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } /* }}} */ PHP_REDIS_API void generic_empty_cmd_impl(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ResultCallback result_callback) { zval *object; RedisSock *redis_sock; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { result_callback(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(result_callback); } PHP_REDIS_API void generic_empty_cmd(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ...) { generic_empty_cmd_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len, redis_boolean_response); } /* {{{ proto string Redis::save() */ PHP_METHOD(Redis, save) { char *cmd; int cmd_len = redis_cmd_format_static(&cmd, "SAVE", ""); generic_empty_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); } /* }}} */ /* {{{ proto string Redis::bgSave() */ PHP_METHOD(Redis, bgSave) { char *cmd; int cmd_len = redis_cmd_format_static(&cmd, "BGSAVE", ""); generic_empty_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); } /* }}} */ PHP_REDIS_API void generic_empty_long_cmd(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ...) { zval *object; RedisSock *redis_sock; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* {{{ proto integer Redis::lastSave() */ PHP_METHOD(Redis, lastSave) { char *cmd; int cmd_len = redis_cmd_format_static(&cmd, "LASTSAVE", ""); generic_empty_long_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); } /* }}} */ /* {{{ proto bool Redis::flushDB() */ PHP_METHOD(Redis, flushDB) { char *cmd; int cmd_len = redis_cmd_format_static(&cmd, "FLUSHDB", ""); generic_empty_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); } /* }}} */ /* {{{ proto bool Redis::flushAll() */ PHP_METHOD(Redis, flushAll) { char *cmd; int cmd_len = redis_cmd_format_static(&cmd, "FLUSHALL", ""); generic_empty_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); } /* }}} */ /* {{{ proto int Redis::dbSize() */ PHP_METHOD(Redis, dbSize) { char *cmd; int cmd_len = redis_cmd_format_static(&cmd, "DBSIZE", ""); generic_empty_long_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); } /* }}} */ /* {{{ proto bool Redis::auth(string passwd) */ PHP_METHOD(Redis, auth) { zval *object; RedisSock *redis_sock; char *cmd, *password; int cmd_len, password_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &password, &password_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } cmd_len = redis_cmd_format_static(&cmd, "AUTH", "s", password, password_len); /* Free previously stored auth if we have one, and store this password */ if(redis_sock->auth) efree(redis_sock->auth); redis_sock->auth = estrndup(password, password_len); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } /* }}} */ /* {{{ proto long Redis::persist(string key) */ PHP_METHOD(Redis, persist) { zval *object; RedisSock *redis_sock; char *cmd, *key; int cmd_len, key_len, key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "PERSIST", "s", key, key_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_1_response); } /* }}} */ PHP_REDIS_API void generic_ttl(INTERNAL_FUNCTION_PARAMETERS, char *keyword) { zval *object; RedisSock *redis_sock; char *cmd, *key; int cmd_len, key_len, key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, keyword, "s", key, key_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* {{{ proto long Redis::ttl(string key) */ PHP_METHOD(Redis, ttl) { generic_ttl(INTERNAL_FUNCTION_PARAM_PASSTHRU, "TTL"); } /* }}} */ /* {{{ proto long Redis::pttl(string key) */ PHP_METHOD(Redis, pttl) { generic_ttl(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PTTL"); } /* }}} */ /* {{{ proto array Redis::info() */ PHP_METHOD(Redis, info) { zval *object; RedisSock *redis_sock; char *cmd, *opt = NULL; int cmd_len, opt_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|s", &object, redis_ce, &opt, &opt_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Build a standalone INFO command or one with an option */ if(opt != NULL) { cmd_len = redis_cmd_format_static(&cmd, "INFO", "s", opt, opt_len); } else { cmd_len = redis_cmd_format_static(&cmd, "INFO", ""); } REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_info_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_info_response); } /* }}} */ /* {{{ proto string Redis::resetStat() */ PHP_METHOD(Redis, resetStat) { char *cmd; int cmd_len = redis_cmd_format_static(&cmd, "CONFIG", "s", "RESETSTAT", 9); generic_empty_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); } /* }}} */ /* {{{ proto bool Redis::select(long dbNumber) */ PHP_METHOD(Redis, select) { zval *object; RedisSock *redis_sock; char *cmd; int cmd_len; long dbNumber; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", &object, redis_ce, &dbNumber) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } redis_sock->dbNumber = dbNumber; cmd_len = redis_cmd_format_static(&cmd, "SELECT", "d", dbNumber); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } /* }}} */ /* {{{ proto bool Redis::move(string key, long dbindex) */ PHP_METHOD(Redis, move) { zval *object; RedisSock *redis_sock; char *cmd, *key; int cmd_len, key_len, key_free; long dbNumber; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osl", &object, redis_ce, &key, &key_len, &dbNumber) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "MOVE", "sd", key, key_len, dbNumber); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_1_response); } /* }}} */ PHP_REDIS_API void generic_mset(INTERNAL_FUNCTION_PARAMETERS, char *kw, void (*fun)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *, zval *, void *)) { zval *object; RedisSock *redis_sock; char *cmd = NULL, *p = NULL; int cmd_len = 0, argc = 0, kw_len = strlen(kw); int step = 0; /* 0: compute size; 1: copy strings. */ zval *z_array; HashTable *keytable; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", &object, redis_ce, &z_array) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } if(zend_hash_num_elements(Z_ARRVAL_P(z_array)) == 0) { RETURN_FALSE; } for(step = 0; step < 2; ++step) { if(step == 1) { cmd_len += 1 + integer_length(1 + 2 * argc) + 2; /* star + arg count + NL */ cmd_len += 1 + integer_length(kw_len) + 2; /* dollar + strlen(kw) + NL */ cmd_len += kw_len + 2; /* kw + NL */ p = cmd = emalloc(cmd_len + 1); /* alloc */ p += sprintf(cmd, "*%d" _NL "$%d" _NL "%s" _NL, 1 + 2 * argc, kw_len, kw); /* copy header */ } keytable = Z_ARRVAL_P(z_array); for(zend_hash_internal_pointer_reset(keytable); zend_hash_has_more_elements(keytable) == SUCCESS; zend_hash_move_forward(keytable)) { char *key, *val; unsigned int key_len; int val_len; unsigned long idx; int type; zval **z_value_pp; int val_free, key_free; char buf[32]; type = zend_hash_get_current_key_ex(keytable, &key, &key_len, &idx, 0, NULL); if(zend_hash_get_current_data(keytable, (void**)&z_value_pp) == FAILURE) { continue; /* this should never happen, according to the PHP people. */ } /* If the key isn't a string, use the index value returned when grabbing the */ /* key. We typecast to long, because they could actually be negative. */ if(type != HASH_KEY_IS_STRING) { /* Create string representation of our index */ key_len = snprintf(buf, sizeof(buf), "%ld", (long)idx); key = (char*)buf; } else if(key_len > 0) { /* When not an integer key, the length will include the \0 */ key_len--; } if(step == 0) argc++; /* found a valid arg */ val_free = redis_serialize(redis_sock, *z_value_pp, &val, &val_len TSRMLS_CC); key_free = redis_key_prefix(redis_sock, &key, (int*)&key_len TSRMLS_CC); if(step == 0) { /* counting */ cmd_len += 1 + integer_length(key_len) + 2 + key_len + 2 + 1 + integer_length(val_len) + 2 + val_len + 2; } else { p += sprintf(p, "$%d" _NL, key_len); /* key len */ memcpy(p, key, key_len); p += key_len; /* key */ memcpy(p, _NL, 2); p += 2; p += sprintf(p, "$%d" _NL, val_len); /* val len */ memcpy(p, val, val_len); p += val_len; /* val */ memcpy(p, _NL, 2); p += 2; } if(val_free) STR_FREE(val); if(key_free) efree(key); } } REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(fun); } /* {{{ proto bool Redis::mset(array (key => value, ...)) */ PHP_METHOD(Redis, mset) { generic_mset(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MSET", redis_boolean_response); } /* }}} */ /* {{{ proto bool Redis::msetnx(array (key => value, ...)) */ PHP_METHOD(Redis, msetnx) { generic_mset(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MSETNX", redis_1_response); } /* }}} */ PHP_REDIS_API void common_rpoplpush(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *srckey, int srckey_len, char *dstkey, int dstkey_len, int timeout) { char *cmd; int cmd_len; int srckey_free = redis_key_prefix(redis_sock, &srckey, &srckey_len TSRMLS_CC); int dstkey_free = redis_key_prefix(redis_sock, &dstkey, &dstkey_len TSRMLS_CC); if(timeout < 0) { cmd_len = redis_cmd_format_static(&cmd, "RPOPLPUSH", "ss", srckey, srckey_len, dstkey, dstkey_len); } else { cmd_len = redis_cmd_format_static(&cmd, "BRPOPLPUSH", "ssd", srckey, srckey_len, dstkey, dstkey_len, timeout); } if(srckey_free) efree(srckey); if(dstkey_free) efree(dstkey); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_string_response); } /* {{{ proto string Redis::rpoplpush(string srckey, string dstkey) */ PHP_METHOD(Redis, rpoplpush) { zval *object; RedisSock *redis_sock; char *srckey = NULL, *dstkey = NULL; int srckey_len, dstkey_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", &object, redis_ce, &srckey, &srckey_len, &dstkey, &dstkey_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } common_rpoplpush(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, srckey, srckey_len, dstkey, dstkey_len, -1); } /* }}} */ /* {{{ proto string Redis::brpoplpush(string srckey, string dstkey) */ PHP_METHOD(Redis, brpoplpush) { zval *object; RedisSock *redis_sock; char *srckey = NULL, *dstkey = NULL; int srckey_len, dstkey_len; long timeout = 0; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ossl", &object, redis_ce, &srckey, &srckey_len, &dstkey, &dstkey_len, &timeout) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } common_rpoplpush(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, srckey, srckey_len, dstkey, dstkey_len, timeout); } /* }}} */ /* {{{ proto long Redis::zAdd(string key, int score, string value) */ PHP_METHOD(Redis, zAdd) { RedisSock *redis_sock; char *cmd; int cmd_len, key_len, val_len; double score; char *key, *val; int val_free, key_free = 0; char *dbl_str; int dbl_len; smart_str buf = {0}; zval **z_args; int argc = ZEND_NUM_ARGS(), i; /* get redis socket */ if (redis_sock_get(getThis(), &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } z_args = emalloc(argc * sizeof(zval*)); if(zend_get_parameters_array(ht, argc, z_args) == FAILURE) { efree(z_args); RETURN_FALSE; } /* need key, score, value, [score, value...] */ if(argc > 1) { convert_to_string(z_args[0]); /* required string */ } if(argc < 3 || Z_TYPE_P(z_args[0]) != IS_STRING || (argc-1) % 2 != 0) { efree(z_args); RETURN_FALSE; } /* possibly serialize key */ key = Z_STRVAL_P(z_args[0]); key_len = Z_STRLEN_P(z_args[0]); key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); /* start building the command */ smart_str_appendc(&buf, '*'); smart_str_append_long(&buf, argc + 1); /* +1 for ZADD command */ smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); /* add command name */ smart_str_appendc(&buf, '$'); smart_str_append_long(&buf, 4); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); smart_str_appendl(&buf, "ZADD", 4); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); /* add key */ smart_str_appendc(&buf, '$'); smart_str_append_long(&buf, key_len); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); smart_str_appendl(&buf, key, key_len); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); for(i = 1; i < argc; i +=2) { convert_to_double(z_args[i]); /* convert score to double */ val_free = redis_serialize(redis_sock, z_args[i+1], &val, &val_len TSRMLS_CC); /* possibly serialize value. */ /* add score */ score = Z_DVAL_P(z_args[i]); REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, score) smart_str_appendc(&buf, '$'); smart_str_append_long(&buf, dbl_len); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); smart_str_appendl(&buf, dbl_str, dbl_len); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); efree(dbl_str); /* add value */ smart_str_appendc(&buf, '$'); smart_str_append_long(&buf, val_len); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); smart_str_appendl(&buf, val, val_len); smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); if(val_free) STR_FREE(val); } /* end string */ smart_str_0(&buf); cmd = buf.c; cmd_len = buf.len; if(key_free) efree(key); /* cleanup */ efree(z_args); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto array Redis::zRange(string key, int start , int end, bool withscores = FALSE) */ PHP_METHOD(Redis, zRange) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; long start, end; long withscores = 0; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osll|b", &object, redis_ce, &key, &key_len, &start, &end, &withscores) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); if(withscores) { cmd_len = redis_cmd_format_static(&cmd, "ZRANGE", "sdds", key, key_len, start, end, "WITHSCORES", 10); } else { cmd_len = redis_cmd_format_static(&cmd, "ZRANGE", "sdd", key, key_len, start, end); } if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); if(withscores) { IF_ATOMIC() { redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_keys_dbl); } else { IF_ATOMIC() { if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } } /* }}} */ /* {{{ proto long Redis::zDelete(string key, string member) */ PHP_METHOD(Redis, zDelete) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREM", sizeof("ZREM") - 1, 2, &redis_sock, 0, 0, 1)) return; IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto long Redis::zDeleteRangeByScore(string key, string start, string end) */ PHP_METHOD(Redis, zDeleteRangeByScore) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; char *start, *end; int start_len, end_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osss", &object, redis_ce, &key, &key_len, &start, &start_len, &end, &end_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "ZREMRANGEBYSCORE", "sss", key, key_len, start, start_len, end, end_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto long Redis::zDeleteRangeByRank(string key, long start, long end) */ PHP_METHOD(Redis, zDeleteRangeByRank) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; long start, end; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osll", &object, redis_ce, &key, &key_len, &start, &end) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "ZREMRANGEBYRANK", "sdd", key, key_len, (int)start, (int)end); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto array Redis::zReverseRange(string key, int start , int end, bool withscores = FALSE) */ PHP_METHOD(Redis, zReverseRange) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; long start, end; long withscores = 0; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osll|b", &object, redis_ce, &key, &key_len, &start, &end, &withscores) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); if(withscores) { cmd_len = redis_cmd_format_static(&cmd, "ZREVRANGE", "sdds", key, key_len, start, end, "WITHSCORES", 10); } else { cmd_len = redis_cmd_format_static(&cmd, "ZREVRANGE", "sdd", key, key_len, start, end); } if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); if(withscores) { IF_ATOMIC() { redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_keys_dbl); } else { IF_ATOMIC() { if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } } /* }}} */ PHP_REDIS_API void redis_generic_zrange_by_score(INTERNAL_FUNCTION_PARAMETERS, char *keyword) { zval *object, *z_options = NULL, **z_limit_val_pp = NULL, **z_withscores_val_pp = NULL; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; zend_bool withscores = 0; char *start, *end; int start_len, end_len; int has_limit = 0; long limit_low, limit_high; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osss|a", &object, redis_ce, &key, &key_len, &start, &start_len, &end, &end_len, &z_options) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* options */ if (z_options && Z_TYPE_P(z_options) == IS_ARRAY) { /* add scores */ zend_hash_find(Z_ARRVAL_P(z_options), "withscores", sizeof("withscores"), (void**)&z_withscores_val_pp); withscores = (z_withscores_val_pp ? Z_BVAL_PP(z_withscores_val_pp) : 0); /* limit offset, count: z_limit_val_pp points to an array($longFrom, $longCount) */ if(zend_hash_find(Z_ARRVAL_P(z_options), "limit", sizeof("limit"), (void**)&z_limit_val_pp)== SUCCESS) {; if(zend_hash_num_elements(Z_ARRVAL_PP(z_limit_val_pp)) == 2) { zval **z_offset_pp, **z_count_pp; /* get the two values from the table, check that they are indeed of LONG type */ if(SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(z_limit_val_pp), 0, (void**)&z_offset_pp) && SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(z_limit_val_pp), 1, (void**)&z_count_pp) && Z_TYPE_PP(z_offset_pp) == IS_LONG && Z_TYPE_PP(z_count_pp) == IS_LONG) { has_limit = 1; limit_low = Z_LVAL_PP(z_offset_pp); limit_high = Z_LVAL_PP(z_count_pp); } } } } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); if(withscores) { if(has_limit) { cmd_len = redis_cmd_format_static(&cmd, keyword, "ssssdds", key, key_len, start, start_len, end, end_len, "LIMIT", 5, limit_low, limit_high, "WITHSCORES", 10); } else { cmd_len = redis_cmd_format_static(&cmd, keyword, "ssss", key, key_len, start, start_len, end, end_len, "WITHSCORES", 10); } } else { if(has_limit) { cmd_len = redis_cmd_format_static(&cmd, keyword, "ssssdd", key, key_len, start, start_len, end, end_len, "LIMIT", 5, limit_low, limit_high); } else { cmd_len = redis_cmd_format_static(&cmd, keyword, "sss", key, key_len, start, start_len, end, end_len); } } if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); if(withscores) { /* with scores! we have to transform the return array. * return_value currently holds this: [elt0, val0, elt1, val1 ... ] * we want [elt0 => val0, elt1 => val1], etc. */ IF_ATOMIC() { if(redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_keys_dbl); } else { IF_ATOMIC() { if(redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } } /* {{{ proto array Redis::zRangeByScore(string key, string start , string end [,array options = NULL]) */ PHP_METHOD(Redis, zRangeByScore) { redis_generic_zrange_by_score(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZRANGEBYSCORE"); } /* }}} */ /* {{{ proto array Redis::zRevRangeByScore(string key, string start , string end [,array options = NULL]) */ PHP_METHOD(Redis, zRevRangeByScore) { redis_generic_zrange_by_score(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREVRANGEBYSCORE"); } /* {{{ proto array Redis::zCount(string key, string start , string end) */ PHP_METHOD(Redis, zCount) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; char *start, *end; int start_len, end_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osss", &object, redis_ce, &key, &key_len, &start, &start_len, &end, &end_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "ZCOUNT", "sss", key, key_len, start, start_len, end, end_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto array Redis::zRangeByLex(string $key, string $min, string $max, * [long $offset, long $count]) */ PHP_METHOD(Redis, zRangeByLex) { zval *object; RedisSock *redis_sock; char *cmd, *key, *min, *max; long offset, count; int argc, cmd_len, key_len; int key_free, min_len, max_len; /* We need either three or five arguments for this to be a valid call */ argc = ZEND_NUM_ARGS(); if (argc != 3 && argc != 5) { RETURN_FALSE; } if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osss|ll", &object, redis_ce, &key, &key_len, &min, &min_len, &max, &max_len, &offset, &count) == FAILURE) { RETURN_FALSE; } /* We can do some simple validation for the user, as we know how min/max are * required to start */ if (!IS_LEX_ARG(min,min_len) || !IS_LEX_ARG(max,max_len)) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); /* Construct our command depending on argc */ if (argc == 3) { cmd_len = redis_cmd_format_static(&cmd, "ZRANGEBYLEX", "sss", key, key_len, min, min_len, max, max_len); } else { cmd_len = redis_cmd_format_static(&cmd, "ZRANGEBYLEX", "ssssll", key, key_len, min, min_len, max, max_len, "LIMIT", sizeof("LIMIT")-1, offset, count); } if(key_free) efree(key); /* Kick it off */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } /* {{{ proto long Redis::zCard(string key) */ PHP_METHOD(Redis, zCard) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "ZCARD", "s", key, key_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto double Redis::zScore(string key, mixed member) */ PHP_METHOD(Redis, zScore) { zval *object; RedisSock *redis_sock; char *key = NULL, *val = NULL, *cmd; int key_len, val_len, cmd_len; int val_free, key_free = 0; zval *z_value; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz", &object, redis_ce, &key, &key_len, &z_value) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "ZSCORE", "ss", key, key_len, val, val_len); if(val_free) STR_FREE(val); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_bulk_double_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_bulk_double_response); } /* }}} */ PHP_REDIS_API void generic_rank_method(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len) { zval *object; RedisSock *redis_sock; char *key = NULL, *val = NULL, *cmd; int key_len, val_len, cmd_len; int val_free, key_free = 0; zval *z_value; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz", &object, redis_ce, &key, &key_len, &z_value) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, keyword, "ss", key, key_len, val, val_len); if(val_free) STR_FREE(val); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* {{{ proto long Redis::zRank(string key, string member) */ PHP_METHOD(Redis, zRank) { generic_rank_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZRANK", 5); } /* }}} */ /* {{{ proto long Redis::zRevRank(string key, string member) */ PHP_METHOD(Redis, zRevRank) { generic_rank_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREVRANK", 8); } /* }}} */ PHP_REDIS_API void generic_incrby_method(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd, *val; int key_len, val_len, cmd_len; double add; int val_free, key_free; zval *z_value; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osdz", &object, redis_ce, &key, &key_len, &add, &z_value) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, keyword, "sfs", key, key_len, add, val, val_len); if(val_free) STR_FREE(val); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_bulk_double_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_bulk_double_response); } /* {{{ proto double Redis::zIncrBy(string key, double value, mixed member) */ PHP_METHOD(Redis, zIncrBy) { generic_incrby_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZINCRBY", sizeof("ZINCRBY")-1); } /* }}} */ PHP_REDIS_API void generic_z_command(INTERNAL_FUNCTION_PARAMETERS, char *command, int command_len) { zval *object, *z_keys, *z_weights = NULL, **z_data; HashTable *ht_keys, *ht_weights = NULL; RedisSock *redis_sock; smart_str cmd = {0}; HashPosition ptr; char *store_key, *agg_op = NULL; int cmd_arg_count = 2, store_key_len, agg_op_len = 0, keys_count; int key_free; /* Grab our parameters */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa|a!s", &object, redis_ce, &store_key, &store_key_len, &z_keys, &z_weights, &agg_op, &agg_op_len) == FAILURE) { RETURN_FALSE; } /* We'll need our socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Grab our keys argument as an array */ ht_keys = Z_ARRVAL_P(z_keys); /* Nothing to do if there aren't any keys */ if((keys_count = zend_hash_num_elements(ht_keys)) == 0) { RETURN_FALSE; } else { /* Increment our overall argument count */ cmd_arg_count += keys_count; } /* Grab and validate our weights array */ if(z_weights != NULL) { ht_weights = Z_ARRVAL_P(z_weights); /* This command is invalid if the weights array isn't the same size */ /* as our keys array. */ if(zend_hash_num_elements(ht_weights) != keys_count) { RETURN_FALSE; } /* Increment our overall argument count by the number of keys */ /* plus one, for the "WEIGHTS" argument itself */ cmd_arg_count += keys_count + 1; } /* AGGREGATE option */ if(agg_op_len != 0) { /* Verify our aggregation option */ if(strncasecmp(agg_op, "SUM", sizeof("SUM")) && strncasecmp(agg_op, "MIN", sizeof("MIN")) && strncasecmp(agg_op, "MAX", sizeof("MAX"))) { RETURN_FALSE; } /* Two more arguments: "AGGREGATE" and agg_op */ cmd_arg_count += 2; } /* Command header */ redis_cmd_init_sstr(&cmd, cmd_arg_count, command, command_len); /* Prefix our key if necessary and add the output key */ key_free = redis_key_prefix(redis_sock, &store_key, &store_key_len TSRMLS_CC); redis_cmd_append_sstr(&cmd, store_key, store_key_len); if(key_free) efree(store_key); /* Number of input keys argument */ redis_cmd_append_sstr_int(&cmd, keys_count); /* Process input keys */ for(zend_hash_internal_pointer_reset_ex(ht_keys, &ptr); zend_hash_get_current_data_ex(ht_keys, (void**)&z_data, &ptr)==SUCCESS; zend_hash_move_forward_ex(ht_keys, &ptr)) { char *key; int key_free, key_len; zval *z_tmp = NULL; if(Z_TYPE_PP(z_data) == IS_STRING) { key = Z_STRVAL_PP(z_data); key_len = Z_STRLEN_PP(z_data); } else { MAKE_STD_ZVAL(z_tmp); *z_tmp = **z_data; convert_to_string(z_tmp); key = Z_STRVAL_P(z_tmp); key_len = Z_STRLEN_P(z_tmp); } /* Apply key prefix if necessary */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); /* Append this input set */ redis_cmd_append_sstr(&cmd, key, key_len); /* Free our key if it was prefixed */ if(key_free) efree(key); /* Free our temporary z_val if it was converted */ if(z_tmp) { zval_dtor(z_tmp); efree(z_tmp); z_tmp = NULL; } } /* Weights */ if(ht_weights != NULL) { /* Append "WEIGHTS" argument */ redis_cmd_append_sstr(&cmd, "WEIGHTS", sizeof("WEIGHTS") - 1); /* Process weights */ for(zend_hash_internal_pointer_reset_ex(ht_weights, &ptr); zend_hash_get_current_data_ex(ht_weights, (void**)&z_data, &ptr)==SUCCESS; zend_hash_move_forward_ex(ht_weights, &ptr)) { /* Ignore non numeric arguments, unless they're special Redis numbers */ if (Z_TYPE_PP(z_data) != IS_LONG && Z_TYPE_PP(z_data) != IS_DOUBLE && strncasecmp(Z_STRVAL_PP(z_data), "inf", sizeof("inf")) != 0 && strncasecmp(Z_STRVAL_PP(z_data), "-inf", sizeof("-inf")) != 0 && strncasecmp(Z_STRVAL_PP(z_data), "+inf", sizeof("+inf")) != 0) { /* We should abort if we have an invalid weight, rather than pass */ /* a different number of weights than the user is expecting */ efree(cmd.c); RETURN_FALSE; } /* Append the weight based on the input type */ switch(Z_TYPE_PP(z_data)) { case IS_LONG: redis_cmd_append_sstr_long(&cmd, Z_LVAL_PP(z_data)); break; case IS_DOUBLE: redis_cmd_append_sstr_dbl(&cmd, Z_DVAL_PP(z_data)); break; case IS_STRING: redis_cmd_append_sstr(&cmd, Z_STRVAL_PP(z_data), Z_STRLEN_PP(z_data)); break; } } } /* Aggregation options, if we have them */ if(agg_op_len != 0) { redis_cmd_append_sstr(&cmd, "AGGREGATE", sizeof("AGGREGATE") - 1); redis_cmd_append_sstr(&cmd, agg_op, agg_op_len); } /* Kick off our request */ REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* zInter */ PHP_METHOD(Redis, zInter) { generic_z_command(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZINTERSTORE", 11); } /* zUnion */ PHP_METHOD(Redis, zUnion) { generic_z_command(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZUNIONSTORE", 11); } /* hashes */ PHP_REDIS_API void generic_hset(INTERNAL_FUNCTION_PARAMETERS, char *kw, void (*fun)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *, zval *, void *)) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd, *member, *val; int key_len, member_len, cmd_len, val_len; int val_free, key_free = 0; zval *z_value; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ossz", &object, redis_ce, &key, &key_len, &member, &member_len, &z_value) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, kw, "sss", key, key_len, member, member_len, val, val_len); if(val_free) STR_FREE(val); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(fun); } /* hSet */ PHP_METHOD(Redis, hSet) { generic_hset(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HSET", redis_long_response); } /* }}} */ /* hSetNx */ PHP_METHOD(Redis, hSetNx) { generic_hset(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HSETNX", redis_1_response); } /* }}} */ /* hGet */ PHP_METHOD(Redis, hGet) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd, *member; int key_len, member_len, cmd_len, key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", &object, redis_ce, &key, &key_len, &member, &member_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "HGET", "ss", key, key_len, member, member_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_string_response); } /* }}} */ /* hLen */ PHP_METHOD(Redis, hLen) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "HLEN", "s", key, key_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ PHP_REDIS_API RedisSock* generic_hash_command_2(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len, char **out_cmd, int *out_len) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd, *member; int key_len, cmd_len, member_len, key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", &object, redis_ce, &key, &key_len, &member, &member_len) == FAILURE) { ZVAL_BOOL(return_value, 0); return NULL; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { ZVAL_BOOL(return_value, 0); return NULL; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, keyword, "ss", key, key_len, member, member_len); if(key_free) efree(key); *out_cmd = cmd; *out_len = cmd_len; return redis_sock; } /* hDel */ PHP_METHOD(Redis, hDel) { RedisSock *redis_sock; if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HDEL", sizeof("HDEL") - 1, 2, &redis_sock, 0, 0, 0)) return; IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* hExists */ PHP_METHOD(Redis, hExists) { char *cmd; int cmd_len; RedisSock *redis_sock = generic_hash_command_2(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HEXISTS", 7, &cmd, &cmd_len); if(!redis_sock) RETURN_FALSE; REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_1_response); } PHP_REDIS_API RedisSock* generic_hash_command_1(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd; int key_len, cmd_len, key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { ZVAL_BOOL(return_value, 0); return NULL; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { ZVAL_BOOL(return_value, 0); return NULL; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, keyword, "s", key, key_len); if(key_free) efree(key); /* call REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) without breaking the return value */ IF_MULTI_OR_ATOMIC() { if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); return NULL; } efree(cmd); } IF_PIPELINE() { PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len); efree(cmd); } return redis_sock; } /* hKeys */ PHP_METHOD(Redis, hKeys) { RedisSock *redis_sock = generic_hash_command_1(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HKEYS", sizeof("HKEYS")-1); if(!redis_sock) RETURN_FALSE; IF_ATOMIC() { if (redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_mbulk_reply_raw); } /* hVals */ PHP_METHOD(Redis, hVals) { RedisSock *redis_sock = generic_hash_command_1(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HVALS", sizeof("HVALS")-1); if(!redis_sock) RETURN_FALSE; IF_ATOMIC() { if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } PHP_METHOD(Redis, hGetAll) { RedisSock *redis_sock = generic_hash_command_1(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HGETALL", sizeof("HGETALL")-1); if(!redis_sock) RETURN_FALSE; IF_ATOMIC() { if (redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_vals); } PHP_METHOD(Redis, hIncrByFloat) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd, *member; int key_len, member_len, cmd_len, key_free; double val; /* Validate we have the right number of arguments */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ossd", &object, redis_ce, &key, &key_len, &member, &member_len, &val) == FAILURE) { RETURN_FALSE; } /* Grab our socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "HINCRBYFLOAT", "ssf", key, key_len, member, member_len, val); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_bulk_double_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_bulk_double_response); } PHP_METHOD(Redis, hIncrBy) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd, *member, *val; int key_len, member_len, cmd_len, val_len, key_free; int i; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osss", &object, redis_ce, &key, &key_len, &member, &member_len, &val, &val_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* check for validity of numeric string */ i = 0; if(val_len && val[0] == '-') { /* negative case */ i++; } for(; i < val_len; ++i) { if(val[i] < '0' || val[i] > '9') { RETURN_FALSE; } } /* HINCRBY key member amount */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "HINCRBY", "sss", key, key_len, member, member_len, val, val_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* {{{ array Redis::hMget(string hash, array keys) */ PHP_METHOD(Redis, hMget) { zval *object; RedisSock *redis_sock; char *key = NULL; zval *z_array, **z_keys, **data; int field_count, i, valid, key_len, key_free; HashTable *ht_array; HashPosition ptr; smart_str cmd = {0}; /* Make sure we can grab our arguments properly */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa", &object, redis_ce, &key, &key_len, &z_array) == FAILURE) { RETURN_FALSE; } /* We'll need our socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Grab member count and abort if we don't have any */ if((field_count = zend_hash_num_elements(Z_ARRVAL_P(z_array))) == 0) { RETURN_FALSE; } /* Prefix our key if we need to */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); /* Allocate enough memory for the number of keys being requested */ z_keys = ecalloc(field_count, sizeof(zval *)); /* Grab our HashTable */ ht_array = Z_ARRVAL_P(z_array); /* Iterate through our keys, grabbing members that are valid */ for(valid=0, zend_hash_internal_pointer_reset_ex(ht_array, &ptr); zend_hash_get_current_data_ex(ht_array, (void**)&data, &ptr)==SUCCESS; zend_hash_move_forward_ex(ht_array, &ptr)) { /* Make sure the data is a long or string, and if it's a string that */ /* it isn't empty. There is no reason to send empty length members. */ if((Z_TYPE_PP(data) == IS_STRING && Z_STRLEN_PP(data)>0) || Z_TYPE_PP(data) == IS_LONG) { /* This is a key we can ask for, copy it and set it in our array */ MAKE_STD_ZVAL(z_keys[valid]); *z_keys[valid] = **data; zval_copy_ctor(z_keys[valid]); convert_to_string(z_keys[valid]); /* Increment the number of valid keys we've encountered */ valid++; } } /* If we don't have any valid keys, we can abort here */ if(valid == 0) { if(key_free) efree(key); efree(z_keys); RETURN_FALSE; } /* Build command header. One extra argument for the hash key itself */ redis_cmd_init_sstr(&cmd, valid+1, "HMGET", sizeof("HMGET")-1); /* Add the hash key */ redis_cmd_append_sstr(&cmd, key, key_len); /* Free key memory if it was prefixed */ if(key_free) efree(key); /* Iterate our keys, appending them as arguments */ for(i=0;i<valid;i++) { redis_cmd_append_sstr(&cmd, Z_STRVAL_P(z_keys[i]), Z_STRLEN_P(z_keys[i])); } /* Kick off our request */ REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); IF_ATOMIC() { redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, z_keys); } REDIS_PROCESS_RESPONSE_CLOSURE(redis_mbulk_reply_assoc, z_keys); } PHP_METHOD(Redis, hMset) { zval *object; RedisSock *redis_sock; char *key = NULL, *cmd, *old_cmd = NULL; int key_len, cmd_len, key_free, i, element_count = 2; zval *z_hash; HashTable *ht_hash; smart_str set_cmds = {0}; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa", &object, redis_ce, &key, &key_len, &z_hash) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } ht_hash = Z_ARRVAL_P(z_hash); if (zend_hash_num_elements(ht_hash) == 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format(&cmd, "$5" _NL "HMSET" _NL "$%d" _NL "%s" _NL , key_len, key, key_len); if(key_free) efree(key); /* looping on each item of the array */ for(i =0, zend_hash_internal_pointer_reset(ht_hash); zend_hash_has_more_elements(ht_hash) == SUCCESS; i++, zend_hash_move_forward(ht_hash)) { char *hkey, hkey_str[40]; unsigned int hkey_len; unsigned long idx; int type; zval **z_value_p; char *hval; int hval_len, hval_free; type = zend_hash_get_current_key_ex(ht_hash, &hkey, &hkey_len, &idx, 0, NULL); if(zend_hash_get_current_data(ht_hash, (void**)&z_value_p) == FAILURE) { continue; /* this should never happen */ } if(type != HASH_KEY_IS_STRING) { /* convert to string */ hkey_len = 1 + sprintf(hkey_str, "%ld", idx); hkey = (char*)hkey_str; } element_count += 2; /* key is set. */ hval_free = redis_serialize(redis_sock, *z_value_p, &hval, &hval_len TSRMLS_CC); /* Append our member and value in place */ redis_cmd_append_sstr(&set_cmds, hkey, hkey_len - 1); redis_cmd_append_sstr(&set_cmds, hval, hval_len); if(hval_free) STR_FREE(hval); } /* Now construct the entire command */ old_cmd = cmd; cmd_len = redis_cmd_format(&cmd, "*%d" _NL "%s%s", element_count, cmd, cmd_len, set_cmds.c, set_cmds.len); efree(old_cmd); /* Free the HMSET bits */ efree(set_cmds.c); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } PHP_REDIS_API int redis_response_enqueued(RedisSock *redis_sock TSRMLS_DC) { char *response; int response_len, ret = 0; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { return 0; } if(strncmp(response, "+QUEUED", 7) == 0) { ret = 1; } efree(response); return ret; } /* flag : get, set {ATOMIC, MULTI, PIPELINE} */ PHP_METHOD(Redis, multi) { RedisSock *redis_sock; char *cmd; int response_len, cmd_len; char * response; zval *object; long multi_value = MULTI; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|l", &object, redis_ce, &multi_value) == FAILURE) { RETURN_FALSE; } /* if the flag is activated, send the command, the reply will be "QUEUED" or -ERR */ if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } if(multi_value == MULTI || multi_value == PIPELINE) { redis_sock->mode = multi_value; } else { RETURN_FALSE; } redis_sock->current = NULL; IF_MULTI() { cmd_len = redis_cmd_format_static(&cmd, "MULTI", ""); if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); RETURN_FALSE; } efree(cmd); if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { RETURN_FALSE; } if(strncmp(response, "+OK", 3) == 0) { efree(response); RETURN_ZVAL(getThis(), 1, 0); } efree(response); RETURN_FALSE; } IF_PIPELINE() { free_reply_callbacks(getThis(), redis_sock); RETURN_ZVAL(getThis(), 1, 0); } } /* discard */ PHP_METHOD(Redis, discard) { RedisSock *redis_sock; zval *object; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } redis_sock->mode = ATOMIC; redis_send_discard(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); } PHP_REDIS_API int redis_sock_read_multibulk_pipeline_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) { zval *z_tab; MAKE_STD_ZVAL(z_tab); array_init(z_tab); redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, 0); *return_value = *z_tab; efree(z_tab); /* free allocated function/request memory */ free_reply_callbacks(getThis(), redis_sock); return 0; } /* redis_sock_read_multibulk_multi_reply */ PHP_REDIS_API int redis_sock_read_multibulk_multi_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) { char inbuf[1024]; int numElems; zval *z_tab; redis_check_eof(redis_sock TSRMLS_CC); php_stream_gets(redis_sock->stream, inbuf, 1024); if(inbuf[0] != '*') { return -1; } /* number of responses */ numElems = atoi(inbuf+1); if(numElems < 0) { return -1; } zval_dtor(return_value); MAKE_STD_ZVAL(z_tab); array_init(z_tab); redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, numElems); *return_value = *z_tab; efree(z_tab); return 0; } void free_reply_callbacks(zval *z_this, RedisSock *redis_sock) { fold_item *fi; fold_item *head = redis_sock->head; request_item *ri; for(fi = head; fi; ) { fold_item *fi_next = fi->next; free(fi); fi = fi_next; } redis_sock->head = NULL; redis_sock->current = NULL; for(ri = redis_sock->pipeline_head; ri; ) { struct request_item *ri_next = ri->next; free(ri->request_str); free(ri); ri = ri_next; } redis_sock->pipeline_head = NULL; redis_sock->pipeline_current = NULL; } /* exec */ PHP_METHOD(Redis, exec) { RedisSock *redis_sock; char *cmd; int cmd_len; zval *object; struct request_item *ri; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } IF_MULTI() { cmd_len = redis_cmd_format_static(&cmd, "EXEC", ""); if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); RETURN_FALSE; } efree(cmd); if (redis_sock_read_multibulk_multi_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock) < 0) { zval_dtor(return_value); free_reply_callbacks(object, redis_sock); redis_sock->mode = ATOMIC; redis_sock->watching = 0; RETURN_FALSE; } free_reply_callbacks(object, redis_sock); redis_sock->mode = ATOMIC; redis_sock->watching = 0; } IF_PIPELINE() { char *request = NULL; int total = 0; int offset = 0; /* compute the total request size */ for(ri = redis_sock->pipeline_head; ri; ri = ri->next) { total += ri->request_size; } if(total) { request = malloc(total); } /* concatenate individual elements one by one in the target buffer */ for(ri = redis_sock->pipeline_head; ri; ri = ri->next) { memcpy(request + offset, ri->request_str, ri->request_size); offset += ri->request_size; } if(request != NULL) { if (redis_sock_write(redis_sock, request, total TSRMLS_CC) < 0) { free(request); free_reply_callbacks(object, redis_sock); redis_sock->mode = ATOMIC; RETURN_FALSE; } free(request); } else { redis_sock->mode = ATOMIC; free_reply_callbacks(object, redis_sock); array_init(return_value); /* empty array when no command was run. */ return; } if (redis_sock_read_multibulk_pipeline_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock) < 0) { redis_sock->mode = ATOMIC; free_reply_callbacks(object, redis_sock); RETURN_FALSE; } redis_sock->mode = ATOMIC; free_reply_callbacks(object, redis_sock); } } PHP_REDIS_API void fold_this_item(INTERNAL_FUNCTION_PARAMETERS, fold_item *item, RedisSock *redis_sock, zval *z_tab) { item->fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, item->ctx TSRMLS_CC); } PHP_REDIS_API int redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int numElems) { fold_item *head = redis_sock->head; fold_item *current = redis_sock->current; for(current = head; current; current = current->next) { fold_this_item(INTERNAL_FUNCTION_PARAM_PASSTHRU, current, redis_sock, z_tab); } redis_sock->current = current; return 0; } PHP_METHOD(Redis, pipeline) { RedisSock *redis_sock; zval *object; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } /* if the flag is activated, send the command, the reply will be "QUEUED" or -ERR */ if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } redis_sock->mode = PIPELINE; /* NB : we keep the function fold, to detect the last function. We need the response format of the n - 1 command. So, we can delete when n > 2, the { 1 .. n - 2} commands */ free_reply_callbacks(getThis(), redis_sock); RETURN_ZVAL(getThis(), 1, 0); } /* publish channel message @return the number of subscribers */ PHP_METHOD(Redis, publish) { zval *object; RedisSock *redis_sock; char *cmd, *key, *val; int cmd_len, key_len, val_len, key_free; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", &object, redis_ce, &key, &key_len, &val, &val_len) == FAILURE) { RETURN_NULL(); } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "PUBLISH", "ss", key, key_len, val, val_len); if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } PHP_REDIS_API void generic_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sub_cmd) { zval *object, *array, **data; HashTable *arr_hash; HashPosition pointer; RedisSock *redis_sock; char *cmd = "", *old_cmd = NULL, *key; int cmd_len, array_count, key_len, key_free; zval *z_tab, **tmp; char *type_response; /* Function call information */ zend_fcall_info z_callback; zend_fcall_info_cache z_callback_cache; zval *z_ret, **z_args[4]; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oaf", &object, redis_ce, &array, &z_callback, &z_callback_cache) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } arr_hash = Z_ARRVAL_P(array); array_count = zend_hash_num_elements(arr_hash); if (array_count == 0) { RETURN_FALSE; } for (zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS; zend_hash_move_forward_ex(arr_hash, &pointer)) { if (Z_TYPE_PP(data) == IS_STRING) { char *old_cmd = NULL; if(*cmd) { old_cmd = cmd; } /* Grab our key and len */ key = Z_STRVAL_PP(data); key_len = Z_STRLEN_PP(data); /* Prefix our key if neccisary */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = spprintf(&cmd, 0, "%s %s", cmd, key); if(old_cmd) { efree(old_cmd); } /* Free our key if it was prefixed */ if(key_free) { efree(key); } } } old_cmd = cmd; cmd_len = spprintf(&cmd, 0, "%s %s\r\n", sub_cmd, cmd); efree(old_cmd); if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); RETURN_FALSE; } efree(cmd); /* read the status of the execution of the command `subscribe` */ z_tab = redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); if(z_tab == NULL) { RETURN_FALSE; } if (zend_hash_index_find(Z_ARRVAL_P(z_tab), 0, (void**)&tmp) == SUCCESS) { type_response = Z_STRVAL_PP(tmp); if(strcmp(type_response, sub_cmd) != 0) { efree(tmp); zval_dtor(z_tab); efree(z_tab); RETURN_FALSE; } } else { zval_dtor(z_tab); efree(z_tab); RETURN_FALSE; } zval_dtor(z_tab); efree(z_tab); /* Set a pointer to our return value and to our arguments. */ z_callback.retval_ptr_ptr = &z_ret; z_callback.params = z_args; z_callback.no_separation = 0; /* Multibulk Response, format : {message type, originating channel, message payload} */ while(1) { /* call the callback with this z_tab in argument */ int is_pmsg, tab_idx = 1; zval **type, **channel, **pattern, **data; z_tab = redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); if(z_tab == NULL || Z_TYPE_P(z_tab) != IS_ARRAY) { /*ERROR */ break; } if (zend_hash_index_find(Z_ARRVAL_P(z_tab), 0, (void**)&type) == FAILURE || Z_TYPE_PP(type) != IS_STRING) { break; } /* Make sure we have a message or pmessage */ if(!strncmp(Z_STRVAL_PP(type), "message", 7) || !strncmp(Z_STRVAL_PP(type), "pmessage", 8)) { /* Is this a pmessage */ is_pmsg = *Z_STRVAL_PP(type) == 'p'; } else { continue; /* It's not a message or pmessage */ } /* If this is a pmessage, we'll want to extract the pattern first */ if(is_pmsg) { /* Extract pattern */ if(zend_hash_index_find(Z_ARRVAL_P(z_tab), tab_idx++, (void**)&pattern) == FAILURE) { break; } } /* Extract channel and data */ if (zend_hash_index_find(Z_ARRVAL_P(z_tab), tab_idx++, (void**)&channel) == FAILURE) { break; } if (zend_hash_index_find(Z_ARRVAL_P(z_tab), tab_idx++, (void**)&data) == FAILURE) { break; } /* Always pass the Redis object through */ z_args[0] = &getThis(); /* Set up our callback args depending on the message type */ if(is_pmsg) { z_args[1] = pattern; z_args[2] = channel; z_args[3] = data; } else { z_args[1] = channel; z_args[2] = data; } /* Set our argument information */ z_callback.param_count = tab_idx; /* Break if we can't call the function */ if(zend_call_function(&z_callback, &z_callback_cache TSRMLS_CC) != SUCCESS) { break; } /* Free reply from Redis */ zval_dtor(z_tab); efree(z_tab); /* Check for a non-null return value. If we have one, return it from * the subscribe function itself. Otherwise continue our loop. */ if (z_ret) { if (Z_TYPE_P(z_ret) != IS_NULL) { RETVAL_ZVAL(z_ret, 0, 1); break; } zval_ptr_dtor(&z_ret); } } } /* {{{ proto void Redis::psubscribe(Array(pattern1, pattern2, ... patternN)) */ PHP_METHOD(Redis, psubscribe) { generic_subscribe_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "psubscribe"); } /* {{{ proto void Redis::subscribe(Array(channel1, channel2, ... channelN)) */ PHP_METHOD(Redis, subscribe) { generic_subscribe_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "subscribe"); } /** * [p]unsubscribe channel_0 channel_1 ... channel_n * [p]unsubscribe(array(channel_0, channel_1, ..., channel_n)) * response format : * array( * channel_0 => TRUE|FALSE, * channel_1 => TRUE|FALSE, * ... * channel_n => TRUE|FALSE * ); **/ PHP_REDIS_API void generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *unsub_cmd) { zval *object, *array, **data; HashTable *arr_hash; HashPosition pointer; RedisSock *redis_sock; char *cmd = "", *old_cmd = NULL; int cmd_len, array_count; int i; zval *z_tab, **z_channel; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", &object, redis_ce, &array) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } arr_hash = Z_ARRVAL_P(array); array_count = zend_hash_num_elements(arr_hash); if (array_count == 0) { RETURN_FALSE; } for (zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS; zend_hash_move_forward_ex(arr_hash, &pointer)) { if (Z_TYPE_PP(data) == IS_STRING) { char *old_cmd = NULL; if(*cmd) { old_cmd = cmd; } cmd_len = spprintf(&cmd, 0, "%s %s", cmd, Z_STRVAL_PP(data)); if(old_cmd) { efree(old_cmd); } } } old_cmd = cmd; cmd_len = spprintf(&cmd, 0, "%s %s\r\n", unsub_cmd, cmd); efree(old_cmd); if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); RETURN_FALSE; } efree(cmd); i = 1; array_init(return_value); while( i <= array_count) { z_tab = redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); if(Z_TYPE_P(z_tab) == IS_ARRAY) { if (zend_hash_index_find(Z_ARRVAL_P(z_tab), 1, (void**)&z_channel) == FAILURE) { RETURN_FALSE; } add_assoc_bool(return_value, Z_STRVAL_PP(z_channel), 1); } else { /*error */ efree(z_tab); RETURN_FALSE; } efree(z_tab); i ++; } } PHP_METHOD(Redis, unsubscribe) { generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "UNSUBSCRIBE"); } PHP_METHOD(Redis, punsubscribe) { generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PUNSUBSCRIBE"); } /* {{{ proto string Redis::bgrewriteaof() */ PHP_METHOD(Redis, bgrewriteaof) { char *cmd; int cmd_len = redis_cmd_format_static(&cmd, "BGREWRITEAOF", ""); generic_empty_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); } /* }}} */ /* {{{ proto string Redis::slaveof([host, port]) */ PHP_METHOD(Redis, slaveof) { zval *object; RedisSock *redis_sock; char *cmd = "", *host = NULL; int cmd_len, host_len; long port = 6379; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|sl", &object, redis_ce, &host, &host_len, &port) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } if(host && host_len) { cmd_len = redis_cmd_format_static(&cmd, "SLAVEOF", "sd", host, host_len, (int)port); } else { cmd_len = redis_cmd_format_static(&cmd, "SLAVEOF", "ss", "NO", 2, "ONE", 3); } REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } /* }}} */ /* {{{ proto string Redis::object(key) */ PHP_METHOD(Redis, object) { zval *object; RedisSock *redis_sock; char *cmd = "", *info = NULL, *key = NULL; int cmd_len, info_len, key_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", &object, redis_ce, &info, &info_len, &key, &key_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } cmd_len = redis_cmd_format_static(&cmd, "OBJECT", "ss", info, info_len, key, key_len); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); if(info_len == 8 && (strncasecmp(info, "refcount", 8) == 0 || strncasecmp(info, "idletime", 8) == 0)) { IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } else if(info_len == 8 && strncasecmp(info, "encoding", 8) == 0) { IF_ATOMIC() { redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_string_response); } else { /* fail */ IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } } /* }}} */ /* {{{ proto string Redis::getOption($option) */ PHP_METHOD(Redis, getOption) { RedisSock *redis_sock; zval *object; long option; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", &object, redis_ce, &option) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } switch(option) { case REDIS_OPT_SERIALIZER: RETURN_LONG(redis_sock->serializer); case REDIS_OPT_PREFIX: if(redis_sock->prefix) { RETURN_STRINGL(redis_sock->prefix, redis_sock->prefix_len, 1); } RETURN_NULL(); case REDIS_OPT_READ_TIMEOUT: RETURN_DOUBLE(redis_sock->read_timeout); case REDIS_OPT_SCAN: RETURN_LONG(redis_sock->scan); default: RETURN_FALSE; } } /* }}} */ /* {{{ proto string Redis::setOption(string $option, mixed $value) */ PHP_METHOD(Redis, setOption) { RedisSock *redis_sock; zval *object; long option, val_long; char *val_str; int val_len; struct timeval read_tv; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ols", &object, redis_ce, &option, &val_str, &val_len) == FAILURE) { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } switch(option) { case REDIS_OPT_SERIALIZER: val_long = atol(val_str); if(val_long == REDIS_SERIALIZER_NONE #ifdef HAVE_REDIS_IGBINARY || val_long == REDIS_SERIALIZER_IGBINARY #endif || val_long == REDIS_SERIALIZER_PHP) { redis_sock->serializer = val_long; RETURN_TRUE; } else { RETURN_FALSE; } break; case REDIS_OPT_PREFIX: if(redis_sock->prefix) { efree(redis_sock->prefix); } if(val_len == 0) { redis_sock->prefix = NULL; redis_sock->prefix_len = 0; } else { redis_sock->prefix_len = val_len; redis_sock->prefix = ecalloc(1+val_len, 1); memcpy(redis_sock->prefix, val_str, val_len); } RETURN_TRUE; case REDIS_OPT_READ_TIMEOUT: redis_sock->read_timeout = atof(val_str); if(redis_sock->stream) { read_tv.tv_sec = (time_t)redis_sock->read_timeout; read_tv.tv_usec = (int)((redis_sock->read_timeout - read_tv.tv_sec) * 1000000); php_stream_set_option(redis_sock->stream, PHP_STREAM_OPTION_READ_TIMEOUT,0, &read_tv); } RETURN_TRUE; case REDIS_OPT_SCAN: val_long = atol(val_str); if(val_long == REDIS_SCAN_NORETRY || val_long == REDIS_SCAN_RETRY) { redis_sock->scan = val_long; RETURN_TRUE; } RETURN_FALSE; break; default: RETURN_FALSE; } } /* }}} */ /* {{{ proto boolean Redis::config(string op, string key [, mixed value]) */ PHP_METHOD(Redis, config) { zval *object; RedisSock *redis_sock; char *key = NULL, *val = NULL, *cmd, *op = NULL; int key_len, val_len, cmd_len, op_len; enum {CFG_GET, CFG_SET} mode; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss|s", &object, redis_ce, &op, &op_len, &key, &key_len, &val, &val_len) == FAILURE) { RETURN_FALSE; } /* op must be GET or SET */ if(strncasecmp(op, "GET", 3) == 0) { mode = CFG_GET; } else if(strncasecmp(op, "SET", 3) == 0) { mode = CFG_SET; } else { RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } if (mode == CFG_GET && val == NULL) { cmd_len = redis_cmd_format_static(&cmd, "CONFIG", "ss", op, op_len, key, key_len); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) IF_ATOMIC() { redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_raw); } else if(mode == CFG_SET && val != NULL) { cmd_len = redis_cmd_format_static(&cmd, "CONFIG", "sss", op, op_len, key, key_len, val, val_len); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } else { RETURN_FALSE; } } /* }}} */ /* {{{ proto boolean Redis::slowlog(string arg, [int option]) */ PHP_METHOD(Redis, slowlog) { zval *object; RedisSock *redis_sock; char *arg, *cmd; int arg_len, cmd_len; long option; enum {SLOWLOG_GET, SLOWLOG_LEN, SLOWLOG_RESET} mode; /* Make sure we can get parameters */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", &object, redis_ce, &arg, &arg_len, &option) == FAILURE) { RETURN_FALSE; } /* Figure out what kind of slowlog command we're executing */ if(!strncasecmp(arg, "GET", 3)) { mode = SLOWLOG_GET; } else if(!strncasecmp(arg, "LEN", 3)) { mode = SLOWLOG_LEN; } else if(!strncasecmp(arg, "RESET", 5)) { mode = SLOWLOG_RESET; } else { /* This command is not valid */ RETURN_FALSE; } /* Make sure we can grab our redis socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Create our command. For everything except SLOWLOG GET (with an arg) it's just two parts */ if(mode == SLOWLOG_GET && ZEND_NUM_ARGS() == 2) { cmd_len = redis_cmd_format_static(&cmd, "SLOWLOG", "sl", arg, arg_len, option); } else { cmd_len = redis_cmd_format_static(&cmd, "SLOWLOG", "s", arg, arg_len); } /* Kick off our command */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_read_variant_reply); } /* {{{ proto Redis::rawCommand(string cmd, arg, arg, arg, ...) }}} */ PHP_METHOD(Redis, rawCommand) { zval **z_args; RedisSock *redis_sock; int argc = ZEND_NUM_ARGS(), i; smart_str cmd = {0}; /* We need at least one argument */ z_args = emalloc(argc * sizeof(zval*)); if (argc < 1 || zend_get_parameters_array(ht, argc, z_args) == FAILURE) { efree(z_args); RETURN_FALSE; } if (redis_sock_get(getThis(), &redis_sock TSRMLS_CC, 0) < 0) { efree(z_args); RETURN_FALSE; } /* Initialize the command we'll send */ convert_to_string(z_args[0]); redis_cmd_init_sstr(&cmd,argc-1,Z_STRVAL_P(z_args[0]),Z_STRLEN_P(z_args[0])); /* Iterate over the remainder of our arguments, appending */ for (i = 1; i < argc; i++) { convert_to_string(z_args[i]); redis_cmd_append_sstr(&cmd, Z_STRVAL_P(z_args[i]), Z_STRLEN_P(z_args[i])); } efree(z_args); /* Kick off our request and read response or enqueue handler */ REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); IF_ATOMIC() { if (redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_read_variant_reply); } /* {{{ proto Redis::wait(int num_slaves, int ms) }}} */ PHP_METHOD(Redis, wait) { zval *object; RedisSock *redis_sock; long num_slaves, timeout; char *cmd; int cmd_len; /* Make sure arguments are valid */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oll", &object, redis_ce, &num_slaves, &timeout) ==FAILURE) { RETURN_FALSE; } /* Don't even send this to Redis if our args are negative */ if(num_slaves < 0 || timeout < 0) { RETURN_FALSE; } /* Grab our socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0)<0) { RETURN_FALSE; } /* Construct the command */ cmd_len = redis_cmd_format_static(&cmd, "WAIT", "ll", num_slaves, timeout); /* Kick it off */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* * Construct a PUBSUB command */ PHP_REDIS_API int redis_build_pubsub_cmd(RedisSock *redis_sock, char **ret, PUBSUB_TYPE type, zval *arg TSRMLS_DC) { HashTable *ht_chan; HashPosition ptr; zval **z_ele; char *key; int cmd_len, key_len, key_free; smart_str cmd = {0}; if(type == PUBSUB_CHANNELS) { if(arg) { /* Get string argument and length. */ key = Z_STRVAL_P(arg); key_len = Z_STRLEN_P(arg); /* Prefix if necissary */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); /* With a pattern */ cmd_len = redis_cmd_format_static(ret, "PUBSUB", "ss", "CHANNELS", sizeof("CHANNELS")-1, key, key_len); /* Free the channel name if we prefixed it */ if(key_free) efree(key); /* Return command length */ return cmd_len; } else { /* No pattern */ return redis_cmd_format_static(ret, "PUBSUB", "s", "CHANNELS", sizeof("CHANNELS")-1); } } else if(type == PUBSUB_NUMSUB) { ht_chan = Z_ARRVAL_P(arg); /* Add PUBSUB and NUMSUB bits */ redis_cmd_init_sstr(&cmd, zend_hash_num_elements(ht_chan)+1, "PUBSUB", sizeof("PUBSUB")-1); redis_cmd_append_sstr(&cmd, "NUMSUB", sizeof("NUMSUB")-1); /* Iterate our elements */ for(zend_hash_internal_pointer_reset_ex(ht_chan, &ptr); zend_hash_get_current_data_ex(ht_chan, (void**)&z_ele, &ptr)==SUCCESS; zend_hash_move_forward_ex(ht_chan, &ptr)) { char *key; int key_len, key_free; zval *z_tmp = NULL; if(Z_TYPE_PP(z_ele) == IS_STRING) { key = Z_STRVAL_PP(z_ele); key_len = Z_STRLEN_PP(z_ele); } else { MAKE_STD_ZVAL(z_tmp); *z_tmp = **z_ele; zval_copy_ctor(z_tmp); convert_to_string(z_tmp); key = Z_STRVAL_P(z_tmp); key_len = Z_STRLEN_P(z_tmp); } /* Apply prefix if required */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); /* Append this channel */ redis_cmd_append_sstr(&cmd, key, key_len); /* Free key if prefixed */ if(key_free) efree(key); /* Free our temp var if we converted from something other than a string */ if(z_tmp) { zval_dtor(z_tmp); efree(z_tmp); z_tmp = NULL; } } /* Set return */ *ret = cmd.c; return cmd.len; } else if(type == PUBSUB_NUMPAT) { return redis_cmd_format_static(ret, "PUBSUB", "s", "NUMPAT", sizeof("NUMPAT")-1); } /* Shouldn't ever happen */ return -1; } /* * {{{ proto Redis::pubsub("channels", pattern); * proto Redis::pubsub("numsub", Array channels); * proto Redis::pubsub("numpat"); }}} */ PHP_METHOD(Redis, pubsub) { zval *object; RedisSock *redis_sock; char *keyword, *cmd; int kw_len, cmd_len; PUBSUB_TYPE type; zval *arg=NULL; /* Parse arguments */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|z", &object, redis_ce, &keyword, &kw_len, &arg) ==FAILURE) { RETURN_FALSE; } /* Validate our sub command keyword, and that we've got proper arguments */ if(!strncasecmp(keyword, "channels", sizeof("channels"))) { /* One (optional) string argument */ if(arg && Z_TYPE_P(arg) != IS_STRING) { RETURN_FALSE; } type = PUBSUB_CHANNELS; } else if(!strncasecmp(keyword, "numsub", sizeof("numsub"))) { /* One array argument */ if(ZEND_NUM_ARGS() < 2 || Z_TYPE_P(arg) != IS_ARRAY || zend_hash_num_elements(Z_ARRVAL_P(arg))==0) { RETURN_FALSE; } type = PUBSUB_NUMSUB; } else if(!strncasecmp(keyword, "numpat", sizeof("numpat"))) { type = PUBSUB_NUMPAT; } else { /* Invalid keyword */ RETURN_FALSE; } /* Grab our socket context object */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0)<0) { RETURN_FALSE; } /* Construct our "PUBSUB" command */ cmd_len = redis_build_pubsub_cmd(redis_sock, &cmd, type, arg TSRMLS_CC); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); if(type == PUBSUB_NUMSUB) { IF_ATOMIC() { if(redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL)<0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_keys_int); } else { IF_ATOMIC() { if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL)<0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_read_variant_reply); } } /* Construct an EVAL or EVALSHA command, with option argument array and number of arguments that are keys parameter */ PHP_REDIS_API int redis_build_eval_cmd(RedisSock *redis_sock, char **ret, char *keyword, char *value, int val_len, zval *args, int keys_count TSRMLS_DC) { zval **elem; HashTable *args_hash; HashPosition hash_pos; int cmd_len, args_count = 0; int eval_cmd_count = 2; /* If we've been provided arguments, we'll want to include those in our eval command */ if(args != NULL) { /* Init our hash array value, and grab the count */ args_hash = Z_ARRVAL_P(args); args_count = zend_hash_num_elements(args_hash); /* We only need to process the arguments if the array is non empty */ if(args_count > 0) { /* Header for our EVAL command */ cmd_len = redis_cmd_format_header(ret, keyword, eval_cmd_count + args_count); /* Now append the script itself, and the number of arguments to treat as keys */ cmd_len = redis_cmd_append_str(ret, cmd_len, value, val_len); cmd_len = redis_cmd_append_int(ret, cmd_len, keys_count); /* Iterate the values in our "keys" array */ for(zend_hash_internal_pointer_reset_ex(args_hash, &hash_pos); zend_hash_get_current_data_ex(args_hash, (void **)&elem, &hash_pos) == SUCCESS; zend_hash_move_forward_ex(args_hash, &hash_pos)) { zval *z_tmp = NULL; char *key, *old_cmd; int key_len, key_free; if(Z_TYPE_PP(elem) == IS_STRING) { key = Z_STRVAL_PP(elem); key_len = Z_STRLEN_PP(elem); } else { /* Convert it to a string */ MAKE_STD_ZVAL(z_tmp); *z_tmp = **elem; zval_copy_ctor(z_tmp); convert_to_string(z_tmp); key = Z_STRVAL_P(z_tmp); key_len = Z_STRLEN_P(z_tmp); } /* Keep track of the old command pointer */ old_cmd = *ret; /* If this is still a key argument, prefix it if we've been set up to prefix keys */ key_free = keys_count-- > 0 ? redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC) : 0; /* Append this key to our EVAL command, free our old command */ cmd_len = redis_cmd_format(ret, "%s$%d" _NL "%s" _NL, *ret, cmd_len, key_len, key, key_len); efree(old_cmd); /* Free our key, old command if we need to */ if(key_free) efree(key); /* Free our temporary zval (converted from non string) if we've got one */ if(z_tmp) { zval_dtor(z_tmp); efree(z_tmp); } } } } /* If there weren't any arguments (none passed, or an empty array), construct a standard no args command */ if(args_count < 1) { cmd_len = redis_cmd_format_static(ret, keyword, "sd", value, val_len, 0); } /* Return our command length */ return cmd_len; } /* {{{ proto variant Redis::evalsha(string script_sha1, [array keys, int num_key_args]) */ PHP_METHOD(Redis, evalsha) { zval *object, *args= NULL; char *cmd, *sha; int cmd_len, sha_len; long keys_count = 0; RedisSock *redis_sock; if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|al", &object, redis_ce, &sha, &sha_len, &args, &keys_count) == FAILURE) { RETURN_FALSE; } /* Attempt to grab socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Construct our EVALSHA command */ cmd_len = redis_build_eval_cmd(redis_sock, &cmd, "EVALSHA", sha, sha_len, args, keys_count TSRMLS_CC); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_read_variant_reply); } /* {{{ proto variant Redis::eval(string script, [array keys, int num_key_args]) */ PHP_METHOD(Redis, eval) { zval *object, *args = NULL; RedisSock *redis_sock; char *script, *cmd = ""; int script_len, cmd_len; long keys_count = 0; /* Attempt to parse parameters */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|al", &object, redis_ce, &script, &script_len, &args, &keys_count) == FAILURE) { RETURN_FALSE; } /* Attempt to grab socket */ if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Construct our EVAL command */ cmd_len = redis_build_eval_cmd(redis_sock, &cmd, "EVAL", script, script_len, args, keys_count TSRMLS_CC); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_read_variant_reply); } PHP_REDIS_API int redis_build_script_exists_cmd(char **ret, zval **argv, int argc) { /* Our command length and iterator */ int cmd_len = 0, i; /* Start building our command */ cmd_len = redis_cmd_format_header(ret, "SCRIPT", argc + 1); /* +1 for "EXISTS" */ cmd_len = redis_cmd_append_str(ret, cmd_len, "EXISTS", 6); /* Iterate our arguments */ for(i=0;i<argc;i++) { /* Convert our argument to a string if we need to */ convert_to_string(argv[i]); /* Append this script sha to our SCRIPT EXISTS command */ cmd_len = redis_cmd_append_str(ret, cmd_len, Z_STRVAL_P(argv[i]), Z_STRLEN_P(argv[i])); } /* Success */ return cmd_len; } /* {{{ proto status Redis::script('flush') * {{{ proto status Redis::script('kill') * {{{ proto string Redis::script('load', lua_script) * {{{ proto int Reids::script('exists', script_sha1 [, script_sha2, ...]) */ PHP_METHOD(Redis, script) { zval **z_args; RedisSock *redis_sock; int cmd_len, argc; char *cmd; /* Attempt to grab our socket */ if(redis_sock_get(getThis(), &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Grab the number of arguments */ argc = ZEND_NUM_ARGS(); /* Allocate an array big enough to store our arguments */ z_args = emalloc(argc * sizeof(zval*)); /* Make sure we can grab our arguments, we have a string directive */ if(zend_get_parameters_array(ht, argc, z_args) == FAILURE || (argc < 1 || Z_TYPE_P(z_args[0]) != IS_STRING)) { efree(z_args); RETURN_FALSE; } /* Branch based on the directive */ if(!strcasecmp(Z_STRVAL_P(z_args[0]), "flush") || !strcasecmp(Z_STRVAL_P(z_args[0]), "kill")) { /* Simple SCRIPT FLUSH, or SCRIPT_KILL command */ cmd_len = redis_cmd_format_static(&cmd, "SCRIPT", "s", Z_STRVAL_P(z_args[0]), Z_STRLEN_P(z_args[0])); } else if(!strcasecmp(Z_STRVAL_P(z_args[0]), "load")) { /* Make sure we have a second argument, and it's not empty. If it is */ /* empty, we can just return an empty array (which is what Redis does) */ if(argc < 2 || Z_TYPE_P(z_args[1]) != IS_STRING || Z_STRLEN_P(z_args[1]) < 1) { /* Free our args */ efree(z_args); RETURN_FALSE; } /* Format our SCRIPT LOAD command */ cmd_len = redis_cmd_format_static(&cmd, "SCRIPT", "ss", "LOAD", 4, Z_STRVAL_P(z_args[1]), Z_STRLEN_P(z_args[1])); } else if(!strcasecmp(Z_STRVAL_P(z_args[0]), "exists")) { /* Construct our SCRIPT EXISTS command */ cmd_len = redis_build_script_exists_cmd(&cmd, &(z_args[1]), argc-1); } else { /* Unknown directive */ efree(z_args); RETURN_FALSE; } /* Free our alocated arguments */ efree(z_args); /* Kick off our request */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_read_variant_reply); } /* {{{ proto DUMP key */ PHP_METHOD(Redis, dump) { zval *object; RedisSock *redis_sock; char *cmd, *key; int cmd_len, key_len, key_free; /* Parse our arguments */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } /* Grab our socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Prefix our key if we need to */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "DUMP", "s", key, key_len); if(key_free) efree(key); /* Kick off our request */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_ping_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_ping_response); } /* {{{ proto Redis::DEBUG(string key) */ PHP_METHOD(Redis, debug) { zval *object; RedisSock *redis_sock; char *cmd, *key; int cmd_len, key_len, key_free; if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len)==FAILURE) { RETURN_FALSE; } /* Grab our socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0)<0) { RETURN_FALSE; } /* Prefix key, format command */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "DEBUG", "ss", "OBJECT", sizeof("OBJECT")-1, key, key_len); if(key_free) efree(key); /* Kick it off */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_debug_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_debug_response); } /* * {{{ proto Redis::restore(ttl, key, value) */ PHP_METHOD(Redis, restore) { zval *object; RedisSock *redis_sock; char *cmd, *key, *value; int cmd_len, key_len, value_len, key_free; long ttl; /* Parse our arguments */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osls", &object, redis_ce, &key, &key_len, &ttl, &value, &value_len) == FAILURE) { RETURN_FALSE; } /* Grab our socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Prefix the key if we need to */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "RESTORE", "sls", key, key_len, ttl, value, value_len); if(key_free) efree(key); /* Kick off our restore request */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } /* * {{{ proto Redis::migrate(host port key dest-db timeout [bool copy, bool replace]) */ PHP_METHOD(Redis, migrate) { zval *object; RedisSock *redis_sock; char *cmd, *host, *key; int cmd_len, host_len, key_len, key_free; zend_bool copy=0, replace=0; long port, dest_db, timeout; /* Parse arguments */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oslsll|bb", &object, redis_ce, &host, &host_len, &port, &key, &key_len, &dest_db, &timeout, ©, &replace) == FAILURE) { RETURN_FALSE; } /* Grabg our socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Prefix our key if we need to, build our command */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); /* Construct our command */ if(copy && replace) { cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsddss", host, host_len, port, key, key_len, dest_db, timeout, "COPY", sizeof("COPY")-1, "REPLACE", sizeof("REPLACE")-1); } else if(copy) { cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsdds", host, host_len, port, key, key_len, dest_db, timeout, "COPY", sizeof("COPY")-1); } else if(replace) { cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsdds", host, host_len, port, key, key_len, dest_db, timeout, "REPLACE", sizeof("REPLACE")-1); } else { cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsdd", host, host_len, port, key, key_len, dest_db, timeout); } /* Free our key if we prefixed it */ if(key_free) efree(key); /* Kick off our MIGRATE request */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } /* * {{{ proto Redis::_prefix(key) */ PHP_METHOD(Redis, _prefix) { zval *object; RedisSock *redis_sock; char *key; int key_len; /* Parse our arguments */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } /* Grab socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Prefix our key if we need to */ if(redis_sock->prefix != NULL && redis_sock->prefix_len > 0) { redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); RETURN_STRINGL(key, key_len, 0); } else { RETURN_STRINGL(key, key_len, 1); } } /* * {{{ proto Redis::_serialize(value) */ PHP_METHOD(Redis, _serialize) { zval *object; RedisSock *redis_sock; zval *z_val; char *val; int val_len, val_free; /* Parse arguments */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oz", &object, redis_ce, &z_val) == FAILURE) { RETURN_FALSE; } /* Grab socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Serialize, which will return a value even if no serializer is set */ val_free = redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC); /* Return serialized value. Tell PHP to make a copy as some can be interned. */ RETVAL_STRINGL(val, val_len, 1); if(val_free) STR_FREE(val); } /* * {{{ proto Redis::_unserialize(value) */ PHP_METHOD(Redis, _unserialize) { zval *object; RedisSock *redis_sock; char *value; int value_len; /* Parse our arguments */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &value, &value_len) == FAILURE) { RETURN_FALSE; } /* Grab socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* We only need to attempt unserialization if we have a serializer running */ if(redis_sock->serializer != REDIS_SERIALIZER_NONE) { zval *z_ret = NULL; if(redis_unserialize(redis_sock, value, value_len, &z_ret TSRMLS_CC) == 0) { /* Badly formed input, throw an execption */ zend_throw_exception(redis_exception_ce, "Invalid serialized data, or unserialization error", 0 TSRMLS_CC); RETURN_FALSE; } RETURN_ZVAL(z_ret, 0, 1); } else { /* Just return the value that was passed to us */ RETURN_STRINGL(value, value_len, 1); } } /* * {{{ proto Redis::getLastError() */ PHP_METHOD(Redis, getLastError) { zval *object; RedisSock *redis_sock; /* Grab our object */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } /* Grab socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Return our last error or NULL if we don't have one */ if(redis_sock->err != NULL && redis_sock->err_len > 0) { RETURN_STRINGL(redis_sock->err, redis_sock->err_len, 1); } else { RETURN_NULL(); } } /* * {{{ proto Redis::clearLastError() */ PHP_METHOD(Redis, clearLastError) { zval *object; RedisSock *redis_sock; /* Grab our object */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } /* Grab socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Clear error message */ if(redis_sock->err) { efree(redis_sock->err); } redis_sock->err = NULL; RETURN_TRUE; } /* * {{{ proto long Redis::getMode() */ PHP_METHOD(Redis, getMode) { zval *object; RedisSock *redis_sock; /* Grab our object */ if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } /* Grab socket */ if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } RETVAL_LONG(redis_sock->mode); } /* * {{{ proto Redis::time() */ PHP_METHOD(Redis, time) { zval *object; RedisSock *redis_sock; char *cmd; int cmd_len; /* Grab our object */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } /* Grab socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Build TIME command */ cmd_len = redis_cmd_format_static(&cmd, "TIME", ""); /* Execute or queue command */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { if(redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_mbulk_reply_raw); } /* * Introspection stuff */ /* * {{{ proto Redis::IsConnected */ PHP_METHOD(Redis, isConnected) { RedisSock *redis_sock; if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { RETURN_TRUE; } else { RETURN_FALSE; } } /* * {{{ proto Redis::getHost() */ PHP_METHOD(Redis, getHost) { RedisSock *redis_sock; if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { RETURN_STRING(redis_sock->host, 1); } else { RETURN_FALSE; } } /* * {{{ proto Redis::getPort() */ PHP_METHOD(Redis, getPort) { RedisSock *redis_sock; if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { /* Return our port */ RETURN_LONG(redis_sock->port); } else { RETURN_FALSE; } } /* * {{{ proto Redis::getDBNum */ PHP_METHOD(Redis, getDBNum) { RedisSock *redis_sock; if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { /* Return our db number */ RETURN_LONG(redis_sock->dbNumber); } else { RETURN_FALSE; } } /* * {{{ proto Redis::getTimeout */ PHP_METHOD(Redis, getTimeout) { RedisSock *redis_sock; if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { RETURN_DOUBLE(redis_sock->timeout); } else { RETURN_FALSE; } } /* * {{{ proto Redis::getReadTimeout */ PHP_METHOD(Redis, getReadTimeout) { RedisSock *redis_sock; if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { RETURN_DOUBLE(redis_sock->read_timeout); } else { RETURN_FALSE; } } /* * {{{ proto Redis::getPersistentID */ PHP_METHOD(Redis, getPersistentID) { RedisSock *redis_sock; if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { if(redis_sock->persistent_id != NULL) { RETURN_STRING(redis_sock->persistent_id, 1); } else { RETURN_NULL(); } } else { RETURN_FALSE; } } /* * {{{ proto Redis::getAuth */ PHP_METHOD(Redis, getAuth) { RedisSock *redis_sock; if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { if(redis_sock->auth != NULL) { RETURN_STRING(redis_sock->auth, 1); } else { RETURN_NULL(); } } else { RETURN_FALSE; } } /* * $redis->client('list'); * $redis->client('kill', <ip:port>); * $redis->client('setname', <name>); * $redis->client('getname'); */ PHP_METHOD(Redis, client) { zval *object; RedisSock *redis_sock; char *cmd, *opt=NULL, *arg=NULL; int cmd_len, opt_len, arg_len; /* Parse our method parameters */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|s", &object, redis_ce, &opt, &opt_len, &arg, &arg_len) == FAILURE) { RETURN_FALSE; } /* Grab our socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Build our CLIENT command */ if(ZEND_NUM_ARGS() == 2) { cmd_len = redis_cmd_format_static(&cmd, "CLIENT", "ss", opt, opt_len, arg, arg_len); } else { cmd_len = redis_cmd_format_static(&cmd, "CLIENT", "s", opt, opt_len); } /* Execute our queue command */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); /* We handle CLIENT LIST with a custom response function */ if(!strncasecmp(opt, "list", 4)) { IF_ATOMIC() { redis_client_list_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,redis_sock,NULL); } REDIS_PROCESS_RESPONSE(redis_client_list_reply); } else { IF_ATOMIC() { redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,redis_sock,NULL); } REDIS_PROCESS_RESPONSE(redis_read_variant_reply); } } /** * Helper to format any combination of SCAN arguments */ PHP_REDIS_API int redis_build_scan_cmd(char **cmd, REDIS_SCAN_TYPE type, char *key, int key_len, int iter, char *pattern, int pattern_len, int count) { char *keyword; int arg_count, cmd_len; /* Count our arguments +1 for key if it's got one, and + 2 for pattern */ /* or count given that they each carry keywords with them. */ arg_count = 1 + (key_len>0) + (pattern_len>0?2:0) + (count>0?2:0); /* Turn our type into a keyword */ switch(type) { case TYPE_SCAN: keyword = "SCAN"; break; case TYPE_SSCAN: keyword = "SSCAN"; break; case TYPE_HSCAN: keyword = "HSCAN"; break; case TYPE_ZSCAN: default: keyword = "ZSCAN"; break; } /* Start the command */ cmd_len = redis_cmd_format_header(cmd, keyword, arg_count); /* Add the key in question if we have one */ if(key_len) { cmd_len = redis_cmd_append_str(cmd, cmd_len, key, key_len); } /* Add our iterator */ cmd_len = redis_cmd_append_int(cmd, cmd_len, iter); /* Append COUNT if we've got it */ if(count) { cmd_len = redis_cmd_append_str(cmd, cmd_len, "COUNT", sizeof("COUNT")-1); cmd_len = redis_cmd_append_int(cmd, cmd_len, count); } /* Append MATCH if we've got it */ if(pattern_len) { cmd_len = redis_cmd_append_str(cmd, cmd_len, "MATCH", sizeof("MATCH")-1); cmd_len = redis_cmd_append_str(cmd, cmd_len, pattern, pattern_len); } /* Return our command length */ return cmd_len; } /** * {{{ proto redis::scan(&$iterator, [pattern, [count]]) */ PHP_REDIS_API void generic_scan_cmd(INTERNAL_FUNCTION_PARAMETERS, REDIS_SCAN_TYPE type) { zval *object, *z_iter; RedisSock *redis_sock; HashTable *hash; char *pattern=NULL, *cmd, *key=NULL; int cmd_len, key_len=0, pattern_len=0, num_elements, key_free=0; long count=0, iter; /* Different prototype depending on if this is a key based scan */ if(type != TYPE_SCAN) { /* Requires a key */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz/|s!l", &object, redis_ce, &key, &key_len, &z_iter, &pattern, &pattern_len, &count)==FAILURE) { RETURN_FALSE; } } else { /* Doesn't require a key */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oz/|s!l", &object, redis_ce, &z_iter, &pattern, &pattern_len, &count) == FAILURE) { RETURN_FALSE; } } /* Grab our socket */ if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* Calling this in a pipeline makes no sense */ IF_NOT_ATOMIC() { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Can't call SCAN commands in multi or pipeline mode!"); RETURN_FALSE; } /* The iterator should be passed in as NULL for the first iteration, but we can treat */ /* any NON LONG value as NULL for these purposes as we've seperated the variable anyway. */ if(Z_TYPE_P(z_iter) != IS_LONG || Z_LVAL_P(z_iter)<0) { /* Convert to long */ convert_to_long(z_iter); iter = 0; } else if(Z_LVAL_P(z_iter)!=0) { /* Update our iterator value for the next passthru */ iter = Z_LVAL_P(z_iter); } else { /* We're done, back to iterator zero */ RETURN_FALSE; } /* Prefix our key if we've got one and we have a prefix set */ if(key_len) { key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); } /** * Redis can return to us empty keys, especially in the case where there are a large * number of keys to scan, and we're matching against a pattern. PHPRedis can be set * up to abstract this from the user, by setting OPT_SCAN to REDIS_SCAN_RETRY. Otherwise * we will return empty keys and the user will need to make subsequent calls with * an updated iterator. */ do { /* Free our previous reply if we're back in the loop. We know we are * if our return_value is an array. */ if(Z_TYPE_P(return_value) == IS_ARRAY) { zval_dtor(return_value); ZVAL_NULL(return_value); } /* Format our SCAN command */ cmd_len = redis_build_scan_cmd(&cmd, type, key, key_len, (int)iter, pattern, pattern_len, count); /* Execute our command getting our new iterator value */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); if(redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,type,&iter)<0) { if(key_free) efree(key); RETURN_FALSE; } /* Get the number of elements */ hash = Z_ARRVAL_P(return_value); num_elements = zend_hash_num_elements(hash); } while(redis_sock->scan == REDIS_SCAN_RETRY && iter != 0 && num_elements == 0); /* Free our key if it was prefixed */ if(key_free) efree(key); /* Update our iterator reference */ Z_LVAL_P(z_iter) = iter; } PHP_METHOD(Redis, scan) { generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_SCAN); } PHP_METHOD(Redis, hscan) { generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_HSCAN); } PHP_METHOD(Redis, sscan) { generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_SSCAN); } PHP_METHOD(Redis, zscan) { generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_ZSCAN); } /* * HyperLogLog based commands */ /* {{{ proto Redis::pfAdd(string key, array elements) }}} */ PHP_METHOD(Redis, pfadd) { zval *object; RedisSock *redis_sock; char *key; int key_len, key_free, argc=1; zval *z_mems, **z_mem; HashTable *ht_mems; HashPosition pos; smart_str cmd = {0}; if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa", &object, redis_ce, &key, &key_len, &z_mems) ==FAILURE) { RETURN_FALSE; } if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } // Grab members as an array ht_mems = Z_ARRVAL_P(z_mems); // Total arguments we'll be sending argc += zend_hash_num_elements(ht_mems); // If the array was empty we can just exit if(argc < 2) { RETURN_FALSE; } // Start constructing our command redis_cmd_init_sstr(&cmd, argc, "PFADD", sizeof("PFADD")-1); // Prefix our key if we're prefixing key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); redis_cmd_append_sstr(&cmd, key, key_len); if(key_free) efree(key); // Iterate over members we're adding for(zend_hash_internal_pointer_reset_ex(ht_mems, &pos); zend_hash_get_current_data_ex(ht_mems, (void**)&z_mem, &pos)==SUCCESS; zend_hash_move_forward_ex(ht_mems, &pos)) { char *mem; int mem_len, val_free; zval *z_tmp = NULL; // Serialize if requested val_free = redis_serialize(redis_sock, *z_mem, &mem, &mem_len TSRMLS_CC); // Allow for non string members if we're not serializing if(!val_free) { if(Z_TYPE_PP(z_mem)==IS_STRING) { mem = Z_STRVAL_PP(z_mem); mem_len = Z_STRLEN_PP(z_mem); } else { MAKE_STD_ZVAL(z_tmp); *z_tmp = **z_mem; convert_to_string(z_tmp); mem = Z_STRVAL_P(z_tmp); mem_len = Z_STRLEN_P(z_tmp); } } // Append this member redis_cmd_append_sstr(&cmd, mem, mem_len); // Free memory if we serialized or converted types if(z_tmp) { zval_dtor(z_tmp); efree(z_tmp); z_tmp = NULL; } else if(val_free) { efree(mem); } } REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); IF_ATOMIC() { redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_1_response); } /* {{{ proto Redis::pfCount(string key) }}} * proto Redis::pfCount(array keys) }}} */ PHP_METHOD(Redis, pfcount) { zval *object, *z_keys, **z_key, *z_tmp = NULL; HashTable *ht_keys; HashPosition ptr; RedisSock *redis_sock; smart_str cmd = {0}; int num_keys, key_len, key_free; char *key; if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oz", &object, redis_ce, &z_keys)==FAILURE) { RETURN_FALSE; } if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } /* If we were passed an array of keys, iterate through them prefixing if * required and capturing lengths and if we need to free them. Otherwise * attempt to treat the argument as a string and just pass one */ if (Z_TYPE_P(z_keys) == IS_ARRAY) { /* Grab key hash table and the number of keys */ ht_keys = Z_ARRVAL_P(z_keys); num_keys = zend_hash_num_elements(ht_keys); /* There is no reason to send zero keys */ if (num_keys == 0) { RETURN_FALSE; } /* Initialize the command with our number of arguments */ redis_cmd_init_sstr(&cmd, num_keys, "PFCOUNT", sizeof("PFCOUNT")-1); /* Append our key(s) */ for (zend_hash_internal_pointer_reset_ex(ht_keys, &ptr); zend_hash_get_current_data_ex(ht_keys, (void**)&z_key, &ptr)==SUCCESS; zend_hash_move_forward_ex(ht_keys, &ptr)) { /* Turn our value into a string if it isn't one */ if (Z_TYPE_PP(z_key) != IS_STRING) { MAKE_STD_ZVAL(z_tmp); *z_tmp = **z_key; zval_copy_ctor(z_tmp); convert_to_string(z_tmp); key = Z_STRVAL_P(z_tmp); key_len = Z_STRLEN_P(z_tmp); } else { key = Z_STRVAL_PP(z_key); key_len = Z_STRLEN_PP(z_key); } /* Append this key to our command */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); redis_cmd_append_sstr(&cmd, key, key_len); /* Cleanup */ if (key_free) efree(key); if (z_tmp) { zval_dtor(z_tmp); efree(z_tmp); z_tmp = NULL; } } } else { /* Turn our key into a string if it's a different type */ if (Z_TYPE_P(z_keys) != IS_STRING) { MAKE_STD_ZVAL(z_tmp); *z_tmp = *z_keys; zval_copy_ctor(z_tmp); convert_to_string(z_tmp); key = Z_STRVAL_P(z_tmp); key_len = Z_STRLEN_P(z_tmp); } else { key = Z_STRVAL_P(z_keys); key_len = Z_STRLEN_P(z_keys); } /* Construct our whole command */ redis_cmd_init_sstr(&cmd, 1, "PFCOUNT", sizeof("PFCOUNT")-1); key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); redis_cmd_append_sstr(&cmd, key, key_len); /* Cleanup */ if (key_free) efree(key); if (z_tmp) { zval_dtor(z_tmp); efree(z_tmp); } } REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* }}} */ /* {{{ proto Redis::pfMerge(array keys) }}}*/ PHP_METHOD(Redis, pfmerge) { zval *object; RedisSock *redis_sock; zval *z_keys, **z_key; HashTable *ht_keys; HashPosition pos; smart_str cmd = {0}; int key_len, key_free, argc=1; char *key; if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa", &object, redis_ce, &key, &key_len, &z_keys)==FAILURE) { RETURN_FALSE; } if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } // Grab keys as an array ht_keys = Z_ARRVAL_P(z_keys); // Total arguments we'll be sending argc += zend_hash_num_elements(ht_keys); // If no keys were passed we can abort if(argc<2) { RETURN_FALSE; } // Initial construction of our command redis_cmd_init_sstr(&cmd, argc, "PFMERGE", sizeof("PFMERGE")-1); // Add our destination key (prefixed if necessary) key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); redis_cmd_append_sstr(&cmd, key, key_len); if(key_free) efree(key); // Iterate our keys array for(zend_hash_internal_pointer_reset_ex(ht_keys, &pos); zend_hash_get_current_data_ex(ht_keys, (void**)&z_key, &pos)==SUCCESS; zend_hash_move_forward_ex(ht_keys, &pos)) { zval *z_tmp = NULL; // Keys could look like a number if(Z_TYPE_PP(z_key) == IS_STRING) { key = Z_STRVAL_PP(z_key); key_len = Z_STRLEN_PP(z_key); } else { MAKE_STD_ZVAL(z_tmp); *z_tmp = **z_key; convert_to_string(z_tmp); key = Z_STRVAL_P(z_tmp); key_len = Z_STRLEN_P(z_tmp); } // Prefix our key if necessary and append this key key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); redis_cmd_append_sstr(&cmd, key, key_len); if(key_free) efree(key); // Free temporary zval if we converted if(z_tmp) { zval_dtor(z_tmp); efree(z_tmp); z_tmp = NULL; } } REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } /* vim: set tabstop=4 softtabstops=4 noexpandtab shiftwidth=4: */ /* -*- Mode: C; tab-width: 4 -*- */ /* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2009 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Original author: Alfonso Jimenez <yo@alfonsojimenez.com> | | Maintainer: Nicolas Favre-Felix <n.favre-felix@owlient.eu> | | Maintainer: Nasreddine Bouafif <n.bouafif@owlient.eu> | | Maintainer: Michael Grunder <michael.grunder@gmail.com> | +----------------------------------------------------------------------+ */ #include "common.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef PHP_SESSION #include "common.h" #include "ext/standard/info.h" #include "php_redis.h" #include "redis_session.h" #include <zend_exceptions.h> #include "library.h" #include "php.h" #include "php_ini.h" #include "php_variables.h" #include "SAPI.h" #include "ext/standard/url.h" ps_module ps_mod_redis = { PS_MOD(redis) }; typedef struct redis_pool_member_ { RedisSock *redis_sock; int weight; int database; char *prefix; size_t prefix_len; char *auth; size_t auth_len; struct redis_pool_member_ *next; } redis_pool_member; typedef struct { int totalWeight; int count; redis_pool_member *head; } redis_pool; PHP_REDIS_API redis_pool* redis_pool_new(TSRMLS_D) { return ecalloc(1, sizeof(redis_pool)); } PHP_REDIS_API void redis_pool_add(redis_pool *pool, RedisSock *redis_sock, int weight, int database, char *prefix, char *auth TSRMLS_DC) { redis_pool_member *rpm = ecalloc(1, sizeof(redis_pool_member)); rpm->redis_sock = redis_sock; rpm->weight = weight; rpm->database = database; rpm->prefix = prefix; rpm->prefix_len = (prefix?strlen(prefix):0); rpm->auth = auth; rpm->auth_len = (auth?strlen(auth):0); rpm->next = pool->head; pool->head = rpm; pool->totalWeight += weight; } PHP_REDIS_API void redis_pool_free(redis_pool *pool TSRMLS_DC) { redis_pool_member *rpm, *next; rpm = pool->head; while(rpm) { next = rpm->next; redis_sock_disconnect(rpm->redis_sock TSRMLS_CC); efree(rpm->redis_sock); if(rpm->prefix) efree(rpm->prefix); if(rpm->auth) efree(rpm->auth); efree(rpm); rpm = next; } efree(pool); } void redis_pool_member_auth(redis_pool_member *rpm TSRMLS_DC) { RedisSock *redis_sock = rpm->redis_sock; char *response, *cmd; int response_len, cmd_len; if(!rpm->auth || !rpm->auth_len) { /* no password given. */ return; } cmd_len = redis_cmd_format_static(&cmd, "AUTH", "s", rpm->auth, rpm->auth_len); if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) >= 0) { if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC))) { efree(response); } } efree(cmd); } static void redis_pool_member_select(redis_pool_member *rpm TSRMLS_DC) { RedisSock *redis_sock = rpm->redis_sock; char *response, *cmd; int response_len, cmd_len; cmd_len = redis_cmd_format_static(&cmd, "SELECT", "d", rpm->database); if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) >= 0) { if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC))) { efree(response); } } efree(cmd); } PHP_REDIS_API redis_pool_member * redis_pool_get_sock(redis_pool *pool, const char *key TSRMLS_DC) { redis_pool_member *rpm = pool->head; unsigned int pos, i; memcpy(&pos, key, sizeof(pos)); pos %= pool->totalWeight; for(i = 0; i < pool->totalWeight;) { if(pos >= i && pos < i + rpm->weight) { int needs_auth = 0; if(rpm->auth && rpm->auth_len && rpm->redis_sock->status != REDIS_SOCK_STATUS_CONNECTED) { needs_auth = 1; } redis_sock_server_open(rpm->redis_sock, 0 TSRMLS_CC); if(needs_auth) { redis_pool_member_auth(rpm TSRMLS_CC); } if(rpm->database >= 0) { /* default is -1 which leaves the choice to redis. */ redis_pool_member_select(rpm TSRMLS_CC); } return rpm; } i += rpm->weight; rpm = rpm->next; } return NULL; } /* {{{ PS_OPEN_FUNC */ PS_OPEN_FUNC(redis) { php_url *url; zval *params, **param; int i, j, path_len; redis_pool *pool = redis_pool_new(TSRMLS_C); for (i=0,j=0,path_len=strlen(save_path); i<path_len; i=j+1) { /* find beginning of url */ while (i<path_len && (isspace(save_path[i]) || save_path[i] == ',')) i++; /* find end of url */ j = i; while (j<path_len && !isspace(save_path[j]) && save_path[j] != ',') j++; if (i < j) { int weight = 1; double timeout = 86400.0; int persistent = 0; int database = -1; char *prefix = NULL, *auth = NULL, *persistent_id = NULL; long retry_interval = 0; RedisSock *redis_sock; /* translate unix: into file: */ if (!strncmp(save_path+i, "unix:", sizeof("unix:")-1)) { int len = j-i; char *path = estrndup(save_path+i, len); memcpy(path, "file:", sizeof("file:")-1); url = php_url_parse_ex(path, len); efree(path); } else { url = php_url_parse_ex(save_path+i, j-i); } if (!url) { char *path = estrndup(save_path+i, j-i); php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse session.save_path (error at offset %d, url was '%s')", i, path); efree(path); redis_pool_free(pool TSRMLS_CC); PS_SET_MOD_DATA(NULL); return FAILURE; } /* parse parameters */ if (url->query != NULL) { MAKE_STD_ZVAL(params); array_init(params); sapi_module.treat_data(PARSE_STRING, estrdup(url->query), params TSRMLS_CC); if (zend_hash_find(Z_ARRVAL_P(params), "weight", sizeof("weight"), (void **) ¶m) != FAILURE) { convert_to_long_ex(param); weight = Z_LVAL_PP(param); } if (zend_hash_find(Z_ARRVAL_P(params), "timeout", sizeof("timeout"), (void **) ¶m) != FAILURE) { timeout = atof(Z_STRVAL_PP(param)); } if (zend_hash_find(Z_ARRVAL_P(params), "persistent", sizeof("persistent"), (void **) ¶m) != FAILURE) { persistent = (atol(Z_STRVAL_PP(param)) == 1 ? 1 : 0); } if (zend_hash_find(Z_ARRVAL_P(params), "persistent_id", sizeof("persistent_id"), (void **) ¶m) != FAILURE) { persistent_id = estrndup(Z_STRVAL_PP(param), Z_STRLEN_PP(param)); } if (zend_hash_find(Z_ARRVAL_P(params), "prefix", sizeof("prefix"), (void **) ¶m) != FAILURE) { prefix = estrndup(Z_STRVAL_PP(param), Z_STRLEN_PP(param)); } if (zend_hash_find(Z_ARRVAL_P(params), "auth", sizeof("auth"), (void **) ¶m) != FAILURE) { auth = estrndup(Z_STRVAL_PP(param), Z_STRLEN_PP(param)); } if (zend_hash_find(Z_ARRVAL_P(params), "database", sizeof("database"), (void **) ¶m) != FAILURE) { convert_to_long_ex(param); database = Z_LVAL_PP(param); } if (zend_hash_find(Z_ARRVAL_P(params), "retry_interval", sizeof("retry_interval"), (void **) ¶m) != FAILURE) { convert_to_long_ex(param); retry_interval = Z_LVAL_PP(param); } zval_ptr_dtor(¶ms); } if ((url->path == NULL && url->host == NULL) || weight <= 0 || timeout <= 0) { php_url_free(url); redis_pool_free(pool TSRMLS_CC); PS_SET_MOD_DATA(NULL); return FAILURE; } if(url->host) { redis_sock = redis_sock_create(url->host, strlen(url->host), url->port, timeout, persistent, persistent_id, retry_interval, 0); } else { /* unix */ redis_sock = redis_sock_create(url->path, strlen(url->path), 0, timeout, persistent, persistent_id, retry_interval, 0); } redis_pool_add(pool, redis_sock, weight, database, prefix, auth TSRMLS_CC); php_url_free(url); } } if (pool->head) { PS_SET_MOD_DATA(pool); return SUCCESS; } return FAILURE; } /* }}} */ /* {{{ PS_CLOSE_FUNC */ PS_CLOSE_FUNC(redis) { redis_pool *pool = PS_GET_MOD_DATA(); if(pool){ redis_pool_free(pool TSRMLS_CC); PS_SET_MOD_DATA(NULL); } return SUCCESS; } /* }}} */ static char * redis_session_key(redis_pool_member *rpm, const char *key, int key_len, int *session_len) { char *session; char default_prefix[] = "PHPREDIS_SESSION:"; char *prefix = default_prefix; size_t prefix_len = sizeof(default_prefix)-1; if(rpm->prefix) { prefix = rpm->prefix; prefix_len = rpm->prefix_len; } /* build session key */ *session_len = key_len + prefix_len; session = emalloc(*session_len); memcpy(session, prefix, prefix_len); memcpy(session + prefix_len, key, key_len); return session; } /* {{{ PS_READ_FUNC */ PS_READ_FUNC(redis) { char *session, *cmd; int session_len, cmd_len; redis_pool *pool = PS_GET_MOD_DATA(); redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC); RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; if(!rpm || !redis_sock){ return FAILURE; } /* send GET command */ session = redis_session_key(rpm, key, strlen(key), &session_len); cmd_len = redis_cmd_format_static(&cmd, "GET", "s", session, session_len); efree(session); if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); return FAILURE; } efree(cmd); /* read response */ if ((*val = redis_sock_read(redis_sock, vallen TSRMLS_CC)) == NULL) { return FAILURE; } return SUCCESS; } /* }}} */ /* {{{ PS_WRITE_FUNC */ PS_WRITE_FUNC(redis) { char *cmd, *response, *session; int cmd_len, response_len, session_len; redis_pool *pool = PS_GET_MOD_DATA(); redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC); RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; if(!rpm || !redis_sock){ return FAILURE; } /* send SET command */ session = redis_session_key(rpm, key, strlen(key), &session_len); cmd_len = redis_cmd_format_static(&cmd, "SETEX", "sds", session, session_len, INI_INT("session.gc_maxlifetime"), val, vallen); efree(session); if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); return FAILURE; } efree(cmd); /* read response */ if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { return FAILURE; } if(response_len == 3 && strncmp(response, "+OK", 3) == 0) { efree(response); return SUCCESS; } else { efree(response); return FAILURE; } } /* }}} */ /* {{{ PS_DESTROY_FUNC */ PS_DESTROY_FUNC(redis) { char *cmd, *response, *session; int cmd_len, response_len, session_len; redis_pool *pool = PS_GET_MOD_DATA(); redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC); RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; if(!rpm || !redis_sock){ return FAILURE; } /* send DEL command */ session = redis_session_key(rpm, key, strlen(key), &session_len); cmd_len = redis_cmd_format_static(&cmd, "DEL", "s", session, session_len); efree(session); if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); return FAILURE; } efree(cmd); /* read response */ if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { return FAILURE; } if(response_len == 2 && response[0] == ':' && (response[1] == '0' || response[1] == '1')) { efree(response); return SUCCESS; } else { efree(response); return FAILURE; } } /* }}} */ /* {{{ PS_GC_FUNC */ PS_GC_FUNC(redis) { return SUCCESS; } /* }}} */ #endif /* vim: set tabstop=4 expandtab: */ #ifndef REDIS_SESSION_H #define REDIS_SESSION_H #ifdef PHP_SESSION #include "ext/session/php_session.h" PS_OPEN_FUNC(redis); PS_CLOSE_FUNC(redis); PS_READ_FUNC(redis); PS_WRITE_FUNC(redis); PS_DESTROY_FUNC(redis); PS_GC_FUNC(redis); #endif #endif -EjPٳuquR-���GBMB