Completed
Push — master ( f11d61...5b7c34 )
by Michal
01:53
created

RedisProxy::zrevrank()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 1
cts 1
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
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 int zscore(string $key, string $member) Returns the score of member in the sorted set at key
31
 * @method int zrem(string $key, string $member) Removes the specified members from the sorted set stored at key. Non existing members are ignored
32
 * @method boolean flushall() Remove all keys from all databases
33
 * @method boolean flushdb() Remove all keys from the current database
34
 */
35
class RedisProxy
36
{
37
    const DRIVER_REDIS = 'redis';
38
39
    const DRIVER_PREDIS = 'predis';
40
41
    const TYPE_STRING = 'string';
42
43
    const TYPE_SET = 'set';
44
45
    const TYPE_HASH = 'hash';
46
47
    const TYPE_LIST = 'list';
48
49
    const TYPE_SORTED_SET = 'sorted_set';
50
51
    private $driver;
52
53
    private $host;
54
55
    private $port;
56
57
    private $database = 0;
58
59
    private $selectedDatabase = 0;
60
61
    private $timeout;
62
63
    private $supportedDrivers = [
64
        self::DRIVER_REDIS,
65
        self::DRIVER_PREDIS,
66
    ];
67
68
    private $driversOrder = [];
69
70
    private $redisTypeMap = [
71
        self::DRIVER_REDIS => [
72
            1 => self::TYPE_STRING,
73
            2 => self::TYPE_SET,
74
            3 => self::TYPE_LIST,
75
            4 => self::TYPE_SORTED_SET,
76
            5 => self::TYPE_HASH,
77
        ],
78
        self::DRIVER_PREDIS => [
79
            'string' => self::TYPE_STRING,
80
            'set' => self::TYPE_SET,
81
            'list' => self::TYPE_LIST,
82
            'zset' => self::TYPE_SORTED_SET,
83
            'hash' => self::TYPE_HASH,
84
        ],
85
    ];
86
87 260
    public function __construct($host, $port, $database = 0, $timeout = null)
88
    {
89 260
        $this->host = $host;
90 260
        $this->port = $port;
91 260
        $this->database = $database;
92 260
        $this->timeout = $timeout;
93 260
        $this->driversOrder = $this->supportedDrivers;
94 260
    }
95
96
    /**
97
     * Set driver priorities - default is 1. redis, 2. predis
98
     * @param array $driversOrder
99
     * @return RedisProxy
100
     * @throws RedisProxyException if some driver is not supported
101
     */
102 260
    public function setDriversOrder(array $driversOrder)
103
    {
104 260
        foreach ($driversOrder as $driver) {
105 258
            if (!in_array($driver, $this->supportedDrivers)) {
106 258
                throw new RedisProxyException('Driver "' . $driver . '" is not supported');
107
            }
108
        }
109 258
        $this->driversOrder = $driversOrder;
110 258
        return $this;
111
    }
112
113 258
    private function init()
114
    {
115 258
        $this->prepareDriver();
116 256
        $this->select($this->database);
117 256
    }
118
119 258
    private function prepareDriver()
120
    {
121 258
        if ($this->driver !== null) {
122 256
            return;
123
        }
124
125 258
        foreach ($this->driversOrder as $preferredDriver) {
126 256
            if ($preferredDriver === self::DRIVER_REDIS && extension_loaded('redis')) {
127 128
                $this->driver = new Redis();
128 128
                return;
129
            }
130 128
            if ($preferredDriver === self::DRIVER_PREDIS && class_exists('Predis\Client')) {
131 128
                $this->driver = new Client([
132 128
                    'host' => $this->host,
133 128
                    'port' => $this->port,
134
                ]);
135 128
                return;
136
            }
137
        }
138 2
        throw new RedisProxyException('No driver available');
139
    }
140
141
    /**
142
     * @return string|null
143
     */
144 258
    public function actualDriver()
145
    {
146 258
        if ($this->driver instanceof Redis) {
147 128
            return self::DRIVER_REDIS;
148
        }
149 134
        if ($this->driver instanceof Client) {
150 128
            return self::DRIVER_PREDIS;
151
        }
152 10
        return null;
153
    }
154
155 256
    private function connect($host, $port, $timeout = null)
156
    {
157 256
        return $this->driver->connect($host, $port, $timeout);
158
    }
159
160 256
    private function isConnected()
161
    {
162 256
        return $this->driver->isConnected();
163
    }
164
165 258
    public function __call($name, $arguments)
166
    {
167 258
        $this->init();
168 256
        $name = strtolower($name);
169
        try {
170 256
            $result = call_user_func_array([$this->driver, $name], $arguments);
171 4
        } catch (Exception $e) {
172 4
            throw new RedisProxyException("Error for command '$name', use getPrevious() for more info", 1484162284, $e);
173
        }
174 256
        return $this->transformResult($result);
175
    }
176
177
    /**
178
     * @param int $database
179
     * @return boolean true on success
180
     * @throws RedisProxyException on failure
181
     */
182 256
    public function select($database)
183
    {
184 256
        $this->prepareDriver();
185 256
        if (!$this->isConnected()) {
186 256
            $this->connect($this->host, $this->port, $this->timeout);
187
        }
188 256
        if ($database == $this->selectedDatabase) {
189 256
            return true;
190
        }
191
        try {
192 12
            $result = $this->driver->select($database);
193 2
        } catch (Exception $e) {
194 2
            throw new RedisProxyException('Invalid DB index');
195
        }
196 10
        $result = $this->transformResult($result);
197 10
        if ($result === false) {
198 2
            throw new RedisProxyException('Invalid DB index');
199
        }
200 8
        $this->database = $database;
201 8
        $this->selectedDatabase = $database;
202 8
        return $result;
203
    }
204
205
    /**
206
     * @param string|null $section
207
     * @return array
208
     */
209 8
    public function info($section = null)
210
    {
211 8
        $this->init();
212 8
        $section = $section ? strtolower($section) : $section;
213 8
        $result = $section === null ? $this->driver->info() : $this->driver->info($section);
214
215 8
        $databases = $section === null || $section === 'keyspace' ? $this->config('get', 'databases')['databases'] : null;
216 8
        $groupedResult = InfoHelper::createInfoArray($this, $result, $databases);
217 8
        if ($section === null) {
218 4
            return $groupedResult;
219
        }
220 8
        if (isset($groupedResult[$section])) {
221 4
            return $groupedResult[$section];
222
        }
223 4
        throw new RedisProxyException('Info section "' . $section . '" doesn\'t exist');
224
    }
225
226
    /**
227
     * Determine if a key exists
228
     * @param string $key
229
     * @return boolean
230
     */
231 4
    public function exists($key)
232
    {
233 4
        $this->init();
234 4
        $result = $this->driver->exists($key);
235 4
        return (bool)$result;
236
    }
237
238
    /**
239
     * @param string $key
240
     * @return string|null
241
     */
242 4
    public function type($key)
243
    {
244 4
        $this->init();
245 4
        $result = $this->driver->type($key);
246 4
        $result = $this->actualDriver() === self::DRIVER_PREDIS && $result instanceof Status ? $result->getPayload() : $result;
247 4
        return isset($this->redisTypeMap[$this->actualDriver()][$result]) ? $this->redisTypeMap[$this->actualDriver()][$result] : null;
248
    }
249
250
    /**
251
     * Get the value of a key
252
     * @param string $key
253
     * @return string|null null if key not set
254
     */
255 88
    public function get($key)
256
    {
257 88
        $this->init();
258 88
        $result = $this->driver->get($key);
259 88
        return $this->convertFalseToNull($result);
260
    }
261
262
    /**
263
     * Set the string value of a key and return its old value
264
     * @param string $key
265
     * @param string $value
266
     * @return string|null null if key was not set before
267
     */
268 4
    public function getset($key, $value)
269
    {
270 4
        $this->init();
271 4
        $result = $this->driver->getset($key, $value);
272 4
        return $this->convertFalseToNull($result);
273
    }
274
275
    /**
276
     * Set a key's time to live in seconds
277
     * @param string $key
278
     * @param int $seconds
279
     * @return boolean true if the timeout was set, false if key does not exist or the timeout could not be set
280
     */
281 12
    public function expire($key, $seconds)
282
    {
283 12
        $this->init();
284 12
        $result = $this->driver->expire($key, $seconds);
285 12
        return (bool)$result;
286
    }
287
288
    /**
289
     * Set a key's time to live in milliseconds
290
     * @param string $key
291
     * @param int $miliseconds
292
     * @return boolean true if the timeout was set, false if key does not exist or the timeout could not be set
293
     */
294 8
    public function pexpire($key, $miliseconds)
295
    {
296 8
        $this->init();
297 8
        $result = $this->driver->pexpire($key, $miliseconds);
298 8
        return (bool)$result;
299
    }
300
301
    /**
302
     * Set the expiration for a key as a UNIX timestamp
303
     * @param string $key
304
     * @param int $timestamp
305
     * @return boolean true if the timeout was set, false if key does not exist or the timeout could not be set
306
     */
307 4
    public function expireat($key, $timestamp)
308
    {
309 4
        $this->init();
310 4
        $result = $this->driver->expireat($key, $timestamp);
311 4
        return (bool)$result;
312
    }
313
314
    /**
315
     * Set the expiration for a key as a UNIX timestamp specified in milliseconds
316
     * @param string $key
317
     * @param int $milisecondsTimestamp
318
     * @return boolean true if the timeout was set, false if key does not exist or the timeout could not be set
319
     */
320 4
    public function pexpireat($key, $milisecondsTimestamp)
321
    {
322 4
        $this->init();
323 4
        $result = $this->driver->pexpireat($key, $milisecondsTimestamp);
324 4
        return (bool)$result;
325
    }
326
327
    /**
328
     * Set the value and expiration in milliseconds of a key
329
     * @param string $key
330
     * @param int $miliseconds
331
     * @param string $value
332
     * @return boolean
333
     */
334 4
    public function psetex($key, $miliseconds, $value)
335
    {
336 4
        $this->init();
337 4
        $result = $this->driver->psetex($key, $miliseconds, $value);
338 4
        if ($result == '+OK') {
339 2
            return true;
340
        }
341 2
        return $this->transformResult($result);
342
    }
343
344
    /**
345
     * Remove the expiration from a key
346
     * @param string $key
347
     * @return boolean
348
     */
349 4
    public function persist($key)
350
    {
351 4
        $this->init();
352 4
        $result = $this->driver->persist($key);
353 4
        return (bool)$result;
354
    }
355
356
    /**
357
     * Set the value of a key, only if the key does not exist
358
     * @param string $key
359
     * @param string $value
360
     * @return boolean true if the key was set, false if the key was not set
361
     */
362 4
    public function setnx($key, $value)
363
    {
364 4
        $this->init();
365 4
        $result = $this->driver->setnx($key, $value);
366 4
        return (bool)$result;
367
    }
368
369
    /**
370
     * Delete a key(s)
371
     * @param array $keys
372
     * @return int number of deleted keys
373
     */
374 32
    public function del(...$keys)
375
    {
376 32
        $this->prepareArguments('del', ...$keys);
377 24
        $this->init();
378 24
        return $this->driver->del(...$keys);
379
    }
380
381
    /**
382
     * Delete a key(s)
383
     * @param array $keys
384
     * @return int number of deleted keys
385
     */
386 12
    public function delete(...$keys)
387
    {
388 12
        return $this->del(...$keys);
389
    }
390
391
    /**
392
     * Increment the integer value of a key by one
393
     * @param string $key
394
     * @return integer
395
     */
396 4
    public function incr($key)
397
    {
398 4
        $this->init();
399 4
        return $this->driver->incr($key);
400
    }
401
402
    /**
403
     * Increment the integer value of a key by the given amount
404
     * @param string $key
405
     * @param integer $increment
406
     * @return integer
407
     */
408 4
    public function incrby($key, $increment = 1)
409
    {
410 4
        $this->init();
411 4
        return $this->driver->incrby($key, (int)$increment);
412
    }
413
414
    /**
415
     * Increment the float value of a key by the given amount
416
     * @param string $key
417
     * @param float $increment
418
     * @return float
419
     */
420 8
    public function incrbyfloat($key, $increment = 1)
421
    {
422 8
        $this->init();
423 8
        return $this->driver->incrbyfloat($key, $increment);
424
    }
425
426
    /**
427
     * Decrement the integer value of a key by one
428
     * @param string $key
429
     * @return integer
430
     */
431 4
    public function decr($key)
432
    {
433 4
        $this->init();
434 4
        return $this->driver->decr($key);
435
    }
436
437
    /**
438
     * Decrement the integer value of a key by the given number
439
     * @param string $key
440
     * @param integer $decrement
441
     * @return integer
442
     */
443 4
    public function decrby($key, $decrement = 1)
444
    {
445 4
        $this->init();
446 4
        return $this->driver->decrby($key, (int)$decrement);
447
    }
448
449
    /**
450
     * Decrement the float value of a key by the given amount
451
     * @param string $key
452
     * @param float $decrement
453
     * @return float
454
     */
455 4
    public function decrbyfloat($key, $decrement = 1)
456
    {
457 4
        return $this->incrbyfloat($key, (-1) * $decrement);
458
    }
459
460
    /**
461
     * Return a serialized version of the value stored at the specified key
462
     * @param string $key
463
     * @return string|null serialized value, null if key doesn't exist
464
     */
465 4
    public function dump($key)
466
    {
467 4
        $this->init();
468 4
        $result = $this->driver->dump($key);
469 4
        return $this->convertFalseToNull($result);
470
    }
471
472
    /**
473
     * Set multiple values to multiple keys
474
     * @param array $dictionary
475
     * @return boolean true on success
476
     * @throws RedisProxyException if number of arguments is wrong
477
     */
478 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...
479
    {
480 12
        $this->init();
481 12
        if (is_array($dictionary[0])) {
482 8
            $result = $this->driver->mset(...$dictionary);
483 8
            return $this->transformResult($result);
484
        }
485 8
        $dictionary = $this->prepareKeyValue($dictionary, 'mset');
486 4
        $result = $this->driver->mset($dictionary);
487 4
        return $this->transformResult($result);
488
    }
489
490
    /**
491
     * Multi get
492
     * @param array $keys
493
     * @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
494
     */
495 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...
496
    {
497 4
        $keys = array_unique($this->prepareArguments('mget', ...$keys));
498 4
        $this->init();
499 4
        $values = [];
500 4
        foreach ($this->driver->mget($keys) as $value) {
501 4
            $values[] = $this->convertFalseToNull($value);
502
        }
503 4
        return array_combine($keys, $values);
504
    }
505
506
    /**
507
     * Incrementally iterate the keys space
508
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
509
     * @param string $pattern pattern for keys, use * as wild card
510
     * @param int $count
511
     * @return array|boolean|null list of found keys, returns null if $iterator is 0 or '0'
512
     */
513 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...
514
    {
515 4
        if ((string)$iterator === '0') {
516 4
            return null;
517
        }
518 4
        $this->init();
519 4
        if ($this->actualDriver() === self::DRIVER_PREDIS) {
520 2
            $returned = $this->driver->scan($iterator, ['match' => $pattern, 'count' => $count]);
521 2
            $iterator = $returned[0];
522 2
            return $returned[1];
523
        }
524 2
        return $this->driver->scan($iterator, $pattern, $count);
525
    }
526
527
    /**
528
     * Get the value of a hash field
529
     * @param string $key
530
     * @param string $field
531
     * @return string|null null if hash field is not set
532
     */
533 16
    public function hget($key, $field)
534
    {
535 16
        $this->init();
536 16
        $result = $this->driver->hget($key, $field);
537 16
        return $this->convertFalseToNull($result);
538
    }
539
540
    /**
541
     * Delete one or more hash fields, returns number of deleted fields
542
     * @param array $key
543
     * @param array $fields
544
     * @return int
545
     */
546 8
    public function hdel($key, ...$fields)
547
    {
548 8
        $fields = $this->prepareArguments('hdel', ...$fields);
549 8
        $this->init();
550 8
        return $this->driver->hdel($key, ...$fields);
551
    }
552
553
    /**
554
     * Increment the integer value of hash field by given number
555
     * @param string $key
556
     * @param string $field
557
     * @param int $increment
558
     * @return int
559
     */
560 4
    public function hincrby($key, $field, $increment = 1)
561
    {
562 4
        $this->init();
563 4
        return $this->driver->hincrby($key, $field, (int)$increment);
564
    }
565
566
    /**
567
     * Increment the float value of hash field by given amount
568
     * @param string $key
569
     * @param string $field
570
     * @param float $increment
571
     * @return float
572
     */
573 4
    public function hincrbyfloat($key, $field, $increment = 1)
574
    {
575 4
        $this->init();
576 4
        return $this->driver->hincrbyfloat($key, $field, $increment);
577
    }
578
579
    /**
580
     * Set multiple values to multiple hash fields
581
     * @param string $key
582
     * @param array $dictionary
583
     * @return boolean true on success
584
     * @throws RedisProxyException if number of arguments is wrong
585
     */
586 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...
587
    {
588 12
        $this->init();
589 12
        if (is_array($dictionary[0])) {
590 8
            $result = $this->driver->hmset($key, ...$dictionary);
591 8
            return $this->transformResult($result);
592
        }
593 8
        $dictionary = $this->prepareKeyValue($dictionary, 'hmset');
594 4
        $result = $this->driver->hmset($key, $dictionary);
595 4
        return $this->transformResult($result);
596
    }
597
598
    /**
599
     * Multi hash get
600
     * @param string $key
601
     * @param array $fields
602
     * @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
603
     */
604 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...
605
    {
606 4
        $fields = array_unique($this->prepareArguments('hmget', ...$fields));
607 4
        $this->init();
608 4
        $values = [];
609 4
        foreach ($this->driver->hmget($key, $fields) as $value) {
610 4
            $values[] = $this->convertFalseToNull($value);
611
        }
612 4
        return array_combine($fields, $values);
613
    }
614
615
    /**
616
     * Incrementally iterate hash fields and associated values
617
     * @param string $key
618
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
619
     * @param string $pattern pattern for fields, use * as wild card
620
     * @param int $count
621
     * @return array|boolean|null list of found fields with associated values, returns null if $iterator is 0 or '0'
622
     */
623 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...
624
    {
625 4
        if ((string)$iterator === '0') {
626 4
            return null;
627
        }
628 4
        $this->init();
629 4
        if ($this->actualDriver() === self::DRIVER_PREDIS) {
630 2
            $returned = $this->driver->hscan($key, $iterator, ['match' => $pattern, 'count' => $count]);
631 2
            $iterator = $returned[0];
632 2
            return $returned[1];
633
        }
634 2
        return $this->driver->hscan($key, $iterator, $pattern, $count);
635
    }
636
637
    /**
638
     * Add one or more members to a set
639
     * @param string $key
640
     * @param array $members
641
     * @return int number of new members added to set
642
     */
643 16
    public function sadd($key, ...$members)
644
    {
645 16
        $members = $this->prepareArguments('sadd', ...$members);
646 16
        $this->init();
647 16
        return $this->driver->sadd($key, ...$members);
648
    }
649
650
    /**
651
     * Remove and return one or multiple random members from a set
652
     * @param string $key
653
     * @param int $count number of members
654
     * @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
655
     */
656 4
    public function spop($key, $count = 1)
657
    {
658 4
        $this->init();
659 4
        if ($count == 1 || $count === null) {
660 4
            $result = $this->driver->spop($key);
661 4
            return $this->convertFalseToNull($result);
662
        }
663
664 4
        $members = [];
665 4
        for ($i = 0; $i < $count; ++$i) {
666 4
            $member = $this->driver->spop($key);
667 4
            if (!$member) {
668 4
                break;
669
            }
670 4
            $members[] = $member;
671
        }
672 4
        return empty($members) ? null : $members;
673
    }
674
675
    /**
676
     * Incrementally iterate Set elements
677
     * @param string $key
678
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
679
     * @param string $pattern pattern for member's values, use * as wild card
680
     * @param int $count
681
     * @return array|boolean|null list of found members, returns null if $iterator is 0 or '0'
682
     */
683 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...
684
    {
685 4
        if ((string)$iterator === '0') {
686 4
            return null;
687
        }
688 4
        $this->init();
689 4
        if ($this->actualDriver() === self::DRIVER_PREDIS) {
690 2
            $returned = $this->driver->sscan($key, $iterator, ['match' => $pattern, 'count' => $count]);
691 2
            $iterator = $returned[0];
692 2
            return $returned[1];
693
        }
694 2
        return $this->driver->sscan($key, $iterator, $pattern, $count);
695
    }
696
697
    /**
698
     * Prepend one or multiple values to a list
699
     * @param string $key
700
     * @param array $elements
701
     * @return int the length of the list after the push operations
702
     */
703 28
    public function lpush($key, ...$elements)
704
    {
705 28
        $elements = $this->prepareArguments('lpush', ...$elements);
706 28
        $this->init();
707 28
        return $this->driver->lpush($key, ...$elements);
708
    }
709
710
    /**
711
     * Append one or multiple values to a list
712
     * @param string $key
713
     * @param array $elements
714
     * @return int the length of the list after the push operations
715
     */
716 12
    public function rpush($key, ...$elements)
717
    {
718 12
        $elements = $this->prepareArguments('rpush', ...$elements);
719 12
        $this->init();
720 12
        return $this->driver->rpush($key, ...$elements);
721
    }
722
723
    /**
724
     * Remove and get the first element in a list
725
     * @param string $key
726
     * @return string|null
727
     */
728 4
    public function lpop($key)
729
    {
730 4
        $this->init();
731 4
        $result = $this->driver->lpop($key);
732 4
        return $this->convertFalseToNull($result);
733
    }
734
735
    /**
736
     * Remove and get the last element in a list
737
     * @param string $key
738
     * @return string|null
739
     */
740 4
    public function rpop($key)
741
    {
742 4
        $this->init();
743 4
        $result = $this->driver->rpop($key);
744 4
        return $this->convertFalseToNull($result);
745
    }
746
747
    /**
748
     * Get an element from a list by its index
749
     * @param string $key
750
     * @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
751
     * @return string|null
752
     */
753 12
    public function lindex($key, $index)
754
    {
755 12
        $this->init();
756 12
        $result = $this->driver->lindex($key, $index);
757 12
        return $this->convertFalseToNull($result);
758
    }
759
760
    /**
761
     * Add one or more members to a sorted set, or update its score if it already exists
762
     * @param string $key
763
     * @param array $dictionary (score1, member1[, score2, member2]) or associative array: [member1 => score1, member2 => score2]
764
     * @return int
765
     */
766 20
    public function zadd($key, ...$dictionary)
767
    {
768 20
        $this->init();
769 20
        if (is_array($dictionary[0])) {
770 12
            $return = 0;
771 12
            foreach ($dictionary[0] as $member => $score) {
772 12
                $res = $this->zadd($key, $score, $member);
773 12
                $return += $res;
774
            }
775 12
            return $return;
776
        }
777 20
        return $this->driver->zadd($key, ...$dictionary);
778
    }
779
780
    /**
781
     * Return a range of members in a sorted set, by index
782
     * @param string $key
783
     * @param int $start
784
     * @param int $stop
785
     * @param boolean $withscores
786
     * @return array
787
     */
788 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...
789
    {
790 4
        $this->init();
791 4
        if ($this->actualDriver() === self::DRIVER_PREDIS) {
792 2
            return $this->driver->zrange($key, $start, $stop, ['WITHSCORES' => $withscores]);
793
        }
794 2
        return $this->driver->zrange($key, $start, $stop, $withscores);
795
    }
796
797
    /**
798
     * Incrementally iterate Sorted set elements
799
     * @param string $key
800
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
801
     * @param string $pattern pattern for member's values, use * as wild card
802
     * @param int $count
803
     * @return array|boolean|null list of found members with their values, returns null if $iterator is 0 or '0'
804
     */
805 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...
806
    {
807
        if ((string)$iterator === '0') {
808
            return null;
809
        }
810
        $this->init();
811
        if ($this->actualDriver() === self::DRIVER_PREDIS) {
812
            $returned = $this->driver->zscan($key, $iterator, ['match' => $pattern, 'count' => $count]);
813
            $iterator = $returned[0];
814
            return $returned[1];
815
        }
816
        return $this->driver->zscan($key, $iterator, $pattern, $count);
817
    }
818
819
    /**
820
     * Return a range of members in a sorted set, by index, with scores ordered from high to low
821
     * @param string $key
822
     * @param int $start
823
     * @param int $stop
824
     * @param boolean $withscores
825
     * @return array
826
     */
827 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...
828
    {
829 4
        $this->init();
830 4
        if ($this->actualDriver() === self::DRIVER_PREDIS) {
831 2
            return $this->driver->zrevrange($key, $start, $stop, ['WITHSCORES' => $withscores]);
832
        }
833 2
        return $this->driver->zrevrange($key, $start, $stop, $withscores);
834
    }
835
836
    /**
837
     * Returns the rank of member in the sorted set stored at key, with the scores ordered from low to high. The rank (or index) is 0-based, which means that the member with the lowest score has rank 0
838
     * @param string $key
839
     * @param string $member
840
     * @return int|null Returns null if member does not exist in the sorted set or key does not exist
841 128
     */
842
    public function zrank($key, $member)
843 128
    {
844
        $this->init();
845
        $result = $this->driver->zrank($key, $member);
846
        return $this->convertFalseToNull($result);
847
    }
848
849
    /**
850
     * Returns the rank of member in the sorted set stored at key, with the scores ordered from high to low. The rank (or index) is 0-based, which means that the member with the highest score has rank 0
851 256
     * @param string $key
852
     * @param string $member
853 256
     * @return int|null Returns null if member does not exist in the sorted set or key does not exist
854 128
     */
855
    public function zrevrank($key, $member)
856 256
    {
857
        $this->init();
858
        $result = $this->driver->zrevrank($key, $member);
859
        return $this->convertFalseToNull($result);
860
    }
861
862
    /**
863
     * Returns null instead of false for Redis driver
864
     * @param mixed $result
865
     * @return mixed
866
     */
867
    private function convertFalseToNull($result)
868 16
    {
869 16
        return $this->actualDriver() === self::DRIVER_REDIS && $result === false ? null : $result;
870 16
    }
871 16
872 16
    /**
873 16
     * Transforms Predis result Payload to boolean
874
     * @param mixed $result
875 16
     * @return mixed
876 8
     */
877
    private function transformResult($result)
878 8
    {
879
        if ($this->actualDriver() === self::DRIVER_PREDIS && $result instanceof Status) {
880
            $result = $result->getPayload() === 'OK';
881 88
        }
882
        return $result;
883 88
    }
884 8
885
    /**
886 80
     * Create array from input array - odd keys are used as keys, even keys are used as values
887 32
     * @param array $dictionary
888
     * @param string $command
889 80
     * @return array
890
     * @throws RedisProxyException if number of keys is not the same as number of values
891
     */
892
    private function prepareKeyValue(array $dictionary, $command)
893
    {
894
        $keys = array_values(array_filter($dictionary, function ($key) {
895
            return $key % 2 == 0;
896
        }, ARRAY_FILTER_USE_KEY));
897
        $values = array_values(array_filter($dictionary, function ($key) {
898
            return $key % 2 == 1;
899
        }, ARRAY_FILTER_USE_KEY));
900
901
        if (count($keys) != count($values)) {
902
            throw new RedisProxyException("Wrong number of arguments for $command command");
903
        }
904
        return array_combine($keys, $values);
905
    }
906
907
    private function prepareArguments($command, ...$params)
908
    {
909
        if (!isset($params[0])) {
910
            throw new RedisProxyException("Wrong number of arguments for $command command");
911
        }
912
        if (is_array($params[0])) {
913
            $params = $params[0];
914
        }
915
        return $params;
916
    }
917
}
918