├── .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 | [](https://packagist.org/packages/huangdijia/laravel-ssdb)
4 | [](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 |
--------------------------------------------------------------------------------