Completed
Pull Request — master (#5)
by Michal
04:59
created

RedisProxy::pexpireat()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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