├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src ├── Cache │ └── Ssdb.php ├── Exception.php ├── Facades │ └── Ssdb.php ├── Manager.php ├── Response.php ├── Session │ └── SsdbSessionHandler.php ├── Simple.php ├── Ssdb.php ├── SsdbServiceProvider.php ├── TimeoutException.php └── helpers.php └── tests └── SsdbTest.php /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: SSDB TEST 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Install SSDB 11 | run: | 12 | cd /tmp 13 | wget --no-check-certificate https://github.com/ideawu/ssdb/archive/master.zip 14 | unzip master 15 | cd ssdb-master 16 | make 17 | sudo make install 18 | 19 | - name: Start SSDB 20 | env: 21 | SSDB_PATH: "/usr/local/ssdb" 22 | run: | 23 | cd $SSDB_PATH 24 | sudo touch log.txt 25 | sudo chmod 777 log.txt 26 | sudo ./ssdb-server -d ssdb.conf 27 | 28 | - name: Test SSDB 29 | run: | 30 | ps aux|grep ssdb 31 | 32 | - name: Installing php 33 | uses: shivammathur/setup-php@master 34 | with: 35 | php-version: 7.3 36 | extension-csv: mbstring, xdebug 37 | ini-values-csv: "post_max_size=256M, short_open_tag=On" 38 | 39 | - name: Check PHP Version 40 | run: php -v 41 | 42 | - name: Check Composer Version 43 | run: composer -V 44 | 45 | - name: Check php extensions 46 | run: php -m 47 | 48 | - uses: actions/checkout@v1 49 | 50 | - name: Composer Install 51 | run: | 52 | composer install 53 | 54 | - name: PHPUnit Run 55 | run: | 56 | ./vendor/bin/phpunit --verbose 57 | 58 | # - name: Init webapp 59 | # run: | 60 | # cd $HOME 61 | # composer create-project --prefer-dist laravel/laravel webapp 62 | 63 | # - name: Install laravel-ssdb 64 | # run: | 65 | # cd $HOME/webapp 66 | # composer config repositories.laravel-ssdb path /home/runner/work/laravel-ssdb/* 67 | # composer require "huangdijia/laravel-ssdb:*@dev" --prefer-dist 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | *.bak 3 | composer.lock 4 | .phpunit.result.cache 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 D.J.Hwang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # laravel-ssdb 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/huangdijia/laravel-ssdb/version.png)](https://packagist.org/packages/huangdijia/laravel-ssdb) 4 | [![Total Downloads](https://poser.pugx.org/huangdijia/laravel-ssdb/d/total.png)](https://packagist.org/packages/huangdijia/laravel-ssdb) 5 | 6 | ## Requirements 7 | 8 | * PHP >= 7.0 9 | * Laravel >= 5.5 10 | 11 | ## Installation 12 | 13 | First, install laravel 5.5, and make sure that the database connection settings are correct. 14 | 15 | ~~~bash 16 | composer require huangdijia/laravel-ssdb 17 | ~~~ 18 | 19 | ## Configurations 20 | 21 | ~~~php 22 | // config/database.php 23 | 24 | 'ssdb' => [ 25 | 'default' => 'default', 26 | 'connections' => [ 27 | 'default' => [ 28 | 'host' => env('SSDB_HOST', '127.0.0.1'), 29 | 'port' => env('SSDB_PORT', 8888), 30 | 'timeout' => env('SSDB_TIMEOUT', 2000), 31 | 'password' => 'your-password', // optional 32 | ], 33 | ], 34 | // ... 35 | ], 36 | ~~~ 37 | 38 | ## Usage 39 | 40 | ### Connection 41 | 42 | ~~~php 43 | $ssdb = Ssdb::connection('default'); 44 | ~~~ 45 | 46 | ### As Facades 47 | 48 | ~~~php 49 | use Huangdijia\Ssdb\Facades\Ssdb; 50 | 51 | ... 52 | Ssdb::set('key', 'value'); 53 | $value = Ssdb::get('key'); 54 | ~~~ 55 | 56 | ### As Helper 57 | 58 | ~~~php 59 | ssdb()->set('key', 'value'); 60 | ssdb()->get('key'); 61 | ~~~ 62 | 63 | ### As Cache Store Driver 64 | 65 | ~~~php 66 | // config/cache.php 67 | 'default' => 'ssdb', 68 | 69 | 'ssdb' => [ 70 | 'driver' => 'ssdb', 71 | 'connection' => 'default', 72 | ], 73 | ~~~ 74 | 75 | or 76 | 77 | set .env as 78 | 79 | ~~~env 80 | CACHE_DRIVER=ssdb 81 | ~~~ 82 | 83 | ### As Session Manager 84 | 85 | ~~~php 86 | // config/session.php 87 | 'driver' => 'ssdb', 88 | ~~~ 89 | 90 | or 91 | 92 | set .env as 93 | 94 | ~~~env 95 | SESSION_DRIVER=ssdb 96 | ~~~ 97 | 98 | ## Other 99 | 100 | [SSDB PHP API](http://ssdb.io/docs/zh_cn/php/index.html) 101 | 102 | ## License 103 | 104 | laravel-ssdb is licensed under The MIT License (MIT). 105 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "huangdijia/laravel-ssdb", 3 | "description": "ssdb for laravel", 4 | "type": "library", 5 | "keywords": [ 6 | "laravel", 7 | "ssdb" 8 | ], 9 | "homepage": "https://github.com/huangdijia/laravel-ssdb", 10 | "license": "MIT", 11 | "authors": [{ 12 | "name": "huangdijia", 13 | "email": "huangdijia@gmail.com" 14 | }], 15 | "require": { 16 | "php": ">=7.0.0", 17 | "illuminate/support": "^5.5|^6.0|^7.0|^8.0", 18 | "illuminate/contracts": "^5.5|^6.0|^7.0|^8.0" 19 | }, 20 | "require-dev": { 21 | "orchestra/testbench": "^4.0", 22 | "mockery/mockery": "^1.0", 23 | "phpunit/phpunit": "^6.0|^7.0|^8.0|^9.0" 24 | }, 25 | "autoload": { 26 | "files": [ 27 | "src/helpers.php" 28 | ], 29 | "psr-4": { 30 | "Huangdijia\\Ssdb\\": "src/" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Huangdijia\\Ssdb\\Tests\\": "tests/" 36 | } 37 | }, 38 | "extra": { 39 | "laravel": { 40 | "providers": [ 41 | "Huangdijia\\Ssdb\\SsdbServiceProvider" 42 | ], 43 | "aliases": { 44 | "Ssdb": "Huangdijia\\Ssdb\\Facades\\Ssdb" 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Cache/Ssdb.php: -------------------------------------------------------------------------------- 1 | ssdb = $app['ssdb.manager']->connection( 22 | $app['config']->get('cache.ssdb.connection', 'default') 23 | ); 24 | $this->prefix = $app['config']->get('cache.prefix', ''); 25 | } 26 | 27 | public function get($key) 28 | { 29 | return $this->ssdb->get($this->prefix . $key); 30 | } 31 | 32 | public function put($key, $value, $seconds) 33 | { 34 | return $this->ssdb->setx($this->prefix . $key, $value, $seconds); 35 | } 36 | 37 | public function many(array $keys) 38 | { 39 | $ret = []; 40 | foreach ($keys as $key) { 41 | $ret[$key] = $this->ssdb->get($this->prefix . $key); 42 | } 43 | return $ret; 44 | } 45 | 46 | public function putMany(array $values, $seconds) 47 | { 48 | foreach ($values as $key => $value) { 49 | $this->ssdb->set($this->prefix . $key, $value, $seconds); 50 | } 51 | return true; 52 | } 53 | 54 | public function increment($key, $value = 1) 55 | { 56 | $value = (int) $value; 57 | return $this->ssdb->incr($this->prefix . $key, $value); 58 | } 59 | 60 | public function decrement($key, $value = 1) 61 | { 62 | $value = (int) $value; 63 | return $this->ssdb->incr($this->prefix . $key, -$value); 64 | } 65 | 66 | public function getPrefix() 67 | { 68 | return $this->prefix; 69 | } 70 | 71 | public function forget($key) 72 | { 73 | return $this->ssdb->del($this->prefix . $key); 74 | } 75 | 76 | public function forever($key, $value) 77 | { 78 | return $this->ssdb->set($this->prefix . $key, $value); 79 | } 80 | 81 | public function flush() 82 | { 83 | return $this->ssdb->flushdb(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Exception.php: -------------------------------------------------------------------------------- 1 | config = $config; 23 | } 24 | 25 | /** 26 | * Get config 27 | * 28 | * @param string|null $key 29 | * @param mixed $default 30 | * @return mixed 31 | */ 32 | public function config(?string $key = null, $default = null) 33 | { 34 | if (is_null($key)) { 35 | return $this->config; 36 | } 37 | 38 | return Arr::get($this->config, $key, $default); 39 | } 40 | 41 | /** 42 | * get connection 43 | * @param mixed|null $name 44 | * @return \Huangdijia\Ssdb\Simple 45 | */ 46 | public function connection($name = null) 47 | { 48 | $name = $name ?: Arr::get($this->config, 'default', 'default'); 49 | 50 | if (!isset($this->connections[$name])) { 51 | if (!isset($this->config['connections'][$name])) { 52 | throw new Exception("config 'database.ssdb.connections.{$name}' is undefined", 1); 53 | } 54 | 55 | $config = $this->config['connections'][$name]; 56 | 57 | $this->connections[$name] = new Simple( 58 | $config['host'], 59 | $config['port'] ?? 8888, 60 | $config['timeout'] ?? 2000 61 | ); 62 | 63 | if (isset($config['password'])) { 64 | $this->connections[$name]->auth($config['password']); 65 | } 66 | } 67 | 68 | return $this->connections[$name]; 69 | } 70 | 71 | /** 72 | * Get all connections 73 | * @return \Huangdijia\Ssdb\Simple[] 74 | */ 75 | public function connections() 76 | { 77 | return $this->connections; 78 | } 79 | 80 | /** 81 | * @param string $method 82 | * @param array $parameters 83 | * @return mixed 84 | */ 85 | public function __call($method, $parameters) 86 | { 87 | return $this->connection()->{$method}(...$parameters); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Response.php: -------------------------------------------------------------------------------- 1 | code = $code; 15 | if ($code == 'ok') { 16 | $this->data = $data_or_message; 17 | } else { 18 | $this->message = $data_or_message; 19 | } 20 | } 21 | 22 | public function __toString() 23 | { 24 | if ($this->code == 'ok') { 25 | $s = $this->data === null ? '' : json_encode($this->data); 26 | } else { 27 | $s = $this->message; 28 | } 29 | return sprintf('%-13s %12s %s', $this->cmd, $this->code, $s); 30 | } 31 | 32 | public function ok() 33 | { 34 | return $this->code == 'ok'; 35 | } 36 | 37 | public function not_found() 38 | { 39 | return $this->code == 'not_found'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Session/SsdbSessionHandler.php: -------------------------------------------------------------------------------- 1 | lifetime = $app['config']->get('session.lifetime') ?? 120; 16 | $this->ssdb = $app['ssdb.manager']; 17 | } 18 | 19 | public function open($savePath, $sessionName) 20 | { 21 | return true; 22 | } 23 | 24 | public function close() 25 | { 26 | $this->ssdb->close(); 27 | } 28 | 29 | public function read($sessionId) 30 | { 31 | return $this->ssdb->get($sessionId); 32 | } 33 | 34 | public function write($sessionId, $data) 35 | { 36 | return $this->ssdb->setx($sessionId, $data, $this->lifetime) ? true : false; 37 | } 38 | 39 | public function destroy($sessionId) 40 | { 41 | return $this->ssdb->del($sessionId); 42 | } 43 | 44 | public function gc($lifetime) 45 | { 46 | return true; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Simple.php: -------------------------------------------------------------------------------- 1 | easy(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Ssdb.php: -------------------------------------------------------------------------------- 1 | sock = @stream_socket_client("$host:$port", $errno, $errstr, $timeout_f); 108 | if(!$this->sock){ 109 | throw new Exception("$errno: $errstr"); 110 | } 111 | $timeout_sec = intval($timeout_ms/1000); 112 | $timeout_usec = ($timeout_ms - $timeout_sec * 1000) * 1000; 113 | @stream_set_timeout($this->sock, $timeout_sec, $timeout_usec); 114 | if(function_exists('stream_set_chunk_size')){ 115 | @stream_set_chunk_size($this->sock, 1024 * 1024); 116 | } 117 | } 118 | 119 | function set_timeout($timeout_ms){ 120 | $timeout_sec = intval($timeout_ms/1000); 121 | $timeout_usec = ($timeout_ms - $timeout_sec * 1000) * 1000; 122 | @stream_set_timeout($this->sock, $timeout_sec, $timeout_usec); 123 | } 124 | 125 | /** 126 | * After this method invoked with yesno=true, all requesting methods 127 | * will not return a Response object. 128 | * And some certain methods like get/zget will return false 129 | * when response is not ok(not_found, etc) 130 | */ 131 | function easy(){ 132 | $this->_easy = true; 133 | } 134 | 135 | function close(){ 136 | if(!$this->_closed){ 137 | @fclose($this->sock); 138 | $this->_closed = true; 139 | $this->sock = null; 140 | } 141 | } 142 | 143 | function closed(){ 144 | return $this->_closed; 145 | } 146 | 147 | private $batch_mode = false; 148 | private $batch_cmds = array(); 149 | 150 | function batch(){ 151 | $this->batch_mode = true; 152 | $this->batch_cmds = array(); 153 | return $this; 154 | } 155 | 156 | function multi(){ 157 | return $this->batch(); 158 | } 159 | 160 | function exec(){ 161 | $ret = array(); 162 | foreach($this->batch_cmds as $op){ 163 | list($cmd, $params) = $op; 164 | $this->send_req($cmd, $params); 165 | } 166 | foreach($this->batch_cmds as $op){ 167 | list($cmd, $params) = $op; 168 | $resp = $this->recv_resp($cmd, $params); 169 | $resp = $this->check_easy_resp($cmd, $resp); 170 | $ret[] = $resp; 171 | } 172 | $this->batch_mode = false; 173 | $this->batch_cmds = array(); 174 | return $ret; 175 | } 176 | 177 | function request(){ 178 | $args = func_get_args(); 179 | $cmd = array_shift($args); 180 | return $this->__call($cmd, $args); 181 | } 182 | 183 | private $async_auth_password = null; 184 | 185 | function auth($password){ 186 | $this->async_auth_password = $password; 187 | return null; 188 | } 189 | 190 | function __call($cmd, $params=array()){ 191 | $cmd = strtolower($cmd); 192 | if($this->async_auth_password !== null){ 193 | $pass = $this->async_auth_password; 194 | $this->async_auth_password = null; 195 | $auth = $this->__call('auth', array($pass)); 196 | if($auth !== true){ 197 | throw new Exception("Authentication failed"); 198 | } 199 | } 200 | 201 | if($this->batch_mode){ 202 | $this->batch_cmds[] = array($cmd, $params); 203 | return $this; 204 | } 205 | 206 | try{ 207 | if($this->send_req($cmd, $params) === false){ 208 | $resp = new Response('error', 'send error'); 209 | }else{ 210 | $resp = $this->recv_resp($cmd, $params); 211 | } 212 | }catch(Exception $e){ 213 | if($this->_easy){ 214 | throw $e; 215 | }else{ 216 | $resp = new Response('error', $e->getMessage()); 217 | } 218 | } 219 | 220 | if($resp->code == 'noauth'){ 221 | $msg = $resp->message; 222 | throw new Exception($msg); 223 | } 224 | 225 | $resp = $this->check_easy_resp($cmd, $resp); 226 | return $resp; 227 | } 228 | 229 | private function check_easy_resp($cmd, $resp){ 230 | $this->last_resp = $resp; 231 | if($this->_easy){ 232 | if($resp->not_found()){ 233 | return NULL; 234 | }else if(!$resp->ok() && !is_array($resp->data)){ 235 | return false; 236 | }else{ 237 | return $resp->data; 238 | } 239 | }else{ 240 | $resp->cmd = $cmd; 241 | return $resp; 242 | } 243 | } 244 | 245 | function multi_set($kvs=array()){ 246 | $args = array(); 247 | foreach($kvs as $k=>$v){ 248 | $args[] = $k; 249 | $args[] = $v; 250 | } 251 | return $this->__call(__FUNCTION__, $args); 252 | } 253 | 254 | function multi_hset($name, $kvs=array()){ 255 | $args = array($name); 256 | foreach($kvs as $k=>$v){ 257 | $args[] = $k; 258 | $args[] = $v; 259 | } 260 | return $this->__call(__FUNCTION__, $args); 261 | } 262 | 263 | function multi_zset($name, $kvs=array()){ 264 | $args = array($name); 265 | foreach($kvs as $k=>$v){ 266 | $args[] = $k; 267 | $args[] = $v; 268 | } 269 | return $this->__call(__FUNCTION__, $args); 270 | } 271 | 272 | function incr($key, $val=1){ 273 | $args = func_get_args(); 274 | return $this->__call(__FUNCTION__, $args); 275 | } 276 | 277 | function decr($key, $val=1){ 278 | $args = func_get_args(); 279 | return $this->__call(__FUNCTION__, $args); 280 | } 281 | 282 | function zincr($name, $key, $score=1){ 283 | $args = func_get_args(); 284 | return $this->__call(__FUNCTION__, $args); 285 | } 286 | 287 | function zdecr($name, $key, $score=1){ 288 | $args = func_get_args(); 289 | return $this->__call(__FUNCTION__, $args); 290 | } 291 | 292 | function zadd($key, $score, $value){ 293 | $args = array($key, $value, $score); 294 | return $this->__call('zset', $args); 295 | } 296 | 297 | function zRevRank($name, $key){ 298 | $args = func_get_args(); 299 | return $this->__call("zrrank", $args); 300 | } 301 | 302 | function zRevRange($name, $offset, $limit){ 303 | $args = func_get_args(); 304 | return $this->__call("zrrange", $args); 305 | } 306 | 307 | function hincr($name, $key, $val=1){ 308 | $args = func_get_args(); 309 | return $this->__call(__FUNCTION__, $args); 310 | } 311 | 312 | function hdecr($name, $key, $val=1){ 313 | $args = func_get_args(); 314 | return $this->__call(__FUNCTION__, $args); 315 | } 316 | 317 | private function send_req($cmd, $params){ 318 | $req = array($cmd); 319 | foreach($params as $p){ 320 | if(is_array($p)){ 321 | $req = array_merge($req, $p); 322 | }else{ 323 | $req[] = $p; 324 | } 325 | } 326 | return $this->send($req); 327 | } 328 | 329 | private function recv_resp($cmd, $params){ 330 | $resp = $this->recv(); 331 | if($resp === false){ 332 | return new Response('error', 'Unknown error'); 333 | }else if(!$resp){ 334 | return new Response('disconnected', 'Connection closed'); 335 | } 336 | if($resp[0] == 'noauth'){ 337 | $errmsg = isset($resp[1])? $resp[1] : ''; 338 | return new Response($resp[0], $errmsg); 339 | } 340 | switch($cmd){ 341 | case 'dbsize': 342 | case 'ping': 343 | case 'qset': 344 | case 'getbit': 345 | case 'setbit': 346 | case 'countbit': 347 | case 'strlen': 348 | case 'set': 349 | case 'setx': 350 | case 'setnx': 351 | case 'zset': 352 | case 'hset': 353 | case 'qpush': 354 | case 'qpush_front': 355 | case 'qpush_back': 356 | case 'qtrim_front': 357 | case 'qtrim_back': 358 | case 'del': 359 | case 'zdel': 360 | case 'hdel': 361 | case 'hsize': 362 | case 'zsize': 363 | case 'qsize': 364 | case 'hclear': 365 | case 'zclear': 366 | case 'qclear': 367 | case 'multi_set': 368 | case 'multi_del': 369 | case 'multi_hset': 370 | case 'multi_hdel': 371 | case 'multi_zset': 372 | case 'multi_zdel': 373 | case 'incr': 374 | case 'decr': 375 | case 'zincr': 376 | case 'zdecr': 377 | case 'hincr': 378 | case 'hdecr': 379 | case 'zget': 380 | case 'zrank': 381 | case 'zrrank': 382 | case 'zcount': 383 | case 'zsum': 384 | case 'zremrangebyrank': 385 | case 'zremrangebyscore': 386 | case 'ttl': 387 | case 'expire': 388 | if($resp[0] == 'ok'){ 389 | $val = isset($resp[1])? intval($resp[1]) : 0; 390 | return new Response($resp[0], $val); 391 | }else{ 392 | $errmsg = isset($resp[1])? $resp[1] : ''; 393 | return new Response($resp[0], $errmsg); 394 | } 395 | case 'zavg': 396 | if($resp[0] == 'ok'){ 397 | $val = isset($resp[1])? floatval($resp[1]) : (float)0; 398 | return new Response($resp[0], $val); 399 | }else{ 400 | $errmsg = isset($resp[1])? $resp[1] : ''; 401 | return new Response($resp[0], $errmsg); 402 | } 403 | case 'get': 404 | case 'substr': 405 | case 'getset': 406 | case 'hget': 407 | case 'qget': 408 | case 'qfront': 409 | case 'qback': 410 | if($resp[0] == 'ok'){ 411 | if(count($resp) == 2){ 412 | return new Response('ok', $resp[1]); 413 | }else{ 414 | return new Response('server_error', 'Invalid response'); 415 | } 416 | }else{ 417 | $errmsg = isset($resp[1])? $resp[1] : ''; 418 | return new Response($resp[0], $errmsg); 419 | } 420 | break; 421 | case 'qpop': 422 | case 'qpop_front': 423 | case 'qpop_back': 424 | if($resp[0] == 'ok'){ 425 | $size = 1; 426 | if(isset($params[1])){ 427 | $size = intval($params[1]); 428 | } 429 | if($size <= 1){ 430 | if(count($resp) == 2){ 431 | return new Response('ok', $resp[1]); 432 | }else{ 433 | return new Response('server_error', 'Invalid response'); 434 | } 435 | }else{ 436 | $data = array_slice($resp, 1); 437 | return new Response('ok', $data); 438 | } 439 | }else{ 440 | $errmsg = isset($resp[1])? $resp[1] : ''; 441 | return new Response($resp[0], $errmsg); 442 | } 443 | break; 444 | case 'keys': 445 | case 'zkeys': 446 | case 'hkeys': 447 | case 'hlist': 448 | case 'zlist': 449 | case 'qslice': 450 | if($resp[0] == 'ok'){ 451 | $data = array(); 452 | if($resp[0] == 'ok'){ 453 | $data = array_slice($resp, 1); 454 | } 455 | return new Response($resp[0], $data); 456 | }else{ 457 | $errmsg = isset($resp[1])? $resp[1] : ''; 458 | return new Response($resp[0], $errmsg); 459 | } 460 | case 'auth': 461 | case 'exists': 462 | case 'hexists': 463 | case 'zexists': 464 | if($resp[0] == 'ok'){ 465 | if(count($resp) == 2){ 466 | return new Response('ok', (bool)$resp[1]); 467 | }else{ 468 | return new Response('server_error', 'Invalid response'); 469 | } 470 | }else{ 471 | $errmsg = isset($resp[1])? $resp[1] : ''; 472 | return new Response($resp[0], $errmsg); 473 | } 474 | break; 475 | case 'multi_exists': 476 | case 'multi_hexists': 477 | case 'multi_zexists': 478 | if($resp[0] == 'ok'){ 479 | if(count($resp) % 2 == 1){ 480 | $data = array(); 481 | for($i=1; $idebug){ 542 | echo '> ' . str_replace(array("\r", "\n"), array('\r', '\n'), $s) . "\n"; 543 | } 544 | try{ 545 | while(true){ 546 | $ret = @fwrite($this->sock, $s); 547 | if($ret === false || $ret === 0){ 548 | $this->close(); 549 | throw new Exception('Connection lost'); 550 | } 551 | $s = substr($s, $ret); 552 | if(strlen($s) == 0){ 553 | break; 554 | } 555 | @fflush($this->sock); 556 | } 557 | }catch(Exception $e){ 558 | $this->close(); 559 | throw new Exception($e->getMessage()); 560 | } 561 | return $ret; 562 | } 563 | 564 | function recv(){ 565 | $this->step = self::STEP_SIZE; 566 | while(true){ 567 | $ret = $this->parse(); 568 | if($ret === null){ 569 | try{ 570 | $data = @fread($this->sock, 1024 * 1024); 571 | if($this->debug){ 572 | echo '< ' . str_replace(array("\r", "\n"), array('\r', '\n'), $data) . "\n"; 573 | } 574 | }catch(Exception $e){ 575 | $data = ''; 576 | } 577 | if($data === false || $data === ''){ 578 | if(feof($this->sock)){ 579 | $this->close(); 580 | throw new Exception('Connection lost'); 581 | }else{ 582 | throw new TimeoutException('Connection timeout'); 583 | } 584 | } 585 | $this->recv_buf .= $data; 586 | # echo "read " . strlen($data) . " total: " . strlen($this->recv_buf) . "\n"; 587 | }else{ 588 | return $ret; 589 | } 590 | } 591 | } 592 | 593 | const STEP_SIZE = 0; 594 | const STEP_DATA = 1; 595 | public $resp = array(); 596 | public $step; 597 | public $block_size; 598 | 599 | private function parse(){ 600 | $spos = 0; 601 | $epos = 0; 602 | $buf_size = strlen($this->recv_buf); 603 | // performance issue for large reponse 604 | //$this->recv_buf = ltrim($this->recv_buf); 605 | while(true){ 606 | $spos = $epos; 607 | if($this->step === self::STEP_SIZE){ 608 | $epos = strpos($this->recv_buf, "\n", $spos); 609 | if($epos === false){ 610 | break; 611 | } 612 | $epos += 1; 613 | $line = substr($this->recv_buf, $spos, $epos - $spos); 614 | $spos = $epos; 615 | 616 | $line = trim($line); 617 | if(strlen($line) == 0){ // head end 618 | $this->recv_buf = substr($this->recv_buf, $spos); 619 | $ret = $this->resp; 620 | $this->resp = array(); 621 | return $ret; 622 | } 623 | $this->block_size = intval($line); 624 | $this->step = self::STEP_DATA; 625 | } 626 | if($this->step === self::STEP_DATA){ 627 | $epos = $spos + $this->block_size; 628 | if($epos <= $buf_size){ 629 | $n = strpos($this->recv_buf, "\n", $epos); 630 | if($n !== false){ 631 | $data = substr($this->recv_buf, $spos, $epos - $spos); 632 | $this->resp[] = $data; 633 | $epos = $n + 1; 634 | $this->step = self::STEP_SIZE; 635 | continue; 636 | } 637 | } 638 | break; 639 | } 640 | } 641 | 642 | // packet not ready 643 | if($spos > 0){ 644 | $this->recv_buf = substr($this->recv_buf, $spos); 645 | } 646 | return null; 647 | } 648 | } 649 | -------------------------------------------------------------------------------- /src/SsdbServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->has('cache')) { 18 | Cache::extend('ssdb', function ($app) { 19 | return Cache::repository(new Ssdb($app)); 20 | }); 21 | } 22 | 23 | if ($this->app->has('session')) { 24 | Session::extend('ssdb', function ($app) { 25 | return new SsdbSessionHandler($app); 26 | }); 27 | } 28 | } 29 | 30 | /** 31 | * 注册服务提供者 32 | * 33 | * @return void 34 | */ 35 | public function register() 36 | { 37 | $this->app->singleton(Manager::class, function ($app) { 38 | return new Manager($app['config']->get('database.ssdb', [])); 39 | }); 40 | 41 | $this->app->alias(Manager::class, 'ssdb.manager'); 42 | } 43 | 44 | /** 45 | * 服务提供 46 | * @return array 47 | */ 48 | public function provides() 49 | { 50 | return [ 51 | Manager::class, 52 | 'ssdb.manager', 53 | ]; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/TimeoutException.php: -------------------------------------------------------------------------------- 1 | connection($name); 6 | } 7 | } -------------------------------------------------------------------------------- /tests/SsdbTest.php: -------------------------------------------------------------------------------- 1 | closed()) { 15 | $connection = new Simple('127.0.0.1', 8888, 2000); 16 | } 17 | 18 | return $connection; 19 | } 20 | 21 | public function test_single_set_and_get() 22 | { 23 | $key = 'test'; 24 | $value = time(); 25 | 26 | self::connection()->set($key, $value); 27 | 28 | $cached = self::connection()->get($key); 29 | 30 | $this->assertEquals($cached, $value); 31 | $this->assertTrue(true); 32 | } 33 | 34 | public function test_multi_set_and_get() 35 | { 36 | self::connection()->multi_set([ 37 | 'a' => 1, 38 | 'b' => 2, 39 | ]); 40 | 41 | $cached = self::connection()->multi_get(['a', 'b']); 42 | 43 | $this->assertIsArray($cached); 44 | $this->assertEquals(count($cached), 2); 45 | } 46 | } 47 | --------------------------------------------------------------------------------