From 08cd35f2de901bed07c2ab8dfbe9d37604f2f4a9 Mon Sep 17 00:00:00 2001 From: Georges Date: Sat, 23 Dec 2023 14:03:59 +0100 Subject: [PATCH] Maintenance update: Increased minimum PHP version to 7.4, added params and return types, added DocBlocks --- composer.json | 3 +- src/phpssdb/Core/SSDB.php | 684 +++++++++++++++++------------ src/phpssdb/Core/SSDB_Response.php | 42 +- 3 files changed, 425 insertions(+), 304 deletions(-) diff --git a/composer.json b/composer.json index cb745b5..86237a8 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ } ], "require": { - "php": ">=5.4.0" + "php": ">=7.4.0", + "ext-json": "*" }, "autoload": { "psr-4": { diff --git a/src/phpssdb/Core/SSDB.php b/src/phpssdb/Core/SSDB.php index e0cff35..ae52b96 100644 --- a/src/phpssdb/Core/SSDB.php +++ b/src/phpssdb/Core/SSDB.php @@ -22,29 +22,40 @@ */ class SSDB { - private $debug = false; + public const STEP_SIZE = 0; + public const STEP_DATA = 1; + public $sock = null; - private $_closed = false; - private $recv_buf = ''; - private $_easy = false; public $last_resp = null; - - function __construct($host, $port, $timeout_ms=2000){ - $timeout_f = (float)$timeout_ms/1000; + public array $resp = []; + public int $step; + public int $block_size; + private bool $debug = false; + private bool $_closed = false; + private string $recv_buf = ''; + private bool $_easy = false; + private bool $batch_mode = false; + private array $batch_cmds = []; + private ?string $async_auth_password = null; + + public function __construct(string $host, int $port, int $timeout_ms = 2000) + { + $timeout_f = (float)$timeout_ms / 1000; $this->sock = @stream_socket_client("[$host]:$port", $errno, $errstr, $timeout_f); - if(!$this->sock){ + if (!$this->sock) { throw new SSDBException("$errno: $errstr"); } - $timeout_sec = intval($timeout_ms/1000); + $timeout_sec = intval($timeout_ms / 1000); $timeout_usec = ($timeout_ms - $timeout_sec * 1000) * 1000; @stream_set_timeout($this->sock, $timeout_sec, $timeout_usec); - if(function_exists('stream_set_chunk_size')){ + if (function_exists('stream_set_chunk_size')) { @stream_set_chunk_size($this->sock, 1024 * 1024); } } - function set_timeout($timeout_ms){ - $timeout_sec = intval($timeout_ms/1000); + public function set_timeout(int $timeout_ms) + { + $timeout_sec = intval($timeout_ms / 1000); $timeout_usec = ($timeout_ms - $timeout_sec * 1000) * 1000; @stream_set_timeout($this->sock, $timeout_sec, $timeout_usec); } @@ -55,216 +66,112 @@ function set_timeout($timeout_ms){ * And some certain methods like get/zget will return false * when response is not ok(not_found, etc) */ - function easy(){ + public function easy(): void + { $this->_easy = true; } - function close(){ - if(!$this->_closed){ - @fclose($this->sock); - $this->_closed = true; - $this->sock = null; - } - } - - function closed(){ + public function closed(): bool + { return $this->_closed; } - private $batch_mode = false; - private $batch_cmds = array(); + public function multi(): self + { + return $this->batch(); + } - function batch(){ + public function batch(): self + { $this->batch_mode = true; - $this->batch_cmds = array(); + $this->batch_cmds = []; return $this; } - function multi(){ - return $this->batch(); - } - - function exec(){ - $ret = array(); - foreach($this->batch_cmds as $op){ - list($cmd, $params) = $op; + public function exec(): array + { + $ret = []; + foreach ($this->batch_cmds as $op) { + [$cmd, $params] = $op; $this->send_req($cmd, $params); } - foreach($this->batch_cmds as $op){ - list($cmd, $params) = $op; + foreach ($this->batch_cmds as $op) { + [$cmd, $params] = $op; $resp = $this->recv_resp($cmd, $params); $resp = $this->check_easy_resp($cmd, $resp); $ret[] = $resp; } $this->batch_mode = false; - $this->batch_cmds = array(); + $this->batch_cmds = []; return $ret; } - function request(){ - $args = func_get_args(); - $cmd = array_shift($args); - return $this->__call($cmd, $args); - } - - private $async_auth_password = null; - - function auth($password){ - $this->async_auth_password = $password; - return null; - } - - function __call($cmd, $params=array()){ - $cmd = strtolower($cmd); - if($this->async_auth_password !== null){ - $pass = $this->async_auth_password; - $this->async_auth_password = null; - $auth = $this->__call('auth', array($pass)); - if($auth !== true){ - throw new Exception("Authentication failed"); - } - } - - if($this->batch_mode){ - $this->batch_cmds[] = array($cmd, $params); - return $this; - } - - try{ - if($this->send_req($cmd, $params) === false){ - $resp = new SSDB_Response('error', 'send error'); - }else{ - $resp = $this->recv_resp($cmd, $params); - } - }catch(SSDBException $e){ - if($this->_easy){ - throw $e; - }else{ - $resp = new SSDB_Response('error', $e->getMessage()); - } - } - - if($resp->code == 'noauth'){ - $msg = $resp->message; - throw new Exception($msg); - } - - $resp = $this->check_easy_resp($cmd, $resp); - return $resp; - } - - private function check_easy_resp($cmd, $resp){ - $this->last_resp = $resp; - if($this->_easy){ - if($resp->not_found()){ - return NULL; - }else if(!$resp->ok() && !is_array($resp->data)){ - return false; - }else{ - return $resp->data; + private function send_req(string $cmd, array $params) + { + $req = [$cmd]; + foreach ($params as $p) { + if (is_array($p)) { + $req = array_merge($req, $p); + } else { + $req[] = $p; } - }else{ - $resp->cmd = $cmd; - return $resp; } + return $this->send($req); } - function multi_set($kvs=array()){ - $args = array(); - foreach($kvs as $k=>$v){ - $args[] = $k; - $args[] = $v; + public function send(array $data) + { + $ps = []; + foreach ($data as $p) { + $ps[] = strlen($p); + $ps[] = $p; } - return $this->__call(__FUNCTION__, $args); - } - - function multi_hset($name, $kvs=array()){ - $args = array($name); - foreach($kvs as $k=>$v){ - $args[] = $k; - $args[] = $v; + $s = join("\n", $ps) . "\n\n"; + if ($this->debug) { + echo '> ' . str_replace(["\r", "\n"], ['\r', '\n'], $s) . "\n"; } - return $this->__call(__FUNCTION__, $args); - } - - function multi_zset($name, $kvs=array()){ - $args = array($name); - foreach($kvs as $k=>$v){ - $args[] = $k; - $args[] = $v; + try { + while (true) { + $ret = @fwrite($this->sock, $s); + if ($ret === false || $ret === 0) { + $this->close(); + throw new SSDBException('Connection lost'); + } + $s = substr($s, $ret); + if (strlen($s) == 0) { + break; + } + @fflush($this->sock); + } + } catch (Exception $e) { + $this->close(); + throw new SSDBException($e->getMessage()); } - return $this->__call(__FUNCTION__, $args); - } - - function incr($key, $val=1){ - $args = func_get_args(); - return $this->__call(__FUNCTION__, $args); - } - - function decr($key, $val=1){ - $args = func_get_args(); - return $this->__call(__FUNCTION__, $args); - } - - function zincr($name, $key, $score=1){ - $args = func_get_args(); - return $this->__call(__FUNCTION__, $args); - } - - function zdecr($name, $key, $score=1){ - $args = func_get_args(); - return $this->__call(__FUNCTION__, $args); - } - - function zadd($key, $score, $value){ - $args = array($key, $value, $score); - return $this->__call('zset', $args); - } - - function zRevRank($name, $key){ - $args = func_get_args(); - return $this->__call("zrrank", $args); - } - - function zRevRange($name, $offset, $limit){ - $args = func_get_args(); - return $this->__call("zrrange", $args); - } - - function hincr($name, $key, $val=1){ - $args = func_get_args(); - return $this->__call(__FUNCTION__, $args); - } - - function hdecr($name, $key, $val=1){ - $args = func_get_args(); - return $this->__call(__FUNCTION__, $args); + return $ret; } - private function send_req($cmd, $params){ - $req = array($cmd); - foreach($params as $p){ - if(is_array($p)){ - $req = array_merge($req, $p); - }else{ - $req[] = $p; - } + public function close(): void + { + if (!$this->_closed) { + @fclose($this->sock); + $this->_closed = true; + $this->sock = null; } - return $this->send($req); } - private function recv_resp($cmd, $params){ + private function recv_resp($cmd, $params): SSDB_Response + { $resp = $this->recv(); - if($resp === false){ + if ($resp === false) { return new SSDB_Response('error', 'Unknown error'); - }else if(!$resp){ + } else if (!$resp) { return new SSDB_Response('disconnected', 'Connection closed'); } - if($resp[0] == 'noauth'){ - $errmsg = isset($resp[1])? $resp[1] : ''; + if ($resp[0] == 'noauth') { + $errmsg = isset($resp[1]) ? $resp[1] : ''; return new SSDB_Response($resp[0], $errmsg); } - switch($cmd){ + switch ($cmd) { case 'dbsize': case 'ping': case 'qset': @@ -312,19 +219,19 @@ private function recv_resp($cmd, $params){ case 'zremrangebyscore': case 'ttl': case 'expire': - if($resp[0] == 'ok'){ - $val = isset($resp[1])? intval($resp[1]) : 0; + if ($resp[0] == 'ok') { + $val = isset($resp[1]) ? intval($resp[1]) : 0; return new SSDB_Response($resp[0], $val); - }else{ - $errmsg = isset($resp[1])? $resp[1] : ''; + } else { + $errmsg = isset($resp[1]) ? $resp[1] : ''; return new SSDB_Response($resp[0], $errmsg); } case 'zavg': - if($resp[0] == 'ok'){ - $val = isset($resp[1])? floatval($resp[1]) : (float)0; + if ($resp[0] == 'ok') { + $val = isset($resp[1]) ? floatval($resp[1]) : (float)0; return new SSDB_Response($resp[0], $val); - }else{ - $errmsg = isset($resp[1])? $resp[1] : ''; + } else { + $errmsg = isset($resp[1]) ? $resp[1] : ''; return new SSDB_Response($resp[0], $errmsg); } case 'get': @@ -334,37 +241,37 @@ private function recv_resp($cmd, $params){ case 'qget': case 'qfront': case 'qback': - if($resp[0] == 'ok'){ - if(count($resp) == 2){ + if ($resp[0] == 'ok') { + if (count($resp) == 2) { return new SSDB_Response('ok', $resp[1]); - }else{ + } else { return new SSDB_Response('server_error', 'Invalid response'); } - }else{ - $errmsg = isset($resp[1])? $resp[1] : ''; + } else { + $errmsg = isset($resp[1]) ? $resp[1] : ''; return new SSDB_Response($resp[0], $errmsg); } break; case 'qpop': case 'qpop_front': case 'qpop_back': - if($resp[0] == 'ok'){ + if ($resp[0] == 'ok') { $size = 1; - if(isset($params[1])){ + if (isset($params[1])) { $size = intval($params[1]); } - if($size <= 1){ - if(count($resp) == 2){ + if ($size <= 1) { + if (count($resp) == 2) { return new SSDB_Response('ok', $resp[1]); - }else{ + } else { return new SSDB_Response('server_error', 'Invalid response'); } - }else{ + } else { $data = array_slice($resp, 1); return new SSDB_Response('ok', $data); } - }else{ - $errmsg = isset($resp[1])? $resp[1] : ''; + } else { + $errmsg = isset($resp[1]) ? $resp[1] : ''; return new SSDB_Response($resp[0], $errmsg); } break; @@ -374,46 +281,46 @@ private function recv_resp($cmd, $params){ case 'hlist': case 'zlist': case 'qslice': - if($resp[0] == 'ok'){ - $data = array(); - if($resp[0] == 'ok'){ + if ($resp[0] == 'ok') { + $data = []; + if ($resp[0] == 'ok') { $data = array_slice($resp, 1); } return new SSDB_Response($resp[0], $data); - }else{ - $errmsg = isset($resp[1])? $resp[1] : ''; + } else { + $errmsg = isset($resp[1]) ? $resp[1] : ''; return new SSDB_Response($resp[0], $errmsg); } case 'auth': case 'exists': case 'hexists': case 'zexists': - if($resp[0] == 'ok'){ - if(count($resp) == 2){ + if ($resp[0] == 'ok') { + if (count($resp) == 2) { return new SSDB_Response('ok', (bool)$resp[1]); - }else{ + } else { return new SSDB_Response('server_error', 'Invalid response'); } - }else{ - $errmsg = isset($resp[1])? $resp[1] : ''; + } else { + $errmsg = isset($resp[1]) ? $resp[1] : ''; return new SSDB_Response($resp[0], $errmsg); } break; case 'multi_exists': case 'multi_hexists': case 'multi_zexists': - if($resp[0] == 'ok'){ - if(count($resp) % 2 == 1){ - $data = array(); - for($i=1; $idebug){ - echo '> ' . str_replace(array("\r", "\n"), array('\r', '\n'), $s) . "\n"; - } - try{ - while(true){ - $ret = @fwrite($this->sock, $s); - if($ret === false || $ret === 0){ - $this->close(); - throw new SSDBException('Connection lost'); - } - $s = substr($s, $ret); - if(strlen($s) == 0){ - break; - } - @fflush($this->sock); - } - }catch(Exception $e){ - $this->close(); - throw new SSDBException($e->getMessage()); - } - return $ret; - } - - function recv(){ + public function recv(): ?array + { $this->step = self::STEP_SIZE; - while(true){ + while (true) { $ret = $this->parse(); - if($ret === null){ - try{ + if ($ret === null) { + try { $data = @fread($this->sock, 1024 * 1024); - if($this->debug){ - echo '< ' . str_replace(array("\r", "\n"), array('\r', '\n'), $data) . "\n"; + if ($this->debug) { + echo '< ' . str_replace(["\r", "\n"], ['\r', '\n'], $data) . "\n"; } - }catch(Exception $e){ + } catch (Exception $e) { $data = ''; } - if($data === false || $data === ''){ - if(feof($this->sock)){ + if ($data === false || $data === '') { + if (feof($this->sock)) { $this->close(); throw new SSDBException('Connection lost'); - }else{ + } else { throw new SSDBTimeoutException('Connection timeout'); } } $this->recv_buf .= $data; -# echo "read " . strlen($data) . " total: " . strlen($this->recv_buf) . "\n"; - }else{ + } else { return $ret; } } } - const STEP_SIZE = 0; - const STEP_DATA = 1; - public $resp = array(); - public $step; - public $block_size; - - private function parse(){ + private function parse(): ?array + { $spos = 0; $epos = 0; $buf_size = strlen($this->recv_buf); // performance issue for large reponse //$this->recv_buf = ltrim($this->recv_buf); - while(true){ + while (true) { $spos = $epos; - if($this->step === self::STEP_SIZE){ + if ($this->step === self::STEP_SIZE) { $epos = strpos($this->recv_buf, "\n", $spos); - if($epos === false){ + if ($epos === false) { break; } $epos += 1; @@ -541,20 +413,20 @@ private function parse(){ $spos = $epos; $line = trim($line); - if(strlen($line) == 0){ // head end + if (strlen($line) == 0) { // head end $this->recv_buf = substr($this->recv_buf, $spos); $ret = $this->resp; - $this->resp = array(); + $this->resp = []; return $ret; } $this->block_size = intval($line); $this->step = self::STEP_DATA; } - if($this->step === self::STEP_DATA){ + if ($this->step === self::STEP_DATA) { $epos = $spos + $this->block_size; - if($epos <= $buf_size){ + if ($epos <= $buf_size) { $n = strpos($this->recv_buf, "\n", $epos); - if($n !== false){ + if ($n !== false) { $data = substr($this->recv_buf, $spos, $epos - $spos); $this->resp[] = $data; $epos = $n + 1; @@ -567,9 +439,245 @@ private function parse(){ } // packet not ready - if($spos > 0){ + if ($spos > 0) { $this->recv_buf = substr($this->recv_buf, $spos); } return null; } + + private function check_easy_resp(string $cmd, $resp) + { + $this->last_resp = $resp; + if ($this->_easy) { + if ($resp->not_found()) { + return NULL; + } else if (!$resp->ok() && !is_array($resp->data)) { + return false; + } else { + return $resp->data; + } + } else { + $resp->cmd = $cmd; + return $resp; + } + } + + public function request() + { + $args = func_get_args(); + $cmd = array_shift($args); + return $this->__call($cmd, $args); + } + + /** + * @param string $cmd + * @param array $params + * @return mixed + * @throws SSDBException + */ + public function __call(string $cmd, array $params = []) + { + $cmd = strtolower($cmd); + if ($this->async_auth_password !== null) { + $pass = $this->async_auth_password; + $this->async_auth_password = null; + $auth = $this->__call('auth', [$pass]); + if ($auth !== true) { + throw new Exception("Authentication failed"); + } + } + + if ($this->batch_mode) { + $this->batch_cmds[] = [$cmd, $params]; + return $this; + } + + try { + if ($this->send_req($cmd, $params) === false) { + $resp = new SSDB_Response('error', 'send error'); + } else { + $resp = $this->recv_resp($cmd, $params); + } + } catch (SSDBException $e) { + if ($this->_easy) { + throw $e; + } else { + $resp = new SSDB_Response('error', $e->getMessage()); + } + } + + if ($resp->code == 'noauth') { + $msg = $resp->message; + throw new Exception($msg); + } + + $resp = $this->check_easy_resp($cmd, $resp); + return $resp; + } + + public function auth(?string $password): void + { + $this->async_auth_password = $password; + } + + /** + * @param array $kvs + * @return mixed + * @throws SSDBException + */ + public function multi_set(array $kvs = []) + { + $args = []; + foreach ($kvs as $k => $v) { + $args[] = $k; + $args[] = $v; + } + return $this->__call(__FUNCTION__, $args); + } + + /** + * @param string $name + * @param array $kvs + * @return mixed + * @throws SSDBException + */ + public function multi_hset(string $name, array $kvs = []) + { + $args = [$name]; + foreach ($kvs as $k => $v) { + $args[] = $k; + $args[] = $v; + } + return $this->__call(__FUNCTION__, $args); + } + + /** + * @param string $name + * @param array $kvs + * @return mixed + * @throws SSDBException + */ + public function multi_zset(string $name, array $kvs = []) + { + $args = [$name]; + foreach ($kvs as $k => $v) { + $args[] = $k; + $args[] = $v; + } + return $this->__call(__FUNCTION__, $args); + } + + /** + * @param string $key + * @param int $val + * @return mixed + * @throws SSDBException + */ + public function incr(string $key, int $val = 1) + { + $args = func_get_args(); + return $this->__call(__FUNCTION__, $args); + } + + /** + * @param string $key + * @param int $val + * @return mixed + * @throws SSDBException + */ + public function decr(string $key, int $val = 1) + { + $args = func_get_args(); + return $this->__call(__FUNCTION__, $args); + } + + /** + * @param string $name + * @param string $key + * @param int $score + * @return mixed + * @throws SSDBException + */ + public function zincr(string $name, string $key, int $score = 1) + { + $args = func_get_args(); + return $this->__call(__FUNCTION__, $args); + } + + /** + * @param string $name + * @param string $key + * @param int $score + * @return mixed + * @throws SSDBException + */ + public function zdecr(string $name, string $key, int $score = 1) + { + $args = func_get_args(); + return $this->__call(__FUNCTION__, $args); + } + + /** + * @param string $key + * @param $score + * @param $value + * @return mixed + * @throws SSDBException + */ + public function zadd(string $key, $score, $value) + { + $args = [$key, $value, $score]; + return $this->__call('zset', $args); + } + + /** + * @param string $name + * @param string $key + * @return mixed + * @throws SSDBException + */ + public function zRevRank(string $name, string $key) + { + $args = func_get_args(); + return $this->__call("zrrank", $args); + } + + /** + * @param string $name + * @param int $offset + * @param int $limit + * @return mixed + * @throws SSDBException + */ + public function zRevRange(string $name, int $offset, int $limit) + { + $args = func_get_args(); + return $this->__call("zrrange", $args); + } + + /** + * @param string $name + * @param string $key + * @param int $val + * @return mixed + * @throws SSDBException + */ + public function hincr(string $name, string $key, int $val = 1) + { + $args = func_get_args(); + return $this->__call(__FUNCTION__, $args); + } + + /** + * @param string $name + * @param string $key + * @param int $val + * @return mixed + * @throws SSDBException + */ + public function hdecr(string $name, string $key, int $val = 1) + { + $args = func_get_args(); + return $this->__call(__FUNCTION__, $args); + } } diff --git a/src/phpssdb/Core/SSDB_Response.php b/src/phpssdb/Core/SSDB_Response.php index 1d97009..c4086fc 100644 --- a/src/phpssdb/Core/SSDB_Response.php +++ b/src/phpssdb/Core/SSDB_Response.php @@ -18,36 +18,48 @@ * Class SSDB_Response * @package phpssdb\Core */ -class SSDB_Response +final class SSDB_Response { - public $cmd; - public $code; + public string $cmd; + public string $code; + public string $message; + + /** + * @var mixed|null + */ public $data = null; - public $message; - function __construct($code='ok', $data_or_message=null){ + /** + * @param string $code + * @param mixed $data_or_message + */ + function __construct(string $code = 'ok', $data_or_message = null) + { $this->code = $code; - if($code == 'ok'){ + if ($code == 'ok') { $this->data = $data_or_message; - }else{ + } else { $this->message = $data_or_message; } } - function __toString(){ - if($this->code == 'ok'){ - $s = $this->data === null? '' : json_encode($this->data); - }else{ + public function __toString() + { + if ($this->code == 'ok') { + $s = $this->data === null ? '' : json_encode($this->data); + } else { $s = $this->message; } return sprintf('%-13s %12s %s', $this->cmd, $this->code, $s); } - function ok(){ - return $this->code == 'ok'; + public function ok(): bool + { + return $this->code === 'ok'; } - function not_found(){ - return $this->code == 'not_found'; + public function not_found(): bool + { + return $this->code === 'not_found'; } }