Completed
Pull Request — master (#2)
by Michal
02:12
created

RedisProxy::get()   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 1
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 mixed dbsize() Return the number of keys in the selected database
13
 * @method boolean set(string $key, string $value) Set the string value of a key
14
 * @method array keys(string $pattern) Find all keys matching the given pattern
15
 * @method array mget(array $keys) Multi get - Returns the values of all specified keys. For every key that does not hold a string value or does not exist, FALSE is returned.
16
 * @method integer hset(string $key, string $field, string $value) Set the string value of a hash field
17
 * @method array hkeys(string $key) Get all fields in hash (without values)
18
 * @method array hgetall(string $key) Get all fields and values in hash
19
 * @method integer hlen(string $key) Get the number of fields in hash
20
 * @method boolean flushall() Remove all keys from all databases
21
 * @method boolean flushdb() Remove all keys from the current database
22
 */
23
class RedisProxy
24
{
25
    const DRIVER_REDIS = 'redis';
26
27
    const DRIVER_PREDIS = 'predis';
28
29
    private $driver;
30
31
    private $host;
32
33
    private $port;
34
35
    private $database = 0;
36
37
    private $timeout;
38
39
    private $supportedDrivers = [
40
        self::DRIVER_REDIS,
41
        self::DRIVER_PREDIS,
42
    ];
43
44
    private $driversOrder = [];
45
46 112
    public function __construct($host, $port, $timeout = null)
47
    {
48 112
        $this->host = $host;
49 112
        $this->port = $port;
50 112
        $this->timeout = $timeout;
51 112
        $this->driversOrder = $this->supportedDrivers;
52 112
    }
53
54 112
    public function setDriversOrder(array $driversOrder)
55
    {
56 112
        if (empty($driversOrder)) {
57 29
            throw new RedisProxyException('You need to set at least one driver');
58
        }
59 110
        foreach ($driversOrder as $driver) {
60 110
            if (!in_array($driver, $this->supportedDrivers)) {
61 56
                throw new RedisProxyException('Driver "' . $driver . '" is not supported');
62
            }
63 54
        }
64 108
        $this->driversOrder = $driversOrder;
65 108
        return $this;
66
    }
67
68 108
    private function init()
69
    {
70 108
        $this->prepareDriver();
71 108
        $this->select($this->database);
72 108
    }
73
74 108
    private function prepareDriver()
75
    {
76 108
        if ($this->driver !== null) {
77 108
            return;
78
        }
79
80 108
        foreach ($this->driversOrder as $preferredDriver) {
81 108
            if ($preferredDriver === self::DRIVER_REDIS && extension_loaded('redis')) {
82 54
                $this->driver = new Redis();
83 54
                return;
84
            }
85 54
            if ($preferredDriver === self::DRIVER_PREDIS && class_exists('Predis\Client')) {
86 54
                $this->driver = new Client();
87 54
                return;
88
            }
89
        }
90
        throw new RedisProxyException('No redis library loaded (ext-redis or predis)');
91
    }
92
93 108
    private function connect($host, $port, $timeout = null)
94
    {
95 108
        return $this->driver->connect($host, $port, $timeout);
96
    }
97
98 108
    private function isConnected()
99
    {
100 108
        return $this->driver->isConnected();
101
    }
102
103 108
    public function __call($name, $arguments)
104
    {
105 108
        $this->init();
106 108
        $name = strtolower($name);
107
        try {
108 108
            $result = call_user_func_array([$this->driver, $name], $arguments);
109 56
        } catch (Exception $e) {
110 4
            throw new RedisProxyException("Error for command '$name', use getPrevious() for more info", 1484162284, $e);
111
        }
112 108
        return $this->transformResult($result);
113
    }
114
115
    /**
116
     * @param integer $database
117
     * @return boolean true on success
118
     * @throws RedisProxyException on failure
119
     */
120 108
    public function select($database)
121
    {
122 108
        $this->prepareDriver();
123 108
        if (!$this->isConnected()) {
124 108
            $this->connect($this->host, $this->port, $this->timeout);
125 54
        }
126 108
        if ($database == $this->database) {
127 108
            return true;
128
        }
129
        try {
130 12
            $result = $this->driver->select($database);
131 7
        } catch (Exception $e) {
132 2
            throw new RedisProxyException('Invalid DB index');
133
        }
134 10
        $result = $this->transformResult($result);
135 10
        if ($result === false) {
136 2
            throw new RedisProxyException('Invalid DB index');
137
        }
138 8
        $this->database = $database;
139 8
        return $result;
140
    }
141
142
    /**
143
     * @param string|null $section
144
     * @return array
145
     */
146 8
    public function info($section = null)
147
    {
148 8
        $this->init();
149 8
        $section = $section ? strtolower($section) : $section;
150 8
        $result = $section === null ? $this->driver->info() : $this->driver->info($section);
151
152 8
        $databases = $section === null || $section === 'keyspace' ? $this->config('get', 'databases')['databases'] : null;
153 8
        $groupedResult = InfoHelper::createInfoArray($this->driver, $result, $databases);
154 8
        if ($section === null) {
155 4
            return $groupedResult;
156
        }
157 8
        if (isset($groupedResult[$section])) {
158 4
            return $groupedResult[$section];
159
        }
160 4
        throw new RedisProxyException('Info section "' . $section . '" doesn\'t exist');
161
    }
162
163
    /**
164
     * Set multiple values to multiple keys
165
     * @param array $dictionary
166
     * @return boolean true on success
167
     * @throws RedisProxyException if number of arguments is wrong
168
     */
169 12 View Code Duplication
    public function mset(...$dictionary)
1 ignored issue
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...
170
    {
171 12
        if (is_array($dictionary[0])) {
172 8
            $result = $this->driver->mset(...$dictionary);
173 8
            return $this->transformResult($result);
174
        }
175 8
        $dictionary = $this->prepareKeyValue($dictionary, 'mset');
176 4
        $result = $this->driver->mset($dictionary);
177 4
        return $this->transformResult($result);
178
    }
179
180
    /**
181
     * @param string $key
182
     * @return string|null null if hash field is not set
183
     */
184 16
    public function get($key)
185
    {
186 16
        $this->init();
187 16
        $result = $this->driver->get($key);
188 16
        return $this->convertFalseToNull($result);
189
    }
190
191
    /**
192
     * Delete a key(s)
193
     * @param array ...$keys
194
     * @return integer number of deleted keys
195
     */
196 20
    public function del(...$keys)
197
    {
198 20
        $this->init();
199 20
        return $this->driver->del(...$keys);
200
    }
201
202
    /**
203
     * Delete a key(s)
204
     * @param array ...$keys
205
     * @return integer number of deleted keys
206
     */
207 8
    public function delete(...$keys)
208
    {
209 8
        return $this->del(...$keys);
210
    }
211
212
    /**
213
     * Incrementally iterate the keys space
214
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
215
     * @param string $pattern pattern for keys, use * as wild card
216
     * @param integer $count
217
     * @return array|null list of found keys, returns null if $iterator is 0 or '0'
218
     */
219 4 View Code Duplication
    public function scan(&$iterator, $pattern = null, $count = null)
220
    {
221 4
        if ((string)$iterator === '0') {
222 4
            return null;
223
        }
224 4
        $this->init();
225 4
        if ($this->driver instanceof Client) {
226 2
            $returned = $this->driver->scan($iterator, ['match' => $pattern, 'count' => $count]);
227 2
            $iterator = $returned[0];
228 2
            return $returned[1];
229
        }
230 2
        $data = $this->driver->scan($iterator, $pattern, $count);
231 2
        return $data === false ? null : $data;
0 ignored issues
show
Bug Compatibility introduced by
The expression $data === false ? null : $data; of type array|boolean adds the type boolean to the return on line 231 which is incompatible with the return type documented by RedisProxy\RedisProxy::scan of type array|null.
Loading history...
232
    }
233
234
    /**
235
     * Get the value of a hash field
236
     * @param string $key
237
     * @param string $field
238
     * @return string|null null if hash field is not set
239
     */
240 16
    public function hget($key, $field)
241
    {
242 16
        $this->init();
243 16
        $result = $this->driver->hget($key, $field);
244 16
        return $this->convertFalseToNull($result);
245
    }
246
247
    /**
248
     * Delete one or more hash fields, returns number of deleted fields
249
     * @param array $key
250
     * @param array ...$fields
251
     * @return integer
252
     */
253 8
    public function hdel($key, ...$fields)
254
    {
255 8
        if (is_array($fields[0])) {
256 4
            $fields = $fields[0];
257 2
        }
258 8
        return $this->driver->hdel($key, ...$fields);
259
    }
260
261
    /**
262
     * Increment the integer value of hash field by given number
263
     * @param string $key
264
     * @param string $field
265
     * @param integer $increment
266
     * @return integer
267
     */
268 4
    public function hincrby($key, $field, $increment = 1)
269
    {
270 4
        return $this->driver->hincrby($key, $field, (int)$increment);
271
    }
272
273
    /**
274
     * Increment the float value of hash field by given amount
275
     * @param string $key
276
     * @param string $field
277
     * @param float $increment
278
     * @return float
279
     */
280 4
    public function hincrbyfloat($key, $field, $increment = 1)
281
    {
282 4
        return $this->driver->hincrbyfloat($key, $field, $increment);
283
    }
284
285
    /**
286
     * Set multiple values to multiple hash fields
287
     * @param string $key
288
     * @param array $dictionary
289
     * @return boolean true on success
290
     * @throws RedisProxyException if number of arguments is wrong
291
     */
292 12 View Code Duplication
    public function hmset($key, ...$dictionary)
1 ignored issue
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...
293
    {
294 12
        if (is_array($dictionary[0])) {
295 8
            $result = $this->driver->hmset($key, ...$dictionary);
296 8
            return $this->transformResult($result);
297
        }
298 8
        $dictionary = $this->prepareKeyValue($dictionary, 'hmset');
299 4
        $result = $this->driver->hmset($key, $dictionary);
300 4
        return $this->transformResult($result);
301
    }
302
303
    /**
304
     * Incrementally iterate hash fields and associated values
305
     * @param string $key
306
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
307
     * @param string $pattern pattern for fields, use * as wild card
308
     * @param integer $count
309
     * @return array|null list of found fields with associated values, returns null if $iterator is 0 or '0'
310
     */
311 4 View Code Duplication
    public function hscan($key, &$iterator, $pattern = null, $count = null)
312
    {
313 4
        if ((string)$iterator === '0') {
314 4
            return null;
315
        }
316 4
        $this->init();
317 4
        if ($this->driver instanceof Client) {
318 2
            $returned = $this->driver->hscan($key, $iterator, ['match' => $pattern, 'count' => $count]);
319 2
            $iterator = $returned[0];
320 2
            return $returned[1];
321
        }
322 2
        $data = $this->driver->hscan($key, $iterator, $pattern, $count);
323 2
        return $data === false ? null : $data;
324
    }
325
326
    /**
327
     * Add one or more members to a set
328
     * @param string $key
329
     * @param array ...$members
330
     * @return integer number of new members added to set
331
     */
332 12
    public function sadd($key, ...$members)
333
    {
334 12
        if (is_array($members[0])) {
335 8
            $members = $members[0];
336 4
        }
337 12
        return $this->driver->sadd($key, ...$members);
338
    }
339
340
    /**
341
     * Remove and return one or multiple random members from a set
342
     * @param string $key
343
     * @param integer $count number of members
344
     * @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
345
     */
346 4
    public function spop($key, $count = 1)
347
    {
348 4
        if ($count == 1 || $count === null) {
349 4
            $result = $this->driver->spop($key);
350 4
            return $this->convertFalseToNull($result);
351
        }
352
353 4
        $members = [];
354 4
        for ($i = 0; $i < $count; ++$i) {
355 4
            $member = $this->driver->spop($key);
356 4
            if (!$member) {
357 4
                break;
358
            }
359 4
            $members[] = $member;
360 2
        }
361 4
        return empty($members) ? null : $members;
362
    }
363
364
    /**
365
     * Incrementally iterate Set elements
366
     * @param string $key
367
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
368
     * @param string $pattern pattern for member's values, use * as wild card
369
     * @param integer $count
370
     * @return array|null list of found members, returns null if $iterator is 0 or '0'
371
     */
372 4 View Code Duplication
    public function sscan($key, &$iterator, $pattern = null, $count = null)
373
    {
374 4
        if ((string)$iterator === '0') {
375 4
            return null;
376
        }
377 4
        $this->init();
378 4
        if ($this->driver instanceof Client) {
379 2
            $returned = $this->driver->sscan($key, $iterator, ['match' => $pattern, 'count' => $count]);
380 2
            $iterator = $returned[0];
381 2
            return $returned[1];
382
        }
383 2
        $data = $this->driver->sscan($key, $iterator, $pattern, $count);
384 2
        return $data === false ? null : $data;
0 ignored issues
show
Bug Compatibility introduced by
The expression $data === false ? null : $data; of type array|boolean adds the type boolean to the return on line 384 which is incompatible with the return type documented by RedisProxy\RedisProxy::sscan of type array|null.
Loading history...
385
    }
386
387
    /**
388
     * Incrementally iterate sorted sets elements and associated scores
389
     * @param string $key
390
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
391
     * @param string $pattern pattern for element's values, use * as wild card
392
     * @param integer $count
393
     * @return array|null list of found elements, returns null if $iterator is 0 or '0'
394
     */
395 View Code Duplication
    public function zscan($key, &$iterator, $pattern = null, $count = null)
396
    {
397
        if ((string)$iterator === '0') {
398
            return null;
399
        }
400
        $this->init();
401
        if ($this->driver instanceof Client) {
402
            $returned = $this->driver->zscan($key, $iterator, ['match' => $pattern, 'count' => $count]);
403
            $iterator = $returned[0];
404
            return $returned[1];
405
        }
406
        $data = $this->driver->zscan($key, $iterator, $pattern, $count);
407
        return $data === false ? null : $data;
0 ignored issues
show
Bug Compatibility introduced by
The expression $data === false ? null : $data; of type array|boolean adds the type boolean to the return on line 407 which is incompatible with the return type documented by RedisProxy\RedisProxy::zscan of type array|null.
Loading history...
408
    }
409
410 36
    private function convertFalseToNull($result)
411
    {
412 36
        return $this->driver instanceof Redis && $result === false ? null : $result;
413
    }
414
415 108
    private function transformResult($result)
416
    {
417 108
        if ($this->driver instanceof Client && $result instanceof Status) {
418 54
            $result = $result->getPayload() === 'OK';
419 27
        }
420 108
        return $result;
421
    }
422
423
    /**
424
     * Create array from input array - odd keys are used as keys, even keys are used as values
425
     * @param array $dictionary
426
     * @param string $command
427
     * @return array
428
     * @throws RedisProxyException if number of keys is not the same as number of values
429
     */
430 16
    private function prepareKeyValue(array $dictionary, $command)
431
    {
432
        $keys = array_values(array_filter($dictionary, function ($key) {
433 16
            return $key % 2 == 0;
434 16
        }, ARRAY_FILTER_USE_KEY));
435 16
        $values = array_values(array_filter($dictionary, function ($key) {
436 16
            return $key % 2 == 1;
437 16
        }, ARRAY_FILTER_USE_KEY));
438
439 16
        if (count($keys) != count($values)) {
440 8
            throw new RedisProxyException("Wrong number of arguments for $command");
441
        }
442 8
        return array_combine($keys, $values);
443
    }
444
}
445