PageRenderTime 55ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/hphp/system/php/redis/Redis.php

http://github.com/facebook/hiphop-php
PHP | 1768 lines | 1510 code | 149 blank | 109 comment | 205 complexity | b50ab37021e1c5912e0763660b16aa92 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-2-Clause, BSD-3-Clause, MPL-2.0-no-copyleft-exception, MIT, LGPL-2.0, Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. class Redis {
  3. /* Redis servers run here by default */
  4. const DEFAULT_PORT = 6379;
  5. /* Return values from Redis::type() */
  6. const REDIS_NOT_FOUND = 0;
  7. const REDIS_STRING = 1;
  8. const REDIS_SET = 2;
  9. const REDIS_LIST = 3;
  10. const REDIS_ZSET = 4;
  11. const REDIS_HASH = 5;
  12. /* Operational modes
  13. *
  14. * In ATOMIC mode, we wait for the server to
  15. * respond to each request and return the value
  16. * directly.
  17. *
  18. * In MULTI mode (activated by calling Redis::multi())
  19. * we send commands immediately. but they are held in
  20. * a transaction by the server. Only upon calling
  21. * Redis::exec() is the transaction committed, and the
  22. * results returned.
  23. *
  24. * In PIPELINE mode (activated by calling Redis::pipeline())
  25. * we queue all commands locally until invoking Redis::exec()
  26. * at which point they are sent to the server in a single batch.
  27. * And all results are packaged back in a single batch.
  28. *
  29. * In both MULTI and PIPELINE modes, pending commands may be
  30. * discarded by calling Redis::discard()
  31. * The return value for both MULTI and PIPELINE for most commands
  32. * is the object itself, meaning fluent calling may be used.
  33. */
  34. const ATOMIC = 0;
  35. const MULTI = 1;
  36. const PIPELINE = 2;
  37. /* Options to Redis::setOption() and Redis::getOption() */
  38. const OPT_SERIALIZER = 1;
  39. const OPT_PREFIX = 2;
  40. const OPT_READ_TIMEOUT = 3;
  41. const OPT_SCAN = 4;
  42. /* Type of serialization to use with values stored in redis */
  43. const SERIALIZER_NONE = 0;
  44. const SERIALIZER_PHP = 1;
  45. /* Options used by lInsert and similar methods */
  46. const AFTER = 'after';
  47. const BEFORE = 'before';
  48. /* Scan retry settings. We essentially always retry, so this is
  49. just for PHP 5 compatibility. */
  50. const SCAN_RETRY = 0;
  51. const SCAN_NORETRY = 1;
  52. /* Connection ---------------------------------------------------------- */
  53. public function connect($host,
  54. $port = -1,
  55. $timeout = 0.0,
  56. $persistent_id = '',
  57. $retry_interval = 0) {
  58. return $this->doConnect($host, $port, $timeout, $persistent_id,
  59. $retry_interval, false);
  60. }
  61. public function pconnect($host,
  62. $port = -1,
  63. $timeout = 0.0,
  64. $persistent_id = '',
  65. $retry_interval = 0) {
  66. return $this->doConnect($host, $port, $timeout, $persistent_id,
  67. $retry_interval, true);
  68. }
  69. public function auth($password) {
  70. $this->password = $password;
  71. $this->processCommand('AUTH', $password);
  72. return $this->processBooleanResponse();
  73. }
  74. public function close() {
  75. $this->processCommand('QUIT');
  76. fclose($this->connection);
  77. $this->connection = null;
  78. }
  79. public function select($dbNumber) {
  80. $this->dbNumber = (int)$dbNumber;
  81. $this->processCommand("SELECT", (int)$dbNumber);
  82. return $this->processBooleanResponse();
  83. }
  84. public function setOption($opt, $value) {
  85. switch ($opt) {
  86. case self::OPT_PREFIX:
  87. $this->prefix = $value;
  88. return true;
  89. case self::OPT_SERIALIZER:
  90. if (($value !== self::SERIALIZER_NONE) &&
  91. ($value !== self::SERIALIZER_PHP)) {
  92. throw new RedisException("Invalid serializer option: $value");
  93. }
  94. $this->serializer = (int)$value;
  95. return true;
  96. case self::OPT_READ_TIMEOUT:
  97. $this->timeout_seconds = (int)floor($value);
  98. $this->timeout_useconds =
  99. (int)(($value - $this->timeout_seconds) * 1000000);
  100. return stream_set_timeout($this->connection, $this->timeout_seconds,
  101. $this->timeout_useconds);
  102. default:
  103. return false;
  104. }
  105. }
  106. public function getOption($opt) {
  107. switch ($opt) {
  108. case self::OPT_PREFIX: return $this->prefix;
  109. case self::OPT_SERIALIZER: return $this->serializer;
  110. case self::OPT_READ_TIMEOUT:
  111. return $this->timeout_seconds + ($this->timeout_useconds / 1000000);
  112. }
  113. return false;
  114. }
  115. /* Server -------------------------------------------------------------- */
  116. public function config($op, $key, $val = '') {
  117. if ($op == 'GET') {
  118. $this->processCommand('CONFIG', 'GET', $key);
  119. return $this->processMapResponse(false, false);
  120. }
  121. if ($op == 'SET') {
  122. $this->processCommand('CONFIG', 'SET', $key, $val);
  123. return $this->processBooleanResponse();
  124. }
  125. throw new RedisException('First arg must be GET or SET');
  126. }
  127. public function info($option = '') {
  128. if ($option) {
  129. $this->processCommand('INFO', $option);
  130. } else {
  131. $this->processCommand('INFO');
  132. }
  133. return $this->processInfoResponse();
  134. }
  135. public function resetStat() {
  136. $this->processCommand('CONFIG', 'RESETSTAT');
  137. return $this->processBooleanResponse();
  138. }
  139. public function slaveOf($host = '', $port = -1) {
  140. if ($host) {
  141. if ($port <= 0) {
  142. $port = self::DEFAULT_PORT;
  143. }
  144. $this->processCommand('SLAVEOF', $host, (int)$port);
  145. } else {
  146. $this->processCommand('SLAVEOF', 'NO', 'ONE');
  147. }
  148. return $this->processBooleanResponse();
  149. }
  150. public function client($cmd, $arg = '') {
  151. $cmd = strtolower($cmd);
  152. if (func_num_args() == 2) {
  153. $this->processCommand('CLIENT', $cmd, $arg);
  154. } else {
  155. $this->processCommand('CLIENT', $cmd);
  156. }
  157. if ($cmd == 'list') {
  158. return $this->processClientListResponse();
  159. }
  160. return $this->processVariantResponse();
  161. }
  162. /* Strings ------------------------------------------------------------- */
  163. public function decr($key, $by = 1) {
  164. if ($by !== 1) {
  165. return $this->decrBy($key, $by);
  166. }
  167. $this->processCommand("DECR", $this->_prefix($key));
  168. return $this->processLongResponse();
  169. }
  170. public function decrBy($key, $by) {
  171. if ($by === 1) {
  172. return $this->decr($key);
  173. }
  174. $this->processCommand("DECRBY", $this->_prefix($key), (int)$by);
  175. return $this->processLongResponse();
  176. }
  177. public function incr($key, $by = 1) {
  178. if ($by !== 1) {
  179. return $this->incrBy($key, $by);
  180. }
  181. $this->processCommand("INCR", $this->_prefix($key));
  182. return $this->processLongResponse();
  183. }
  184. public function incrBy($key, $by) {
  185. if ($by === 1) {
  186. return $this->incr($key);
  187. }
  188. $this->processCommand("INCRBY", $this->_prefix($key), (int)$by);
  189. return $this->processLongResponse();
  190. }
  191. public function incrByFloat($key, $by) {
  192. $this->processCommand("INCRBYFLOAT", $this->_prefix($key),
  193. (float)$by);
  194. return $this->processDoubleResponse();
  195. }
  196. public function set($key, $value, $optionArrayOrExpiration = -1) {
  197. $key = $this->_prefix($key);
  198. $value = $this->_serialize($value);
  199. if (is_array($optionArrayOrExpiration) &&
  200. count($optionArrayOrExpiration) > 0) {
  201. $ex = array_key_exists('ex', $optionArrayOrExpiration);
  202. $px = array_key_exists('px', $optionArrayOrExpiration);
  203. $nx = in_array('nx', $optionArrayOrExpiration, true);
  204. $xx = in_array('xx', $optionArrayOrExpiration, true);
  205. if ($nx && $xx) {
  206. throw new RedisException(
  207. "Invalid set options: nx and xx may not be specified at the same time"
  208. );
  209. }
  210. $args = [
  211. $key,
  212. $value
  213. ];
  214. if ($px) {
  215. $args[] = "px";
  216. $args[] = $optionArrayOrExpiration['px'];
  217. } else if($ex) {
  218. $args[] = "ex";
  219. $args[] = $optionArrayOrExpiration['ex'];
  220. }
  221. if ($nx) {
  222. $args[] = "nx";
  223. } else if ($xx) {
  224. $args[] = "xx";
  225. }
  226. $this->processArrayCommand("SET", $args);
  227. } else if (is_numeric($optionArrayOrExpiration) &&
  228. (int)$optionArrayOrExpiration > 0) {
  229. $this->processCommand("SETEX", $key, $optionArrayOrExpiration, $value);
  230. } else {
  231. $this->processCommand("SET", $key, $value);
  232. }
  233. return $this->processBooleanResponse();
  234. }
  235. /* Keys ---------------------------------------------------------------- */
  236. public function sort($key, array $arr = null) {
  237. $args = $this->sortClause($arr, $using_store);
  238. array_unshift($args, $key);
  239. $this->processArrayCommand('SORT', $args);
  240. if ($using_store) {
  241. return $this->processVectorResponse(true);
  242. }
  243. return $this->processLongResponse();
  244. }
  245. public function sortAsc($key,
  246. $pattern = null,
  247. $get = null,
  248. $start = -1,
  249. $count = -1,
  250. $store = null) {
  251. $limit = (($start > 0) AND ($count > 0)) ? [$start, $count] : null;
  252. return $this->sort($key, [
  253. 'by' => $pattern,
  254. 'get' => $get,
  255. 'limit' => $limit,
  256. 'store' => $store,
  257. 'dir' => 'ASC',
  258. ]);
  259. }
  260. public function sortAscAlpha($key,
  261. $pattern = null,
  262. $get = null,
  263. $start = -1,
  264. $count = -1,
  265. $store = null) {
  266. $limit = (($start > 0) AND ($count > 0)) ? [$start, $count] : null;
  267. return $this->sort($key, [
  268. 'by' => $pattern,
  269. 'get' => $get,
  270. 'limit' => $limit,
  271. 'store' => $store,
  272. 'dir' => 'ASC',
  273. 'alpha' => true,
  274. ]);
  275. }
  276. public function sortDesc($key,
  277. $pattern = null,
  278. $get = null,
  279. $start = -1,
  280. $count = -1,
  281. $store = null) {
  282. $limit = (($start > 0) AND ($count > 0)) ? [$start, $count] : null;
  283. return $this->sort($key, [
  284. 'by' => $pattern,
  285. 'get' => $get,
  286. 'limit' => $limit,
  287. 'store' => $store,
  288. 'dir' => 'DESC',
  289. ]);
  290. }
  291. public function sortDescAlpha($key,
  292. $pattern = null,
  293. $get = null,
  294. $start = -1,
  295. $count = -1,
  296. $store = null) {
  297. $limit = (($start > 0) AND ($count > 0)) ? [$start, $count] : null;
  298. return $this->sort($key, [
  299. 'by' => $pattern,
  300. 'get' => $get,
  301. 'limit' => $limit,
  302. 'store' => $store,
  303. 'dir' => 'DESC',
  304. 'alpha' => true,
  305. ]);
  306. }
  307. public function object($info, $key) {
  308. $this->processCommand('OBJECT', $info, $this->_prefix($key));
  309. switch ($info) {
  310. case 'refcount': return $this->processLongResponse();
  311. case 'encoding': return $this->processStringResponse();
  312. default: return $this->processBooleanResponse();
  313. }
  314. }
  315. /* Hashes -------------------------------------------------------------- */
  316. public function hMGet($key, array $members) {
  317. $members = array_values($members);
  318. $args = array_merge([$this->_prefix($key)], $members);
  319. $this->processArrayCommand('HMGET', $args);
  320. return $this->processAssocResponse($members);
  321. }
  322. public function hMSet($key, array $pairs) {
  323. $args = [$this->_prefix($key)];
  324. foreach ($pairs as $k => $v) {
  325. $args[] = $k;
  326. $args[] = $this->_serialize($v);
  327. }
  328. $this->processArrayCommand('HMSET', $args);
  329. return $this->processBooleanResponse();
  330. }
  331. /* Sets ---------------------------------------------------------------- */
  332. public function sRandMember($key, $count = null) {
  333. $args = [$this->_prefix($key)];
  334. if ($count !== null) {
  335. $args[] = $count;
  336. }
  337. $this->processArrayCommand('SRANDMEMBER', $args);
  338. if ($count !== null) {
  339. return $this->processVectorResponse(true);
  340. }
  341. return $this->processStringResponse();
  342. }
  343. /* zSets --------------------------------------------------------------- */
  344. public function zAdd($key, $score, $value/*, $scoreN, $valueN */) {
  345. $args = func_get_args();
  346. $count = count($args);
  347. if (($count - 1) % 2) {
  348. return false;
  349. }
  350. $args[0] = $this->_prefix($args[0]);
  351. for ($i = 1; $i < $count; $i += 2) {
  352. $args[$i ] = (double)$args[$i];
  353. $args[$i+1] = $this->_serialize($args[$i+1]);
  354. }
  355. $this->processArrayCommand('ZADD', $args);
  356. return $this->processLongResponse();
  357. }
  358. protected function zInterUnionStore($cmd,
  359. $key,
  360. array $keys,
  361. array $weights = null,
  362. $op = '') {
  363. $args = [ $this->_prefix($key), count($keys) ];
  364. foreach ($keys as $k) {
  365. $args[] = $this->_prefix($k);
  366. }
  367. if ($weights) {
  368. $args[] = 'WEIGHTS';
  369. foreach ($weights as $weight) {
  370. if (is_int($weight) OR
  371. is_float($weight) OR
  372. ($weight === 'inf') OR
  373. ($weight === '-inf') OR
  374. ($weight === '+inf')) {
  375. $args[] = $weight;
  376. }
  377. }
  378. }
  379. if ($op) {
  380. $args[] = 'AGGREGATE';
  381. $args[] = $op;
  382. }
  383. $this->processArrayCommand($cmd, $args);
  384. return $this->processLongResponse();
  385. }
  386. public function zInterStore($key,
  387. array $keys,
  388. array $weights = null,
  389. $op = '') {
  390. return $this->zInterUnionStore('ZINTERSTORE', $key, $keys, $weights, $op);
  391. }
  392. public function zUnionStore($key,
  393. array $keys,
  394. array $weights = null,
  395. $op = '') {
  396. return $this->zInterUnionStore('ZUNIONSTORE', $key, $keys, $weights, $op);
  397. }
  398. public function zRange($key, $start, $end, $withscores = false) {
  399. $args = [
  400. $this->_prefix($key),
  401. (int)$start,
  402. (int)$end,
  403. ];
  404. if ($withscores) {
  405. $args[] = 'WITHSCORES';
  406. }
  407. $this->processArrayCommand('ZRANGE', $args);
  408. if ($withscores) {
  409. return $this->processMapResponse(true, false);
  410. }
  411. return $this->processVectorResponse(true);
  412. }
  413. protected function zRangeByScoreImpl($cmd,
  414. $key,
  415. $start,
  416. $end,
  417. array $opts = null) {
  418. $args = [$this->_prefix($key), $start, $end];
  419. if (isset($opts['limit']) AND
  420. is_array($opts['limit']) AND
  421. (count($opts['limit']) == 2)) {
  422. list($limit_start, $limit_end) = $opts['limit'];
  423. $args[] = 'LIMIT';
  424. $args[] = $limit_start;
  425. $args[] = $limit_end;
  426. }
  427. if (!empty($opts['withscores'])) {
  428. $args[] = 'WITHSCORES';
  429. }
  430. $this->processArrayCommand($cmd, $args);
  431. if (!empty($opts['withscores'])) {
  432. return $this->processMapResponse(true, false);
  433. }
  434. return $this->processVectorResponse(true);
  435. }
  436. public function zRangeByScore($key, $start, $end, array $opts = null) {
  437. return $this->zRangeByScoreImpl('ZRANGEBYSCORE',
  438. $key, $start, $end, $opts);
  439. }
  440. public function zRevRangeByScore($key, $start, $end, array $opts = null) {
  441. return $this->zRangeByScoreImpl('ZREVRANGEBYSCORE',
  442. $key, $start, $end, $opts);
  443. }
  444. public function zRevRange($key, $start, $end, $withscores = false) {
  445. $args = [
  446. $this->_prefix($key),
  447. (int)$start,
  448. (int)$end,
  449. ];
  450. if ($withscores) {
  451. $args[] = 'WITHSCORES';
  452. }
  453. $this->processArrayCommand('ZREVRANGE', $args);
  454. if ($withscores) {
  455. return $this->processMapResponse(true, false);
  456. }
  457. return $this->processVectorResponse(true);
  458. }
  459. /* Scan --------------------------------------------------------------- */
  460. protected function scanImpl($cmd, $key, &$cursor, $pattern, $count) {
  461. if ($this->mode != self::ATOMIC) {
  462. throw new RedisException("Can't call SCAN commands in multi or pipeline mode!");
  463. }
  464. $results = false;
  465. do {
  466. if ($cursor === 0) return $results;
  467. $args = [];
  468. if ($cmd !== 'SCAN') {
  469. $args[] = $this->_prefix($key);
  470. }
  471. $args[] = (int)$cursor;
  472. if ($pattern !== null) {
  473. $args[] = 'MATCH';
  474. $args[] = (string)$pattern;
  475. }
  476. if ($count !== null) {
  477. $args[] = 'COUNT';
  478. $args[] = (int)$count;
  479. }
  480. $this->processArrayCommand($cmd, $args);
  481. $resp = $this->processVariantResponse();
  482. if (!is_array($resp) || count($resp) != 2 || !is_array($resp[1])) {
  483. throw new RedisException(
  484. sprintf("Invalid %s response: %s", $cmd, print_r($resp, true)));
  485. }
  486. $cursor = (int)$resp[0];
  487. $results = $resp[1];
  488. // Provide SCAN_RETRY semantics by default. If iteration is done and
  489. // there were no results, $cursor === 0 check at the top of the loop
  490. // will pop us out.
  491. } while(count($results) == 0);
  492. return $results;
  493. }
  494. public function scan(&$cursor, $pattern = null, $count = null) {
  495. return $this->scanImpl('SCAN', null, $cursor, $pattern, $count);
  496. }
  497. public function sScan($key, &$cursor, $pattern = null, $count = null) {
  498. return $this->scanImpl('SSCAN', $key, $cursor, $pattern, $count);
  499. }
  500. public function hScan($key, &$cursor, $pattern = null, $count = null) {
  501. return $this->scanImpl('HSCAN', $key, $cursor, $pattern, $count);
  502. }
  503. public function zScan($key, &$cursor, $pattern = null, $count = null) {
  504. $flat = $this->scanImpl('ZSCAN', $key, $cursor, $pattern, $count);
  505. if ($flat === false) return $flat;
  506. /*
  507. * ZScan behaves differently from the other *scan functions. The wire
  508. * protocol returns names in even slots s, and the corresponding value
  509. * in odd slot s + 1. The PHP client returns these as an array mapping
  510. * keys to values.
  511. */
  512. assert(count($flat) % 2 == 0);
  513. $ret = array();
  514. for ($i = 0; $i < count($flat); $i += 2) $ret[$flat[$i]] = $flat[$i + 1];
  515. return $ret;
  516. }
  517. /* Multi --------------------------------------------------------------- */
  518. protected function flushCallbacks($multibulk = true) {
  519. if ($multibulk) $this->sockReadData($type); // Response Count
  520. $ret = [];
  521. foreach ($this->multiHandler as $callback) {
  522. $args = isset($callback['args']) ? $callback['args'] : [];
  523. $ret[] = call_user_func_array($callback['cb'], $args);
  524. }
  525. $this->multiHandler = [];
  526. return $ret;
  527. }
  528. public function multi($mode = self::MULTI) {
  529. if ($mode === self::PIPELINE) {
  530. return $this->pipeline();
  531. }
  532. if ($mode !== self::MULTI) {
  533. return false;
  534. }
  535. $this->discard();
  536. $this->processCommand('MULTI');
  537. $resp = $this->sockReadData($type);
  538. if (($type === self::TYPE_LINE) AND ($resp === 'OK')) {
  539. $this->mode = self::MULTI;
  540. return $this;
  541. }
  542. return false;
  543. }
  544. public function exec() {
  545. if ($this->mode === self::MULTI) {
  546. $this->mode = self::ATOMIC;
  547. $this->processCommand('EXEC');
  548. return $this->flushCallbacks();
  549. }
  550. if ($this->mode === self::PIPELINE) {
  551. $this->mode = self::ATOMIC;
  552. foreach ($this->commands as $cmd) {
  553. $this->processArrayCommand($cmd['cmd'], $cmd['args']);
  554. }
  555. $this->commands = [];
  556. return $this->flushCallbacks(false);
  557. }
  558. }
  559. public function discard() {
  560. $discard = ($this->mode === self::MULTI);
  561. $this->mode = self::ATOMIC;
  562. $this->commands = [];
  563. $this->multiHandler = [];
  564. if ($discard) {
  565. $this->processCommand('DISCARD');
  566. return $this->process1Response();
  567. }
  568. return true;
  569. }
  570. public function pipeline() {
  571. $this->discard();
  572. $this->mode = self::PIPELINE;
  573. return $this;
  574. }
  575. public function watch($key/* ... */) {
  576. $args = array_map([$this, '_prefix'], func_get_args());
  577. $this->processArrayCommand("WATCH", $args);
  578. return $this->processBooleanResponse();
  579. }
  580. public function unwatch() {
  581. $this->processCommand("UNWATCH");
  582. return $this->processBooleanResponse();
  583. }
  584. /* Batch --------------------------------------------------------------- */
  585. protected function processMSetCommand($cmd, array $data) {
  586. $args = [];
  587. foreach ($data as $key => $val) {
  588. $args[] = $this->_prefix($key);
  589. $args[] = $this->_serialize($val);
  590. }
  591. $this->processArrayCommand($cmd, $args);
  592. }
  593. public function mSet(array $data) {
  594. $this->processMSetCommand('MSET', $data);
  595. return $this->processBooleanResponse();
  596. }
  597. public function mSetNx(array $data) {
  598. $this->processMSetCommand('MSETNX', $data);
  599. return $this->process1Response();
  600. }
  601. /* Scripting ----------------------------------------------------------- */
  602. protected function doEval($cmd, $script, array $args, $numKeys) {
  603. $keyCount = $numKeys;
  604. foreach($args as &$arg) {
  605. if ($keyCount-- <= 0) break;
  606. $arg = $this->_prefix($arg);
  607. }
  608. array_unshift($args, $numKeys);
  609. array_unshift($args, $script);
  610. $this->processArrayCommand($cmd, $args);
  611. $response = $this->processVariantResponse();
  612. return ($response !== NULL) ? $response : false;
  613. }
  614. public function evaluate($script, array $args = [], $numKeys = 0) {
  615. return $this->doEval('EVAL', $script, $args, $numKeys);
  616. }
  617. public function eval($script, array $args = [], $numKeys = 0) {
  618. return $this->doEval('EVAL', $script, $args, $numKeys);
  619. }
  620. public function evaluateSha($sha, array $args = [], $numKeys = 0) {
  621. return $this->doEval('EVALSHA', $sha, $args, $numKeys);
  622. }
  623. public function evalSha($sha, array $args = [], $numKeys = 0) {
  624. return $this->doEval('EVALSHA', $sha, $args, $numKeys);
  625. }
  626. public function script($subcmd/* ... */) {
  627. switch (strtolower($subcmd)) {
  628. case 'flush':
  629. case 'kill':
  630. $this->processCommand('SCRIPT', $subcmd);
  631. $response = $this->processVariantResponse();
  632. return ($response !== NULL) ? true : false;
  633. case 'load':
  634. if (func_num_args() < 2) {
  635. return false;
  636. }
  637. $script = func_get_arg(1);
  638. if (!is_string($script) OR empty($script)) {
  639. return false;
  640. }
  641. $this->processCommand('SCRIPT', 'load', $script);
  642. $response = $this->processVariantResponse();
  643. return ($response !== NULL) ? $response : false;
  644. case 'exists':
  645. $args = func_get_args();
  646. $args[0] = 'EXISTS';
  647. $this->processArrayCommand('SCRIPT', $args);
  648. return $this->processVariantResponse();
  649. default:
  650. return false;
  651. }
  652. }
  653. /* Introspection ------------------------------------------------------- */
  654. public function isConnected() {
  655. return $this->checkConnection(false);
  656. }
  657. public function getHost() {
  658. return $this->host;
  659. }
  660. public function getPort() {
  661. return $this->port;
  662. }
  663. public function getDBNum() {
  664. return $this->dbNumber;
  665. }
  666. public function getTimeout() {
  667. return $this->timeout_connect;
  668. }
  669. public function getReadTimeout() {
  670. return $this->getOption(self::OPT_READ_TIMEOUT);
  671. }
  672. public function getPersistentId() {
  673. throw new RedisException('Named persistent connections are '.
  674. 'not supported.');
  675. }
  676. public function getPassword() {
  677. return $this->password;
  678. }
  679. public function getLastError() {
  680. return $this->lastError;
  681. }
  682. public function clearLastError() {
  683. $this->lastError = null;
  684. return true;
  685. }
  686. /* Standard Function Map ----------------------------------------------- */
  687. /**
  688. * The majority of the Redis API is implemented by __call
  689. * which references this list for how the individual command
  690. * should be handled.
  691. *
  692. * By default the name of the method (key in this array)
  693. * is uppercased to become the actual command sent to the redis server.
  694. *
  695. * Example: 'get' becomes the Redis command `GET`
  696. *
  697. * This mapping may be overridden by adding a 'cmd' element such as:
  698. * 'myget' => [ 'cmd' => 'GET' ],
  699. *
  700. * The argument spec is defined by the 'format' subparameter with each
  701. * position in the string specifying what type of param it is.
  702. * 's' => Ordinary string to be passed trough to the server unmodified
  703. * 'k' => The name of a key. Prefixed with $this->prefix.
  704. * 'v' => A piece of user data. Serialized according to $this->serialize.
  705. * 'l' => An integer(long). Explicitly cast from whatever is passed.
  706. * 'd' => A float(double). Explicitly cast from whatever is passed.
  707. * 'b' => A boolean. Explicitly cast from whatever is passed.
  708. * 'p' => Pivot point (self::BEFORE or self::AFTER). Validated.
  709. *
  710. * In lieu of 'format', a mapping may specify 'vararg' for variadic methods.
  711. * The value must be a bitmask of the VAR_* constants.
  712. * See Redis::translateVarArgs()
  713. *
  714. * The method (on this class) called to handle the response is named by the
  715. * 'handler' field. A shortcut for this is the 'return' field which will
  716. * be mapped into 'handler' as: 'process{$return}Response'
  717. * To pass arguments to the handler, use 'retargs'.
  718. *
  719. * Lastly, the 'alias' field (given by itself), will map calls from one
  720. * function directly to another. If the target method actually exists,
  721. * the fcall will be proxied through call_user_func_array(). If the target
  722. * is elsewhere in the map, __call's state will be reset to use the new
  723. * map element.
  724. */
  725. protected static $map = [
  726. // Connection
  727. 'open' => [ 'alias' => 'connect' ],
  728. 'popen' => [ 'alias' => 'pconnect' ],
  729. 'ping' => [ 'return' => 'Raw' ],
  730. 'echo' => [ 'format' => 's', 'return' => 'String' ],
  731. // Server
  732. 'bgrewriteaof' => [ 'return' => 'Boolean' ],
  733. 'bgsave' => [ 'return' => 'Boolean' ],
  734. 'dbsize' => [ 'return' => 'Long' ],
  735. 'flushall' => [ 'return' => 'Boolean' ],
  736. 'flushdb' => [ 'return' => 'Boolean' ],
  737. 'lastsave' => [ 'return' => 'Long' ],
  738. 'save' => [ 'return' => 'Boolean' ],
  739. 'time' => [ 'return' => 'Vector' ],
  740. // Strings
  741. 'append' => [ 'format' => 'kv', 'return' => 'Long' ],
  742. 'bitcount' => [ 'format' => 'kll', 'return' => 'Long' ],
  743. 'bitop' => [ 'vararg' => self::VAR_KEY_NOT_FIRST, 'return' => 'Long' ],
  744. 'get' => [ 'format' => 'k', 'return' => 'Serialized' ],
  745. 'getbit' => [ 'format' => 'kl', 'return' => 'Long' ],
  746. 'getrange' => [ 'format' => 'kll', 'return' => 'String', 'cmd' => 'RANGE' ],
  747. 'getset' => [ 'format' => 'kv', 'return' => 'Serialized' ],
  748. 'setbit' => [ 'format' => 'klv', 'return' => 'Long' ],
  749. 'setex' => [ 'format' => 'klv', 'return' => 'Boolean' ],
  750. 'psetex' => [ 'format' => 'klv', 'return' => 'Boolean' ],
  751. 'setnx' => [ 'format' => 'kv', 'return' => '1' ],
  752. 'setrange' => [ 'format' => 'klv', 'return' => 'Long' ],
  753. 'strlen' => [ 'format' => 'k', 'return' => 'Long' ],
  754. // Keys
  755. 'del' => [ 'vararg' => self::VAR_KEY_ALL, 'return' => 'Long' ],
  756. 'delete' => [ 'alias' => 'del' ],
  757. 'dump' => [ 'format' => 'k', 'return' => 'Raw' ],
  758. 'exists' => [ 'format' => 'k', 'return' => '1' ],
  759. 'expire' => [ 'format' => 'kl', 'return' => '1' ],
  760. 'settimeout' => [ 'alias' => 'expire' ],
  761. 'pexpire' => [ 'format' => 'kl', 'return' => '1' ],
  762. 'expireat' => [ 'format' => 'kl', 'return' => '1' ],
  763. 'pexpireat' => [ 'format' => 'kl', 'return' => '1' ],
  764. 'keys' => [ 'format' => 's', 'return' => 'Vector' ],
  765. 'getkeys' => [ 'alias' => 'keys' ],
  766. 'migrate' => [ 'format' => 'slkll', 'return' => 'Boolean' ],
  767. 'move' => [ 'format' => 'kl', 'return' => '1' ],
  768. 'persist' => [ 'format' => 'k', 'return' => '1' ],
  769. 'randomkey' => [ 'return' => 'String' ],
  770. 'rename' => [ 'format' => 'kk', 'return' => 'Boolean' ],
  771. 'renamekey' => [ 'alias' => 'rename' ],
  772. 'renamenx' => [ 'format' => 'kk', 'return' => '1' ],
  773. 'type' => [ 'format' => 'k', 'return' => 'Type' ],
  774. 'ttl' => [ 'format' => 'k', 'return' => 'Long' ],
  775. 'pttl' => [ 'format' => 'k', 'return' => 'Long' ],
  776. 'restore' => [ 'format' => 'kls', 'return' => 'Boolean' ],
  777. //Geospatial
  778. 'geoadd' => [ 'format' => 'kdds', 'return' => 'Long' ],
  779. 'geodist' => [ 'vararg' => self::VAR_KEY_FIRST, 'return' => 'String' ],
  780. 'geohash' => [ 'vararg' => self::VAR_KEY_FIRST, 'return' => 'Vector' ],
  781. 'geopos' => [ 'vararg' => self::VAR_KEY_FIRST, 'return' => 'Variant' ],
  782. 'georadius' => [ 'vararg' => self::VAR_KEY_FIRST, 'return' => 'Variant' ],
  783. 'georadiusbymember' => [ 'vararg' => self::VAR_KEY_FIRST, 'return' => 'Variant' ],
  784. // Hashes
  785. 'hdel' => [ 'vararg' => self::VAR_KEY_FIRST, 'return' => 'Long' ],
  786. 'hexists' => [ 'format' => 'ks', 'return' => '1' ],
  787. 'hget' => [ 'format' => 'ks', 'return' => 'Serialized' ],
  788. 'hgetall' => [ 'format' => 'k', 'return' => 'Map',
  789. 'retargs' => [false,true] ],
  790. 'hincrby' => [ 'format' => 'ksl', 'return' => 'Long' ],
  791. 'hincrbyfloat' => [ 'format' => 'ksd', 'return' => 'Double' ],
  792. 'hkeys' => [ 'format' => 'k', 'return' => 'Vector' ],
  793. 'hlen' => [ 'format' => 'k', 'return' => 'Long' ],
  794. 'hset' => [ 'format' => 'ksv', 'return' => 'Long' ],
  795. 'hsetnx' => [ 'format' => 'ksv', 'return' => '1' ],
  796. 'hvals' => [ 'format' => 'k', 'return' => 'Vector', 'retargs' => [1] ],
  797. // Lists
  798. 'blpop' => [ 'vararg' => self::VAR_KEY_ALL_AND_TIMEOUT,
  799. 'return' => 'Vector', 'retargs' => [1] ],
  800. 'brpop' => [ 'vararg' => self::VAR_KEY_ALL_AND_TIMEOUT,
  801. 'return' => 'Vector', 'retargs' => [1] ],
  802. 'brpoplpush' => [ 'format' => 'kkl', 'return' => 'Serialized' ],
  803. 'lindex' => [ 'format' => 'kl', 'return' => 'Serialized' ],
  804. 'lget' => [ 'alias' => 'lindex' ],
  805. 'linsert' => [ 'format' => 'kpkv', 'return' => 'Long' ],
  806. 'llen' => [ 'format' => 'k', 'return' => 'Long', 'cmd' => 'LLEN' ],
  807. 'lsize' => [ 'alias' => 'llen' ],
  808. 'lpop' => [ 'format' => 'k', 'return' => 'Serialized' ],
  809. 'lpush' => [ 'vararg' => self::VAR_KEY_FIRST_AND_SERIALIZE,
  810. 'return' => 'Long' ],
  811. 'lpushx' => [ 'format' => 'kl', 'return' => 'Long' ],
  812. 'lrange' => [ 'format' => 'kll', 'return' => 'Vector', 'retargs' => [1] ],
  813. 'lgetrange' => [ 'alias' => 'lrange' ],
  814. 'lrem' => [ 'format' => 'kvs', 'return' => 'Long' ],
  815. 'lremove' => [ 'alias' => 'lrem' ],
  816. 'lset' => [ 'format' => 'klv', 'return' => 'Boolean' ],
  817. 'ltrim' => [ 'format' => 'kll', 'return' => 'Boolean' ],
  818. 'listtrim' => [ 'alias' => 'ltrim' ],
  819. 'rpop' => [ 'format' => 'k', 'return' => 'Serialized' ],
  820. 'rpoplpush' => [ 'format' => 'kk', 'return' => 'Serialized' ],
  821. 'rpush' => [ 'vararg' => self::VAR_KEY_FIRST_AND_SERIALIZE,
  822. 'return' => 'Long' ],
  823. 'rpushx' => [ 'format' => 'kl', 'return' => 'Long' ],
  824. // Sets
  825. 'sadd' => [ 'vararg' => self::VAR_KEY_FIRST_AND_SERIALIZE,
  826. 'return' => 'Long' ],
  827. 'scard' => [ 'format' => 'k', 'return' => 'Long' ],
  828. 'ssize' => [ 'alias' => 'scard' ],
  829. 'sdiff' => [ 'vararg' => self::VAR_KEY_ALL, 'return' => 'Vector' ],
  830. 'sdiffstore' => [ 'vararg' => self::VAR_KEY_ALL, 'return' => 'Long' ],
  831. 'sinter' => [ 'vararg' => self::VAR_KEY_ALL, 'return' => 'Vector' ],
  832. 'sinterstore' => [ 'vararg' => self::VAR_KEY_ALL, 'return' => 'Long' ],
  833. 'sismember' => [ 'format' => 'kv', 'return' => '1' ],
  834. 'scontains' => [ 'alias' => 'sismember' ],
  835. 'smembers' => [ 'format' => 'k', 'return' => 'Vector' ],
  836. 'sgetmembers' => [ 'alias' => 'smembers' ],
  837. 'smove' => [ 'format' => 'kkv', 'return' => '1' ],
  838. 'spop' => [ 'format' => 'k', 'return' => 'Serialized' ],
  839. 'srem' => [ 'vararg' => self::VAR_KEY_FIRST_AND_SERIALIZE,
  840. 'return' => 'Long' ],
  841. 'sremove' => [ 'alias' => 'srem' ],
  842. 'sunion' => [ 'vararg' => self::VAR_KEY_ALL, 'return' => 'Vector' ],
  843. 'sunionstore' => [ 'vararg' => self::VAR_KEY_ALL, 'return' => 'Long' ],
  844. // zSets
  845. 'zcard' => [ 'format' => 'k', 'return' => 'Long' ],
  846. 'zsize' => [ 'alias' => 'zcard' ],
  847. 'zcount' => [ 'format' => 'kss', 'return' => 'Long' ],
  848. 'zincrby' => [ 'format' => 'kdv', 'return' => 'Double' ],
  849. 'zinter' => [ 'alias' => 'zinterstore' ],
  850. 'zunion' => [ 'alias' => 'zunionstore' ],
  851. 'zrank' => [ 'format' => 'kv', 'return' => 'Long' ],
  852. 'zrevrank' => [ 'format' => 'kv', 'return' => 'Long' ],
  853. 'zrem' => [ 'vararg' => self::VAR_KEY_FIRST_AND_SERIALIZE,
  854. 'return' => 'Long' ],
  855. 'zremove' => [ 'alias' => 'zrem' ],
  856. 'zdelete' => [ 'alias' => 'zrem' ],
  857. 'zremrangebyrank' => [ 'format' => 'kll', 'return' => 'Long' ],
  858. 'zdeleterangebyrank' => [ 'alias' => 'zremrangebyrank' ],
  859. 'zremrangebyscore' => [ 'format' => 'kll', 'return' => 'Long' ],
  860. 'zdeleterangebyscore' => [ 'alias' => 'zremrangebyscore' ],
  861. 'zreverserange' => [ 'alias' => 'zrevrange' ],
  862. 'zscore' => [ 'format' => 'kv', 'return' => 'Double' ],
  863. // Publish
  864. 'publish' => [ 'format' => 'kv', 'return' => 'Long' ],
  865. /* These APIs are listed as "subject to change", avoid for now */
  866. 'subscribe' => false,
  867. 'psubscribe' => false,
  868. 'unsubscribe' => false,
  869. 'punsubscribe' => false,
  870. // Batch Ops
  871. 'mget' => [ 'vararg' => self::VAR_KEY_ALL,
  872. 'return' => 'Vector', 'retargs' => [1] ],
  873. 'getmultiple' => [ 'alias' => 'mget' ],
  874. ];
  875. /* Internal Use Only beyond this point --------------------------------- */
  876. protected $host = '';
  877. protected $port = -1;
  878. protected $password = '';
  879. protected $dbNumber = 0;
  880. protected $last_connect = -1;
  881. protected $retry_interval = 0;
  882. protected $persistent = false;
  883. protected $connection = null;
  884. protected $lastError = null;
  885. protected $timeout_connect = 0;
  886. protected $timeout_seconds = 0;
  887. protected $timeout_useconds = 0;
  888. protected $mode = self::ATOMIC;
  889. protected $multiHandler = [];
  890. protected $commands = [];
  891. protected $prefix = '';
  892. protected $serializer = self::SERIALIZER_NONE;
  893. /* protocol ------------------------------------------------------------ */
  894. /* Internal use constants for var arg parsing */
  895. const VAR_KEY_NONE = 0;
  896. const VAR_KEY_FIRST = 1;
  897. const VAR_KEY_NOT_FIRST = 2;
  898. const VAR_KEY_ALL = 3;
  899. const VAR_KEY_MASK = 0x000F;
  900. const VAR_SERIALIZE = 0x0010;
  901. const VAR_TIMEOUT = 0x0020;
  902. const VAR_KEY_FIRST_AND_SERIALIZE = 0x0011;
  903. const VAR_KEY_ALL_AND_TIMEOUT = 0x0023;
  904. /* Returned by reference from Redis::sockReadData()
  905. * Depending on the type of data returned by the server
  906. */
  907. const TYPE_LINE = '+';
  908. const TYPE_INT = ':';
  909. const TYPE_ERR = '-';
  910. const TYPE_BULK = '$';
  911. const TYPE_MULTIBULK = '*';
  912. protected function checkConnection($auto_reconnect = true) {
  913. if (!$this->connection) {
  914. return false;
  915. }
  916. // Check if we have hit the stream timeout
  917. if (stream_get_meta_data($this->connection)['timed_out']) {
  918. throw new RedisException("read error on connection");
  919. }
  920. if (!feof($this->connection)) {
  921. // Connection seems fine
  922. return true;
  923. }
  924. if ((time() - $this->last_connect) < $this->retry_interval) {
  925. // We've tried connecting too recently, don't retry
  926. return false;
  927. }
  928. if ($auto_reconnect AND
  929. $this->doConnect($this->host, $this->port,
  930. $this->timeout_connect,
  931. null, $this->retry_interval,
  932. $this->persistent)) {
  933. if ($this->password) {
  934. $this->auth($this->password);
  935. }
  936. if ($this->dbNumber) {
  937. $this->select($this->dbNumber);
  938. }
  939. return true;
  940. }
  941. // Reconnect failed, give up
  942. return false;
  943. }
  944. protected function sockReadLine() {
  945. $line = '';
  946. do {
  947. if (!$this->checkConnection()) {
  948. return false;
  949. }
  950. $line .= fgets($this->connection);
  951. } while (substr($line, -2) !== "\r\n");
  952. return substr($line, 0, -2);
  953. }
  954. protected function sockReadData(&$type) {
  955. $line = $this->sockReadLine();
  956. if (strlen($line)) {
  957. $type = $line[0];
  958. $line = substr($line, 1);
  959. switch ($type) {
  960. case self::TYPE_ERR:
  961. if (!strncmp($line, '-ERR SYNC ', 10)) {
  962. throw new RedisException("Sync with master in progress");
  963. }
  964. return $line;
  965. case self::TYPE_INT:
  966. case self::TYPE_LINE:
  967. case self::TYPE_MULTIBULK: // Count of elements to follow
  968. return $line;
  969. case self::TYPE_BULK:
  970. $bytes = (int)$line;
  971. if ($bytes < 0) return null;
  972. $buf = '';
  973. while (strlen($buf) < ($bytes + 2)) {
  974. $buf .= fread($this->connection, ($bytes + 2) - strlen($buf));
  975. if (!$this->checkConnection()) {
  976. return null;
  977. }
  978. }
  979. return substr($buf, 0, -2);
  980. default:
  981. throw new RedisException("protocol error, got '{$type}' ".
  982. "as reply type byte");
  983. }
  984. }
  985. return null;
  986. }
  987. /**
  988. * Process arguments for variadic functions based on $flags
  989. *
  990. * Redis::VAR_TIMEOUT indicates that the last argument
  991. * in the list should be treated as an integer timeout
  992. * for the operation
  993. * Redis::VAR_KEY_* indicates which (NONE, FIRST, NOT_FIRST, ALL)
  994. * of the arguments (excluding TIMEOUT, as application)
  995. * should be treated as keys, and thus prefixed with Redis::$prefix
  996. * Redis::VAR_SERIALIZE indicates that all non-timeout/non-key
  997. * fields are data values, and should be serialzed
  998. * (if a serialzied is specified)
  999. */
  1000. protected function translateVarArgs(array $args, $flags) {
  1001. // Check alternate vararg schemes first
  1002. if (($flags & self::VAR_TIMEOUT) AND
  1003. (count($args) == 2) AND
  1004. (is_array($args[0])) AND
  1005. (is_int($args[1]))) {
  1006. $args = $args[0] + [$args[1]];
  1007. }
  1008. if ((!($flags & self::VAR_TIMEOUT)) AND
  1009. (count($args) == 1) AND
  1010. (is_array($args[0]))) {
  1011. $args = $args[0];
  1012. }
  1013. // Then prefix, serialie, and cast as needed
  1014. if ($flags & self::VAR_TIMEOUT) {
  1015. $timeout = array_pop($args);
  1016. }
  1017. if (($this->prefix AND ($flags & self::VAR_KEY_MASK)) OR
  1018. ($flags & self::VAR_SERIALIZE)) {
  1019. $first = true;
  1020. $varkey = $flags & self::VAR_KEY_MASK;
  1021. foreach($args as &$arg) {
  1022. if (( $first AND ($varkey == self::VAR_KEY_FIRST)) OR
  1023. (!$first AND ($varkey == self::VAR_KEY_NOT_FIRST)) OR
  1024. ($varkey == self::VAR_KEY_ALL)) {
  1025. $arg = $this->_prefix($arg);
  1026. } else if ($flags & self::VAR_SERIALIZE) {
  1027. $arg = $this->_serialize($arg);
  1028. }
  1029. $first = false;
  1030. }
  1031. }
  1032. if ($flags & self::VAR_TIMEOUT) {
  1033. $args[] = (int)$timeout;
  1034. }
  1035. return $args;
  1036. }
  1037. /**
  1038. * Actually send a command to the server.
  1039. * assumes all appropriate prefixing and serialization
  1040. * has been preformed by the caller and constructs
  1041. * a Redis Protocol packet in the form:
  1042. *
  1043. * *N\r\n
  1044. *
  1045. * Folled by N instances of:
  1046. *
  1047. * $L\r\nA
  1048. *
  1049. * Where L is the length in bytes of argument A.
  1050. *
  1051. * So for the command `GET somekey` we'd serialize as:
  1052. *
  1053. * "*2\r\n$3\r\nGET\r\n$7\r\nsomekey\r\n"
  1054. */
  1055. protected function processArrayCommand($cmd, array $args) {
  1056. if ($this->mode == self::PIPELINE) {
  1057. $this->commands[] = [ 'cmd' => $cmd, 'args' => $args ];
  1058. return true;
  1059. }
  1060. $clen = strlen($cmd);
  1061. $count = count($args) + 1;
  1062. $cmd = "*{$count}\r\n\${$clen}\r\n{$cmd}\r\n";
  1063. while (count($args)) {
  1064. $arg = (string)array_shift($args);
  1065. $alen = strlen($arg);
  1066. $cmd .= "\${$alen}\r\n{$arg}\r\n";
  1067. }
  1068. if (!$this->checkConnection()) {
  1069. return false;
  1070. }
  1071. return (bool)fwrite($this->connection, $cmd);
  1072. }
  1073. protected function processCommand($cmd/* ... */) {
  1074. $args = func_get_args();
  1075. array_shift($args);
  1076. return $this->processArrayCommand($cmd, $args);
  1077. }
  1078. public function _serialize($str) {
  1079. switch ($this->serializer) {
  1080. case self::SERIALIZER_NONE:
  1081. return $str;
  1082. case self::SERIALIZER_PHP:
  1083. return serialize($str);
  1084. default:
  1085. throw new RedisException("Not Implemented");
  1086. }
  1087. }
  1088. public function _unserialize($str) {
  1089. switch ($this->serializer) {
  1090. case self::SERIALIZER_NONE:
  1091. return $str;
  1092. case self::SERIALIZER_PHP:
  1093. return unserialize($str);
  1094. default:
  1095. throw new RedisException("Not Implemented");
  1096. }
  1097. }
  1098. protected function processClientListResponse() {
  1099. if ($this->mode !== self::ATOMIC) {
  1100. $this->multiHandler[] = [ 'cb' => [$this,'processClientListResponse'] ];
  1101. if (($this->mode === self::MULTI) && !$this->processQueuedResponse()) {
  1102. return false;
  1103. }
  1104. return $this;
  1105. }
  1106. $resp = $this->sockReadData($type);
  1107. if (($type !== self::TYPE_LINE) AND ($type !== self::TYPE_BULK)) {
  1108. return null;
  1109. }
  1110. $ret = [];
  1111. $pairs = explode(' ', trim($resp));
  1112. foreach ($pairs as $pair) {
  1113. $kv = explode('=', $pair, 2);
  1114. if (count($kv) == 1) {
  1115. $ret[] = $pair;
  1116. } else {
  1117. list($k, $v) = $kv;
  1118. $ret[$k] = $v;
  1119. }
  1120. }
  1121. return $ret;
  1122. }
  1123. protected function processVariantResponse() {
  1124. if ($this->mode !== self::ATOMIC) {
  1125. $this->multiHandler[] = [ 'cb' => [$this,'processVariantResponse'] ];
  1126. if (($this->mode === self::MULTI) && !$this->processQueuedResponse()) {
  1127. return false;
  1128. }
  1129. return $this;
  1130. }
  1131. return $this->doProcessVariantResponse();
  1132. }
  1133. private function doProcessVariantResponse() {
  1134. $resp = $this->sockReadData($type);
  1135. if ($type === self::TYPE_INT) {
  1136. return (int) $resp;
  1137. }
  1138. if ($type === self::TYPE_MULTIBULK) {
  1139. if ($resp === '-1') {
  1140. return '';
  1141. }
  1142. $ret = [];
  1143. $lineNo = 0;
  1144. $count = (int) $resp;
  1145. while($count--) {
  1146. $lineNo++;
  1147. $ret[] = $this->doProcessVariantResponse();
  1148. }
  1149. return $ret;
  1150. }
  1151. if ($type === self::TYPE_ERR) {
  1152. $this->lastError = $resp;
  1153. return null;
  1154. }
  1155. return $resp;
  1156. }
  1157. protected function processSerializedResponse() {
  1158. if ($this->mode === self::ATOMIC) {
  1159. $resp = $this->sockReadData($type);
  1160. if ($resp === null) {
  1161. return false;
  1162. }
  1163. return (($type === self::TYPE_LINE) OR ($type === self::TYPE_BULK))
  1164. ? $this->_unserialize($resp) : false;
  1165. }
  1166. $this->multiHandler[] = [ 'cb' => [$this,'processSerializedResponse'] ];
  1167. if (($this->mode === self::MULTI) && !$this->processQueuedResponse()) {
  1168. return false;
  1169. }
  1170. return $this;
  1171. }
  1172. protected function processBooleanResponse() {
  1173. if ($this->mode === self::ATOMIC) {
  1174. $resp = $this->sockReadData($type);
  1175. return ($type === self::TYPE_LINE) AND ($resp === 'OK');
  1176. }
  1177. $this->multiHandler[] = [ 'cb' => [$this,'processBooleanResponse'] ];
  1178. if (($this->mode === self::MULTI) && !$this->processQueuedResponse()) {
  1179. return false;
  1180. }
  1181. return $this;
  1182. }
  1183. protected function processLongResponse() {
  1184. if ($this->mode === self::ATOMIC) {
  1185. $resp = $this->sockReadData($type);
  1186. return ($type === self::TYPE_INT) ? ((int)$resp) : null;
  1187. }
  1188. $this->multiHandler[] = [ 'cb' => [$this,'processLongResponse'] ];
  1189. if (($this->mode === self::MULTI) && !$this->processQueuedResponse()) {
  1190. return false;
  1191. }
  1192. return $this;
  1193. }
  1194. protected function processDoubleResponse() {
  1195. if ($this->mode === self::ATOMIC) {
  1196. $resp = $this->sockReadData($type);
  1197. if (($type === self::TYPE_INT) ||
  1198. ($type === self::TYPE_BULK && is_numeric($resp))) {
  1199. return (float)$resp;
  1200. }
  1201. return false;
  1202. }
  1203. $this->multiHandler[] = [ 'cb' => [$this,'processDoubleResponse'] ];
  1204. if (($this->mode === self::MULTI) && !$this->processQueuedResponse()) {
  1205. return false;
  1206. }
  1207. return $this;
  1208. }
  1209. protected function processStringResponse() {
  1210. if ($this->mode === self::ATOMIC) {
  1211. $resp = $this->sockReadData($type);
  1212. return (($type === self::TYPE_LINE) OR ($type === self::TYPE_BULK))
  1213. ? ((string)$resp) : null;
  1214. }
  1215. $this->multiHandler[] = [ 'cb' => [$this,'processStringResponse'] ];
  1216. if (($this->mode === self::MULTI) && !$this->processQueuedResponse()) {
  1217. return false;
  1218. }
  1219. return $this;
  1220. }
  1221. protected function processVectorResponse($unser = 0) {
  1222. if ($this->mode !== self::ATOMIC) {
  1223. $this->multiHandler[] = [ 'cb' => [$this, 'processVectorResponse'],
  1224. 'args' => [$unser]
  1225. ];
  1226. if (($this->mode === self::MULTI) && !$this->processQueuedResponse()) {
  1227. return false;
  1228. }
  1229. return $this;
  1230. }
  1231. $count = $this->sockReadData($type);
  1232. if ($type !== self::TYPE_MULTIBULK) {
  1233. return null;
  1234. }
  1235. $ret = [];
  1236. $lineNo = 0;
  1237. while($count--) {
  1238. $lineNo++;
  1239. $val = $this->sockReadData($type);
  1240. if ($unser AND (($lineNo % $unser) == 0)) {
  1241. $val = $this->_unserialize($val);
  1242. }
  1243. $ret[] = $val !== null ? $val : false;
  1244. }
  1245. return $ret;
  1246. }
  1247. protected function processMapResponse($unser_key, $unser_val = true) {
  1248. if ($this->mode !== self::ATOMIC) {
  1249. $this->multiHandler[] = [ 'cb' => [$this, 'processMapResponse'],
  1250. 'args' => [$unser_key,$unser_val]
  1251. ];
  1252. if (($this->mode === self::MULTI) && !$this->processQueuedResponse()) {
  1253. return false;
  1254. }
  1255. return $this;
  1256. }
  1257. $count = $this->sockReadData($type);
  1258. if ($type !== self::TYPE_MULTIBULK) {
  1259. return null;
  1260. }
  1261. $ret = [];
  1262. while($count > 1) {
  1263. $key = $this->sockReadData($type);
  1264. if ($unser_key) {
  1265. $key = $this->_unserialize($key);
  1266. }
  1267. $val = $this->sockReadData($type);
  1268. if ($unser_val) {
  1269. $val = $this->_unserialize($val);
  1270. }
  1271. $ret[$key] = $val;
  1272. $count -= 2;
  1273. }
  1274. if ($count > 1) {
  1275. $ret[$this->sockReadData($type)] = null;
  1276. }
  1277. return $ret;
  1278. }
  1279. protected function processAssocResponse(array $keys, $unser_val = true) {
  1280. if ($this->mode !== self::ATOMIC) {
  1281. $this->multiHandler[] = [ 'cb' => [$this, 'processAssocResponse'],
  1282. 'args' => [$keys, $unser_val]
  1283. ];
  1284. if (($this->mode === self::MULTI) && !$this->processQueuedResponse()) {
  1285. return false;
  1286. }
  1287. return $this;
  1288. }
  1289. $count = $this->sockReadData($type);
  1290. if ($type !== self::TYPE_MULTIBULK) {
  1291. return null;
  1292. }
  1293. $ret = [];
  1294. while($count--) {
  1295. $key = array_shift($keys);
  1296. $val = $this->sockReadData($type);
  1297. if ($unser_val) {
  1298. $val = $this->_unserialize($val);
  1299. }
  1300. $ret[$key] = $val !== null ? $val : false;
  1301. }
  1302. return $ret;
  1303. }
  1304. protected function process1Response() {
  1305. if ($this->mode === self::ATOMIC) {
  1306. $resp = $this->sockReadData($type);
  1307. return ($type === self::TYPE_INT) && ($resp === '1');
  1308. }
  1309. $this->multiHandler[] = [ 'cb' => [$this,'process1Response'] ];
  1310. if (($this->mode === self::MULTI) && !$this->processQueuedResponse()) {
  1311. return false;
  1312. }
  1313. return $this;
  1314. }
  1315. protected function processTypeResponse() {
  1316. if ($this->mode === self::ATOMIC) {
  1317. $resp = $this->sockReadData($type);
  1318. if ($type !== self::TYPE_LINE) {
  1319. return self::REDIS_NOT_FOUND;
  1320. }
  1321. switch($resp) {
  1322. case 'string': return self::REDIS_STRING;
  1323. case 'set': return self::REDIS_SET;
  1324. case 'list': return self::REDIS_LIST;
  1325. case 'zset': return self::REDIS_ZSET;
  1326. case 'hash': return self::REDIS_HASH;
  1327. default: return self::REDIS_NOT_FOUND;
  1328. }
  1329. }
  1330. $this->multiHandler[] = [ 'cb' => 'processTypeResponse' ];
  1331. if (($this->mode === self::MULTI) && !$this->processQueuedResponse()) {
  1332. return false;
  1333. }
  1334. return $this;
  1335. }
  1336. protected function processRawResponse() {
  1337. if ($this->mode === self::ATOMIC) {
  1338. return $this->sockReadLine();
  1339. }
  1340. $this->multiHandler[] = [ 'cb' => 'processRawResponse' ];
  1341. if (($this->mode === self::MULTI) && !$this->processQueuedResponse()) {
  1342. return false;
  1343. }
  1344. return $this;
  1345. }
  1346. protected function processInfoResponse() {
  1347. if ($this->mode !== self::ATOMIC) {
  1348. $this->multiHandler[] = [ 'cb' => 'processInfoResponse' ];
  1349. if (($this->mode === self::MULTI) && !$this->processQueuedResponse()) {
  1350. return false;
  1351. }
  1352. return $this;
  1353. }
  1354. $resp = $this->sockReadData($type);
  1355. if (($type !== self::TYPE_LINE) AND ($type !== self::TYPE_BULK)) {
  1356. return false;
  1357. }
  1358. $ret = [];
  1359. $lines = preg_split('/[\r\n]+/', $resp);
  1360. foreach ($lines as $line) {
  1361. if ((substr($line, 0, 1) == '#') OR
  1362. !trim($line)) {
  1363. continue;
  1364. }
  1365. $colon = strpos($line, ':');
  1366. if ($colon === false) {
  1367. break;
  1368. }
  1369. list($key, $val) = explode(':', $line, 2);
  1370. $ret[$key] = $val;
  1371. }
  1372. return $ret;
  1373. }
  1374. protected function processQueuedResponse() {
  1375. $resp = $this->sockReadData($type);
  1376. return ($type === self::TYPE_LINE) AND ($resp === 'QUEUED');
  1377. }
  1378. public function _prefix($key) {
  1379. return $this->prefix . $key;
  1380. }
  1381. /**
  1382. * Dispatches all commands in the Redis::$map list
  1383. *
  1384. * All other commands are handled by explicit implementations
  1385. */
  1386. public function __call($fname, $args) {
  1387. $fname = strtolower($fname);
  1388. if (!isset(self::$map[$fname])) {
  1389. trigger_error("Call to undefined function Redis::$fname()", E_ERROR);
  1390. return null;
  1391. }
  1392. $func = self::$map[$fname];
  1393. if ($func === false) {
  1394. throw new RedisException("Redis::$fname() is currently unimplemented");
  1395. }
  1396. // Normalize record
  1397. if (!empty($func['alias'])) {
  1398. if (isset(self::$

Large files files are truncated, but you can click here to view the full file