summaryrefslogtreecommitdiffstats
path: root/preload-redis.inc
diff options
context:
space:
mode:
Diffstat (limited to 'preload-redis.inc')
-rw-r--r--preload-redis.inc246
1 files changed, 246 insertions, 0 deletions
diff --git a/preload-redis.inc b/preload-redis.inc
new file mode 100644
index 0000000..199b0fd
--- /dev/null
+++ b/preload-redis.inc
@@ -0,0 +1,246 @@
+<?php
+/**
+ * Redis connector using FFI and libhiredis
+ * PoC, only for documentation purpose
+ *
+ * Copyright (c) 2019 Remi Collet
+ * License: CC-BY-SA
+ * http://creativecommons.org/licenses/by-sa/4.0/
+ */
+namespace Remi;
+
+class Redis {
+ // Singleton
+ static private $ffi = NULL;
+ // Redis connection: redisContext
+ private $conn = NULL;
+ // Log method calls
+ private $debug = false;
+
+ // From hiredis/read.h, REDIS_REPLY_* macros
+ const REDIS_REPLY_STRING = 1;
+ const REDIS_REPLY_ARRAY = 2;
+ const REDIS_REPLY_INTEGER = 3;
+ const REDIS_REPLY_NIL = 4;
+ const REDIS_REPLY_STATUS = 5;
+ const REDIS_REPLY_ERROR = 6;
+
+ /**
+ * Display debug information
+ */
+ private function log($f, ...$a) {
+ if ($this->debug) {
+ vprintf($f, $a);
+ }
+ }
+
+ /**
+ * Parser the header and and init the FFI Singleton
+ */
+ private function initFFI() {
+ if (self::$ffi) {
+ return;
+ }
+ $this->log("+ %s()\n", __METHOD__);
+ // Try if preloaded
+ try {
+ self::$ffi = \FFI::scope("_REMI_REDIS_");
+ } catch (\FFI\Exception $e) {
+ // Try direct load
+ if (PHP_SAPI === 'cli' || (int)ini_get("ffi.enable")) {
+ self::$ffi = \FFI::load(__DIR__ . '/preload-redis.h');
+ } else {
+ throw $e;
+ }
+ }
+ if (!self::$ffi) {
+ throw new \RuntimeException("FFI parse fails");
+ }
+ }
+
+ /**
+ * Free redisContext memory
+ */
+ public function cleanup() {
+ if (!is_null($this->conn)) {
+ self::$ffi->redisFree($this->conn);
+ $this->conn = NULL;
+ }
+ }
+
+ /**
+ * Constructor + connection
+ */
+ public function __construct($path, $port=6379, $debug=false) {
+ $this->debug = $debug;
+ $this->log("+ %s(%s, %d)\n", __METHOD__, $path, $port);
+ $this->initFFI();
+ if ($path[0] === '/') {
+ $this->conn = self::$ffi->redisConnectUnix($path);
+ } else {
+ $this->conn = self::$ffi->redisConnect($path, $port);
+ }
+ if ($this->conn->err) {
+ $msg = '';
+ for($i=0 ; $i<128 && $this->conn->errstr[$i] ; $i++) {
+ $msg .= $this->conn->errstr[$i];
+ }
+ $this->cleanup();
+ throw new \RuntimeException($msg);
+ }
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct() {
+ $this->log("+ %s\n\n", __METHOD__);
+ $this->cleanup();
+ }
+
+ /**
+ * Parse the redisReply
+ */
+ private function resp($rep, $free=true) {
+ if (is_null($rep)) {
+ throw new \RuntimeException('Command fails');
+ }
+ switch ($rep->type) {
+ case self::REDIS_REPLY_STATUS:
+ case self::REDIS_REPLY_STRING:
+ case self::REDIS_REPLY_ERROR:
+ $msg = '';
+ for($i=0 ; $i<$rep->len ; $i++) {
+ $msg .= $rep->str[$i];
+ }
+ if ($rep->type == self::REDIS_REPLY_ERROR) {
+ if ($free) self::$ffi->freeReplyObject($rep);
+ throw new \RuntimeException($msg);
+ }
+ $ret = $msg;
+ break;
+ case self::REDIS_REPLY_ARRAY:
+ $ret = [];
+ for ($i=0 ; $i<$rep->elements ; $i++) {
+ $ret[] = $this->resp($rep->element[$i], false);
+ }
+ break;
+ case self::REDIS_REPLY_INTEGER:
+ $ret = $rep->integer;
+ break;
+ case self::REDIS_REPLY_NIL:
+ $ret = NULL;
+ break;
+ default:
+ if ($free) self::$ffi->freeReplyObject($rep);
+ throw new \RuntimeException('Unkown response type');
+ }
+ if ($free) self::$ffi->freeReplyObject($rep);
+ return $ret;
+ }
+
+ /**
+ * Unkown command
+ */
+ public function grrr() {
+ $this->log("+ %s()\n", __METHOD__);
+ return($this->resp(self::$ffi->redisCommand($this->conn, 'GRRR')));
+ }
+
+ /**
+ * DEL command
+ */
+ public function del($name) {
+ $this->log("+ %s(%s)\n", __METHOD__, $name);
+ return($this->resp(self::$ffi->redisCommand($this->conn, "DEL $name")));
+ }
+
+ /**
+ * SET command
+ */
+ public function set($name, $value) {
+ $this->log("+ %s(%s, %s)\n", __METHOD__, $name, $value);
+ return($this->resp(self::$ffi->redisCommand($this->conn, "SET $name %s", (string)$value)));
+ }
+
+ /**
+ * GET command
+ */
+ public function get($name) {
+ $this->log("+ %s(%s)\n", __METHOD__, $name);
+ return($this->resp(self::$ffi->redisCommand($this->conn, "GET $name")));
+ }
+
+ /**
+ * INCR command
+ */
+ public function incr($name) {
+ $this->log("+ %s(%s)\n", __METHOD__, $name);
+ return($this->resp(self::$ffi->redisCommand($this->conn, "INCR $name")));
+ }
+
+ /**
+ * DECR command
+ */
+ public function decr($name) {
+ $this->log("+ %s(%s)\n", __METHOD__, $name);
+ return($this->resp(self::$ffi->redisCommand($this->conn, "DECR $name")));
+ }
+
+ /**
+ * LPUSH command
+ */
+ public function lpush($name, $elt) {
+ $this->log("+ %s(%s, %s)\n", __METHOD__, $name, $elt);
+ return($this->resp(self::$ffi->redisCommand($this->conn, "LPUSH $name %s", (string)$elt)));
+ }
+
+ /**
+ * RPUSH command
+ */
+ public function rpush($name, $elt) {
+ $this->log("+ %s(%s, %s)\n", __METHOD__, $name, $elt);
+ return($this->resp(self::$ffi->redisCommand($this->conn, "RPUSH $name %s", (string)$elt)));
+ }
+
+ /**
+ * LSET command
+ */
+ public function lset($name, $ind, $elt) {
+ $this->log("+ %s(%s, %d, %s)\n", __METHOD__, $name, $ind, $elt);
+ return($this->resp(self::$ffi->redisCommand($this->conn, "LSET $name %d %s", (int)$ind, (string)$elt)));
+ }
+
+ /**
+ * LRANGE command
+ */
+ public function lrange($name, $start, $end) {
+ $this->log("+ %s(%s, %d, %s)\n", __METHOD__, $name, $start, $end);
+ return($this->resp(self::$ffi->redisCommand($this->conn, "LRANGE $name %d %d", (int)$start, (int)$end)));
+ }
+
+ /**
+ * LPOP command
+ */
+ public function lpop($name) {
+ $this->log("+ %s(%s)\n", __METHOD__, $name);
+ return($this->resp(self::$ffi->redisCommand($this->conn, "LPOP $name")));
+ }
+
+ /**
+ * RPOP command
+ */
+ public function rpop($name) {
+ $this->log("+ %s(%s)\n", __METHOD__, $name);
+ return($this->resp(self::$ffi->redisCommand($this->conn, "RPOP $name")));
+ }
+
+ /**
+ * LLEN command
+ */
+ public function llen($name) {
+ $this->log("+ %s(%s)\n", __METHOD__, $name);
+ return($this->resp(self::$ffi->redisCommand($this->conn, "LLEN $name")));
+ }
+}
+