Completed
Push — master ( 2ea026...d310f4 )
by Michal
13:32 queued 10:59
created

RedisProxy::zscan()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13

Duplication

Lines 13
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 13
loc 13
ccs 0
cts 9
cp 0
rs 9.8333
c 0
b 0
f 0
cc 3
nc 3
nop 4
crap 12
1
<?php
2
3
namespace RedisProxy;
4
5
use Exception;
6
use Predis\Client;
7
use Predis\Response\Status;
8
use Redis;
9
10
/**
11
 * @method mixed config(string $command, $argument = null)
12
 * @method int dbsize() Return the number of keys in the selected database
13
 * @method boolean restore(string $key, int $ttl, string $serializedValue) Create a key using the provided serialized value, previously obtained using DUMP. If ttl is 0 the key is created without any expire, otherwise the specified expire time (in milliseconds) is set
14
 * @method boolean set(string $key, string $value) Set the string value of a key
15
 * @method boolean setex(string $key, int $seconds, string $value) Set the value and expiration of a key
16
 * @method int ttl(string $key) Get the time to live for a key, returns TTL in seconds, -2 if the key does not exist, -1 if the key exists but has no associated expire
17
 * @method int pttl(string $key) Get the time to live for a key in milliseconds, returns TTL in miliseconds, -2 if the key does not exist, -1 if the key exists but has no associated expire
18
 * @method array keys(string $pattern) Find all keys matching the given pattern
19
 * @method int hset(string $key, string $field, string $value) Set the string value of a hash field
20
 * @method array hkeys(string $key) Get all fields in a hash (without values)
21
 * @method array hgetall(string $key) Get all fields and values in a hash
22
 * @method int hlen(string $key) Get the number of fields in a hash
23
 * @method array smembers(string $key) Get all the members in a set
24
 * @method int scard(string $key) Get the number of members in a set
25
 * @method int llen(string $key) Get the length of a list
26
 * @method bool lset(string $key, string $index, string $value) Sets the list element at index to value
27
 * @method array lrange(string $key, int $start, int $stop) Get a range of elements from a list
28
 * @method int lrem(string $key, string $value) Removes the first count occurrences of elements equal to value from the list stored at key.
29
 * @method int zcard(string $key) Get the number of members in a sorted set
30
 * @method boolean flushall() Remove all keys from all databases
31
 * @method boolean flushdb() Remove all keys from the current database
32
 */
33
class RedisProxy
34
{
35
    const DRIVER_REDIS = 'redis';
36
37
    const DRIVER_PREDIS = 'predis';
38
39
    const TYPE_STRING = 'string';
40
41
    const TYPE_SET = 'set';
42
43
    const TYPE_HASH = 'hash';
44
45
    const TYPE_LIST = 'list';
46
47
    const TYPE_SORTED_SET = 'sorted_set';
48
49
    private $driver;
50
51
    private $host;
52
53
    private $port;
54
55
    private $database = 0;
56
57
    private $selectedDatabase = 0;
58
59
    private $timeout;
60
61
    private $supportedDrivers = [
62
        self::DRIVER_REDIS,
63
        self::DRIVER_PREDIS,
64
    ];
65
66
    private $driversOrder = [];
67
68
    private $redisTypeMap = [
69
        self::DRIVER_REDIS => [
70
            1 => self::TYPE_STRING,
71
            2 => self::TYPE_SET,
72
            3 => self::TYPE_LIST,
73
            4 => self::TYPE_SORTED_SET,
74
            5 => self::TYPE_HASH,
75
        ],
76
        self::DRIVER_PREDIS => [
77
            'string' => self::TYPE_STRING,
78
            'set' => self::TYPE_SET,
79
            'list' => self::TYPE_LIST,
80
            'zset' => self::TYPE_SORTED_SET,
81
            'hash' => self::TYPE_HASH,
82
        ],
83
    ];
84
85 260
    public function __construct($host, $port, $database = 0, $timeout = null)
86
    {
87 260
        $this->host = $host;
88 260
        $this->port = $port;
89 260
        $this->database = $database;
90 260
        $this->timeout = $timeout;
91 260
        $this->driversOrder = $this->supportedDrivers;
92 260
    }
93
94
    /**
95
     * Set driver priorities - default is 1. redis, 2. predis
96
     * @param array $driversOrder
97
     * @return RedisProxy
98
     * @throws RedisProxyException if some driver is not supported
99
     */
100 260
    public function setDriversOrder(array $driversOrder)
101
    {
102 260
        foreach ($driversOrder as $driver) {
103 258
            if (!in_array($driver, $this->supportedDrivers)) {
104 130
                throw new RedisProxyException('Driver "' . $driver . '" is not supported');
105
            }
106 129
        }
107 258
        $this->driversOrder = $driversOrder;
108 258
        return $this;
109
    }
110
111 258
    private function init()
112
    {
113 258
        $this->prepareDriver();
114 256
        $this->select($this->database);
115 256
    }
116
117 258
    private function prepareDriver()
118
    {
119 258
        if ($this->driver !== null) {
120 256
            return;
121
        }
122
123 258
        foreach ($this->driversOrder as $preferredDriver) {
124 256
            if ($preferredDriver === self::DRIVER_REDIS && extension_loaded('redis')) {
125 128
                $this->driver = new Redis();
126 128
                return;
127
            }
128 128
            if ($preferredDriver === self::DRIVER_PREDIS && class_exists('Predis\Client')) {
129 128
                $this->driver = new Client([
130 128
                    'host' => $this->host,
131 128
                    'port' => $this->port,
132 64
                ]);
133 128
                return;
134
            }
135 1
        }
136 2
        throw new RedisProxyException('No driver available');
137
    }
138
139
    /**
140
     * @return string|null
141
     */
142 258
    public function actualDriver()
143
    {
144 258
        if ($this->driver instanceof Redis) {
145 128
            return self::DRIVER_REDIS;
146
        }
147 134
        if ($this->driver instanceof Client) {
148 128
            return self::DRIVER_PREDIS;
149
        }
150 10
        return null;
151
    }
152
153 256
    private function connect($host, $port, $timeout = null)
154
    {
155 256
        return $this->driver->connect($host, $port, $timeout);
156
    }
157
158 256
    private function isConnected()
159
    {
160 256
        return $this->driver->isConnected();
161
    }
162
163 258
    public function __call($name, $arguments)
164
    {
165 258
        $this->init();
166 256
        $name = strtolower($name);
167
        try {
168 256
            $result = call_user_func_array([$this->driver, $name], $arguments);
169 130
        } catch (Exception $e) {
170 4
            throw new RedisProxyException("Error for command '$name', use getPrevious() for more info", 1484162284, $e);
171
        }
172 256
        return $this->transformResult($result);
173
    }
174
175
    /**
176
     * @param int $database
177
     * @return boolean true on success
178
     * @throws RedisProxyException on failure
179
     */
180 256
    public function select($database)
181
    {
182 256
        $this->prepareDriver();
183 256
        if (!$this->isConnected()) {
184 256
            $this->connect($this->host, $this->port, $this->timeout);
185 128
        }
186 256
        if ($database == $this->selectedDatabase) {
187 256
            return true;
188
        }
189
        try {
190 12
            $result = $this->driver->select($database);
191 7
        } catch (Exception $e) {
192 2
            throw new RedisProxyException('Invalid DB index');
193
        }
194 10
        $result = $this->transformResult($result);
195 10
        if ($result === false) {
196 2
            throw new RedisProxyException('Invalid DB index');
197
        }
198 8
        $this->database = $database;
199 8
        $this->selectedDatabase = $database;
200 8
        return $result;
201
    }
202
203
    /**
204
     * @param string|null $section
205
     * @return array
206
     */
207 8
    public function info($section = null)
208
    {
209 8
        $this->init();
210 8
        $section = $section ? strtolower($section) : $section;
211 8
        $result = $section === null ? $this->driver->info() : $this->driver->info($section);
212
213 8
        $databases = $section === null || $section === 'keyspace' ? $this->config('get', 'databases')['databases'] : null;
214 8
        $groupedResult = InfoHelper::createInfoArray($this, $result, $databases);
215 8
        if ($section === null) {
216 4
            return $groupedResult;
217
        }
218 8
        if (isset($groupedResult[$section])) {
219 4
            return $groupedResult[$section];
220
        }
221 4
        throw new RedisProxyException('Info section "' . $section . '" doesn\'t exist');
222
    }
223
224
    /**
225
     * Determine if a key exists
226
     * @param string $key
227
     * @return boolean
228
     */
229 4
    public function exists($key)
230
    {
231 4
        $this->init();
232 4
        $result = $this->driver->exists($key);
233 4
        return (bool)$result;
234
    }
235
236
    /**
237
     * @param string $key
238
     * @return string|null
239
     */
240 4
    public function type($key)
241
    {
242 4
        $this->init();
243 4
        $result = $this->driver->type($key);
244 4
        $result = $this->actualDriver() === self::DRIVER_PREDIS && $result instanceof Status ? $result->getPayload() : $result;
245 4
        return isset($this->redisTypeMap[$this->actualDriver()][$result]) ? $this->redisTypeMap[$this->actualDriver()][$result] : null;
246
    }
247
248
    /**
249
     * Get the value of a key
250
     * @param string $key
251
     * @return string|null null if key not set
252
     */
253 88
    public function get($key)
254
    {
255 88
        $this->init();
256 88
        $result = $this->driver->get($key);
257 88
        return $this->convertFalseToNull($result);
258
    }
259
260
    /**
261
     * Set the string value of a key and return its old value
262
     * @param string $key
263
     * @param string $value
264
     * @return string|null null if key was not set before
265
     */
266 4
    public function getset($key, $value)
267
    {
268 4
        $this->init();
269 4
        $result = $this->driver->getset($key, $value);
270 4
        return $this->convertFalseToNull($result);
271
    }
272
273
    /**
274
     * Set a key's time to live in seconds
275
     * @param string $key
276
     * @param int $seconds
277
     * @return boolean true if the timeout was set, false if key does not exist or the timeout could not be set
278
     */
279 12
    public function expire($key, $seconds)
280
    {
281 12
        $this->init();
282 12
        $result = $this->driver->expire($key, $seconds);
283 12
        return (bool)$result;
284
    }
285
286
    /**
287
     * Set a key's time to live in milliseconds
288
     * @param string $key
289
     * @param int $miliseconds
290
     * @return boolean true if the timeout was set, false if key does not exist or the timeout could not be set
291
     */
292 8
    public function pexpire($key, $miliseconds)
293
    {
294 8
        $this->init();
295 8
        $result = $this->driver->pexpire($key, $miliseconds);
296 8
        return (bool)$result;
297
    }
298
299
    /**
300
     * Set the expiration for a key as a UNIX timestamp
301
     * @param string $key
302
     * @param int $timestamp
303
     * @return boolean true if the timeout was set, false if key does not exist or the timeout could not be set
304
     */
305 4
    public function expireat($key, $timestamp)
306
    {
307 4
        $this->init();
308 4
        $result = $this->driver->expireat($key, $timestamp);
309 4
        return (bool)$result;
310
    }
311
312
    /**
313
     * Set the expiration for a key as a UNIX timestamp specified in milliseconds
314
     * @param string $key
315
     * @param int $milisecondsTimestamp
316
     * @return boolean true if the timeout was set, false if key does not exist or the timeout could not be set
317
     */
318 4
    public function pexpireat($key, $milisecondsTimestamp)
319
    {
320 4
        $this->init();
321 4
        $result = $this->driver->pexpireat($key, $milisecondsTimestamp);
322 4
        return (bool)$result;
323
    }
324
325
    /**
326
     * Set the value and expiration in milliseconds of a key
327
     * @param string $key
328
     * @param int $miliseconds
329
     * @param string $value
330
     * @return boolean
331
     */
332 4
    public function psetex($key, $miliseconds, $value)
333
    {
334 4
        $this->init();
335 4
        $result = $this->driver->psetex($key, $miliseconds, $value);
336 4
        if ($result == '+OK') {
337 2
            return true;
338
        }
339 2
        return $this->transformResult($result);
340
    }
341
342
    /**
343
     * Remove the expiration from a key
344
     * @param string $key
345
     * @return boolean
346
     */
347 4
    public function persist($key)
348
    {
349 4
        $this->init();
350 4
        $result = $this->driver->persist($key);
351 4
        return (bool)$result;
352
    }
353
354
    /**
355
     * Set the value of a key, only if the key does not exist
356
     * @param string $key
357
     * @param string $value
358
     * @return boolean true if the key was set, false if the key was not set
359
     */
360 4
    public function setnx($key, $value)
361
    {
362 4
        $this->init();
363 4
        $result = $this->driver->setnx($key, $value);
364 4
        return (bool)$result;
365
    }
366
367
    /**
368
     * Delete a key(s)
369
     * @param array $keys
370
     * @return int number of deleted keys
371
     */
372 32
    public function del(...$keys)
373
    {
374 32
        $this->prepareArguments('del', ...$keys);
375 24
        $this->init();
376 24
        return $this->driver->del(...$keys);
377
    }
378
379
    /**
380
     * Delete a key(s)
381
     * @param array $keys
382
     * @return int number of deleted keys
383
     */
384 12
    public function delete(...$keys)
385
    {
386 12
        return $this->del(...$keys);
387
    }
388
389
    /**
390
     * Increment the integer value of a key by one
391
     * @param string $key
392
     * @return integer
393
     */
394 4
    public function incr($key)
395
    {
396 4
        $this->init();
397 4
        return $this->driver->incr($key);
398
    }
399
400
    /**
401
     * Increment the integer value of a key by the given amount
402
     * @param string $key
403
     * @param integer $increment
404
     * @return integer
405
     */
406 4
    public function incrby($key, $increment = 1)
407
    {
408 4
        $this->init();
409 4
        return $this->driver->incrby($key, (int)$increment);
410
    }
411
412
    /**
413
     * Increment the float value of a key by the given amount
414
     * @param string $key
415
     * @param float $increment
416
     * @return float
417
     */
418 8
    public function incrbyfloat($key, $increment = 1)
419
    {
420 8
        $this->init();
421 8
        return $this->driver->incrbyfloat($key, $increment);
422
    }
423
424
    /**
425
     * Decrement the integer value of a key by one
426
     * @param string $key
427
     * @return integer
428
     */
429 4
    public function decr($key)
430
    {
431 4
        $this->init();
432 4
        return $this->driver->decr($key);
433
    }
434
435
    /**
436
     * Decrement the integer value of a key by the given number
437
     * @param string $key
438
     * @param integer $decrement
439
     * @return integer
440
     */
441 4
    public function decrby($key, $decrement = 1)
442
    {
443 4
        $this->init();
444 4
        return $this->driver->decrby($key, (int)$decrement);
445
    }
446
447
    /**
448
     * Decrement the float value of a key by the given amount
449
     * @param string $key
450
     * @param float $decrement
451
     * @return float
452
     */
453 4
    public function decrbyfloat($key, $decrement = 1)
454
    {
455 4
        return $this->incrbyfloat($key, (-1) * $decrement);
456
    }
457
458
    /**
459
     * Return a serialized version of the value stored at the specified key
460
     * @param string $key
461
     * @return string|null serialized value, null if key doesn't exist
462
     */
463 4
    public function dump($key)
464
    {
465 4
        $this->init();
466 4
        $result = $this->driver->dump($key);
467 4
        return $this->convertFalseToNull($result);
468
    }
469
470
    /**
471
     * Set multiple values to multiple keys
472
     * @param array $dictionary
473
     * @return boolean true on success
474
     * @throws RedisProxyException if number of arguments is wrong
475
     */
476 12 View Code Duplication
    public function mset(...$dictionary)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
477
    {
478 12
        $this->init();
479 12
        if (is_array($dictionary[0])) {
480 8
            $result = $this->driver->mset(...$dictionary);
481 8
            return $this->transformResult($result);
482
        }
483 8
        $dictionary = $this->prepareKeyValue($dictionary, 'mset');
484 4
        $result = $this->driver->mset($dictionary);
485 4
        return $this->transformResult($result);
486
    }
487
488
    /**
489
     * Multi get
490
     * @param array $keys
491
     * @return array Returns the values for all specified keys. For every key that does not hold a string value or does not exist, null is returned
492
     */
493 4 View Code Duplication
    public function mget(...$keys)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
494
    {
495 4
        $keys = array_unique($this->prepareArguments('mget', ...$keys));
496 4
        $this->init();
497 4
        $values = [];
498 4
        foreach ($this->driver->mget($keys) as $value) {
499 4
            $values[] = $this->convertFalseToNull($value);
500 2
        }
501 4
        return array_combine($keys, $values);
502
    }
503
504
    /**
505
     * Incrementally iterate the keys space
506
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
507
     * @param string $pattern pattern for keys, use * as wild card
508
     * @param int $count
509
     * @return array|boolean|null list of found keys, returns null if $iterator is 0 or '0'
510
     */
511 4 View Code Duplication
    public function scan(&$iterator, $pattern = null, $count = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
512
    {
513 4
        if ((string)$iterator === '0') {
514 4
            return null;
515
        }
516 4
        $this->init();
517 4
        if ($this->actualDriver() === self::DRIVER_PREDIS) {
518 2
            $returned = $this->driver->scan($iterator, ['match' => $pattern, 'count' => $count]);
519 2
            $iterator = $returned[0];
520 2
            return $returned[1];
521
        }
522 2
        return $this->driver->scan($iterator, $pattern, $count);
523
    }
524
525
    /**
526
     * Get the value of a hash field
527
     * @param string $key
528
     * @param string $field
529
     * @return string|null null if hash field is not set
530
     */
531 16
    public function hget($key, $field)
532
    {
533 16
        $this->init();
534 16
        $result = $this->driver->hget($key, $field);
535 16
        return $this->convertFalseToNull($result);
536
    }
537
538
    /**
539
     * Delete one or more hash fields, returns number of deleted fields
540
     * @param array $key
541
     * @param array $fields
542
     * @return int
543
     */
544 8
    public function hdel($key, ...$fields)
545
    {
546 8
        $fields = $this->prepareArguments('hdel', ...$fields);
547 8
        $this->init();
548 8
        return $this->driver->hdel($key, ...$fields);
549
    }
550
551
    /**
552
     * Increment the integer value of hash field by given number
553
     * @param string $key
554
     * @param string $field
555
     * @param int $increment
556
     * @return int
557
     */
558 4
    public function hincrby($key, $field, $increment = 1)
559
    {
560 4
        $this->init();
561 4
        return $this->driver->hincrby($key, $field, (int)$increment);
562
    }
563
564
    /**
565
     * Increment the float value of hash field by given amount
566
     * @param string $key
567
     * @param string $field
568
     * @param float $increment
569
     * @return float
570
     */
571 4
    public function hincrbyfloat($key, $field, $increment = 1)
572
    {
573 4
        $this->init();
574 4
        return $this->driver->hincrbyfloat($key, $field, $increment);
575
    }
576
577
    /**
578
     * Set multiple values to multiple hash fields
579
     * @param string $key
580
     * @param array $dictionary
581
     * @return boolean true on success
582
     * @throws RedisProxyException if number of arguments is wrong
583
     */
584 12 View Code Duplication
    public function hmset($key, ...$dictionary)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
585
    {
586 12
        $this->init();
587 12
        if (is_array($dictionary[0])) {
588 8
            $result = $this->driver->hmset($key, ...$dictionary);
589 8
            return $this->transformResult($result);
590
        }
591 8
        $dictionary = $this->prepareKeyValue($dictionary, 'hmset');
592 4
        $result = $this->driver->hmset($key, $dictionary);
593 4
        return $this->transformResult($result);
594
    }
595
596
    /**
597
     * Multi hash get
598
     * @param string $key
599
     * @param array $fields
600
     * @return array Returns the values for all specified fields. For every field that does not hold a string value or does not exist, null is returned
601
     */
602 4 View Code Duplication
    public function hmget($key, ...$fields)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
603
    {
604 4
        $fields = array_unique($this->prepareArguments('hmget', ...$fields));
605 4
        $this->init();
606 4
        $values = [];
607 4
        foreach ($this->driver->hmget($key, $fields) as $value) {
608 4
            $values[] = $this->convertFalseToNull($value);
609 2
        }
610 4
        return array_combine($fields, $values);
611
    }
612
613
    /**
614
     * Incrementally iterate hash fields and associated values
615
     * @param string $key
616
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
617
     * @param string $pattern pattern for fields, use * as wild card
618
     * @param int $count
619
     * @return array|boolean|null list of found fields with associated values, returns null if $iterator is 0 or '0'
620
     */
621 4 View Code Duplication
    public function hscan($key, &$iterator, $pattern = null, $count = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
622
    {
623 4
        if ((string)$iterator === '0') {
624 4
            return null;
625
        }
626 4
        $this->init();
627 4
        if ($this->actualDriver() === self::DRIVER_PREDIS) {
628 2
            $returned = $this->driver->hscan($key, $iterator, ['match' => $pattern, 'count' => $count]);
629 2
            $iterator = $returned[0];
630 2
            return $returned[1];
631
        }
632 2
        return $this->driver->hscan($key, $iterator, $pattern, $count);
633
    }
634
635
    /**
636
     * Add one or more members to a set
637
     * @param string $key
638
     * @param array $members
639
     * @return int number of new members added to set
640
     */
641 16
    public function sadd($key, ...$members)
642
    {
643 16
        $members = $this->prepareArguments('sadd', ...$members);
644 16
        $this->init();
645 16
        return $this->driver->sadd($key, ...$members);
646
    }
647
648
    /**
649
     * Remove and return one or multiple random members from a set
650
     * @param string $key
651
     * @param int $count number of members
652
     * @return mixed string if $count is null or 1 and $key exists, array if $count > 1 and $key exists, null if $key doesn't exist
653
     */
654 4
    public function spop($key, $count = 1)
655
    {
656 4
        $this->init();
657 4
        if ($count == 1 || $count === null) {
658 4
            $result = $this->driver->spop($key);
659 4
            return $this->convertFalseToNull($result);
660
        }
661
662 4
        $members = [];
663 4
        for ($i = 0; $i < $count; ++$i) {
664 4
            $member = $this->driver->spop($key);
665 4
            if (!$member) {
666 4
                break;
667
            }
668 4
            $members[] = $member;
669 2
        }
670 4
        return empty($members) ? null : $members;
671
    }
672
673
    /**
674
     * Incrementally iterate Set elements
675
     * @param string $key
676
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
677
     * @param string $pattern pattern for member's values, use * as wild card
678
     * @param int $count
679
     * @return array|boolean|null list of found members, returns null if $iterator is 0 or '0'
680
     */
681 4 View Code Duplication
    public function sscan($key, &$iterator, $pattern = null, $count = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
682
    {
683 4
        if ((string)$iterator === '0') {
684 4
            return null;
685
        }
686 4
        $this->init();
687 4
        if ($this->actualDriver() === self::DRIVER_PREDIS) {
688 2
            $returned = $this->driver->sscan($key, $iterator, ['match' => $pattern, 'count' => $count]);
689 2
            $iterator = $returned[0];
690 2
            return $returned[1];
691
        }
692 2
        return $this->driver->sscan($key, $iterator, $pattern, $count);
693
    }
694
695
    /**
696
     * Prepend one or multiple values to a list
697
     * @param string $key
698
     * @param array $elements
699
     * @return int the length of the list after the push operations
700
     */
701 28
    public function lpush($key, ...$elements)
702
    {
703 28
        $elements = $this->prepareArguments('lpush', ...$elements);
704 28
        $this->init();
705 28
        return $this->driver->lpush($key, ...$elements);
706
    }
707
708
    /**
709
     * Append one or multiple values to a list
710
     * @param string $key
711
     * @param array $elements
712
     * @return int the length of the list after the push operations
713
     */
714 12
    public function rpush($key, ...$elements)
715
    {
716 12
        $elements = $this->prepareArguments('rpush', ...$elements);
717 12
        $this->init();
718 12
        return $this->driver->rpush($key, ...$elements);
719
    }
720
721
    /**
722
     * Remove and get the first element in a list
723
     * @param string $key
724
     * @return string|null
725
     */
726 4
    public function lpop($key)
727
    {
728 4
        $this->init();
729 4
        $result = $this->driver->lpop($key);
730 4
        return $this->convertFalseToNull($result);
731
    }
732
733
    /**
734
     * Remove and get the last element in a list
735
     * @param string $key
736
     * @return string|null
737
     */
738 4
    public function rpop($key)
739
    {
740 4
        $this->init();
741 4
        $result = $this->driver->rpop($key);
742 4
        return $this->convertFalseToNull($result);
743
    }
744
745
    /**
746
     * Get an element from a list by its index
747
     * @param string $key
748
     * @param int $index zero-based, so 0 means the first element, 1 the second element and so on. -1 means the last element, -2 means the penultimate and so forth
749
     * @return string|null
750
     */
751 12
    public function lindex($key, $index)
752
    {
753 12
        $this->init();
754 12
        $result = $this->driver->lindex($key, $index);
755 12
        return $this->convertFalseToNull($result);
756
    }
757
758
    /**
759
     * Add one or more members to a sorted set, or update its score if it already exists
760
     * @param string $key
761
     * @param array $dictionary (score1, member1[, score2, member2]) or associative array: [member1 => score1, member2 => score2]
762
     * @return int
763
     */
764 20
    public function zadd($key, ...$dictionary)
765
    {
766 20
        $this->init();
767 20
        if (is_array($dictionary[0])) {
768 12
            $return = 0;
769 12
            foreach ($dictionary[0] as $member => $score) {
770 12
                $res = $this->zadd($key, $score, $member);
771 12
                $return += $res;
772 6
            }
773 12
            return $return;
774
        }
775 20
        return $this->driver->zadd($key, ...$dictionary);
776
    }
777
778
    /**
779
     * Return a range of members in a sorted set, by index
780
     * @param string $key
781
     * @param int $start
782
     * @param int $stop
783
     * @param boolean $withscores
784
     * @return array
785
     */
786 4 View Code Duplication
    public function zrange($key, $start, $stop, $withscores = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
787
    {
788 4
        $this->init();
789 4
        if ($this->actualDriver() === self::DRIVER_PREDIS) {
790 2
            return $this->driver->zrange($key, $start, $stop, ['WITHSCORES' => $withscores]);
791
        }
792 2
        return $this->driver->zrange($key, $start, $stop, $withscores);
793
    }
794
795
    /**
796
     * Incrementally iterate Sorted set elements
797
     * @param string $key
798
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
799
     * @param string $pattern pattern for member's values, use * as wild card
800
     * @param int $count
801
     * @return array|boolean|null list of found members with their values, returns null if $iterator is 0 or '0'
802
     */
803 View Code Duplication
    public function zscan($key, &$iterator, $pattern = null, $count = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
804
    {
805
        if ((string)$iterator === '0') {
806
            return null;
807
        }
808
        $this->init();
809
        if ($this->actualDriver() === self::DRIVER_PREDIS) {
810
            $returned = $this->driver->zscan($key, $iterator, ['match' => $pattern, 'count' => $count]);
811
            $iterator = $returned[0];
812
            return $returned[1];
813
        }
814
        return $this->driver->zscan($key, $iterator, $pattern, $count);
815
    }
816
817
    /**
818
     * Return a range of members in a sorted set, by index, with scores ordered from high to low
819
     * @param string $key
820
     * @param int $start
821
     * @param int $stop
822
     * @param boolean $withscores
823
     * @return array
824
     */
825 4 View Code Duplication
    public function zrevrange($key, $start, $stop, $withscores = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
826
    {
827 4
        $this->init();
828 4
        if ($this->actualDriver() === self::DRIVER_PREDIS) {
829 2
            return $this->driver->zrevrange($key, $start, $stop, ['WITHSCORES' => $withscores]);
830
        }
831 2
        return $this->driver->zrevrange($key, $start, $stop, $withscores);
832
    }
833
834
    /**
835
     * Returns null instead of false for Redis driver
836
     * @param mixed $result
837
     * @return mixed
838
     */
839 128
    private function convertFalseToNull($result)
840
    {
841 128
        return $this->actualDriver() === self::DRIVER_REDIS && $result === false ? null : $result;
842
    }
843
844
    /**
845
     * Transforms Predis result Payload to boolean
846
     * @param mixed $result
847
     * @return mixed
848
     */
849 256
    private function transformResult($result)
850
    {
851 256
        if ($this->actualDriver() === self::DRIVER_PREDIS && $result instanceof Status) {
852 128
            $result = $result->getPayload() === 'OK';
853 64
        }
854 256
        return $result;
855
    }
856
857
    /**
858
     * Create array from input array - odd keys are used as keys, even keys are used as values
859
     * @param array $dictionary
860
     * @param string $command
861
     * @return array
862
     * @throws RedisProxyException if number of keys is not the same as number of values
863
     */
864 8
    private function prepareKeyValue(array $dictionary, $command)
865
    {
866 8
        $keys = array_values(array_filter($dictionary, function ($key) {
867 16
            return $key % 2 == 0;
868 16
        }, ARRAY_FILTER_USE_KEY));
869 16
        $values = array_values(array_filter($dictionary, function ($key) {
870 16
            return $key % 2 == 1;
871 16
        }, ARRAY_FILTER_USE_KEY));
872
873 16
        if (count($keys) != count($values)) {
874 8
            throw new RedisProxyException("Wrong number of arguments for $command command");
875
        }
876 8
        return array_combine($keys, $values);
877
    }
878
879 88
    private function prepareArguments($command, ...$params)
880
    {
881 88
        if (!isset($params[0])) {
882 8
            throw new RedisProxyException("Wrong number of arguments for $command command");
883
        }
884 80
        if (is_array($params[0])) {
885 32
            $params = $params[0];
886 16
        }
887 80
        return $params;
888
    }
889
}
890