Completed
Pull Request — master (#2)
by Michal
34:57
created

RedisProxy::connect()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 3
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 boolean set(string $key, string $value) Set the string value of a key
13
 * @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.
14
 * @method integer hset(string $key, string $field, string $value) Set the string value of a hash field
15
 * @method array hgetall(string $key) Get all fields and values in hash
16
 * @method array hGetAll(string $key) Get all fields and values in hash
17
 * @method integer hlen(string $key) Get the number of fields in hash
18
 * @method integer hLen(string $key) Get the number of fields in hash
19
 * @method boolean flushall() Remove all keys from all databases
20
 * @method boolean flushAll() Remove all keys from all databases
21
 * @method boolean flushdb() Remove all keys from the current database
22
 * @method boolean flushDb() Remove all keys from the current database
23
 * @method boolean flushDB() Remove all keys from the current database
24
 */
25
class RedisProxy
26
{
27
    const DRIVER_REDIS = 'redis';
28
29
    const DRIVER_PREDIS = 'predis';
30
31
    private $driver;
32
33
    private $host;
34
35
    private $port;
36
37
    private $database = 0;
38
39
    private $timeout;
40
41
    private $supportedDrivers = [
42
        self::DRIVER_REDIS,
43
        self::DRIVER_PREDIS,
44
    ];
45
46
    private $driversOrder = [];
47
48 64
    public function __construct($host, $port, $timeout = null)
49
    {
50 64
        $this->host = $host;
51 64
        $this->port = $port;
52 64
        $this->timeout = $timeout;
53 64
        $this->driversOrder = $this->supportedDrivers;
54 64
    }
55
56 64
    public function setDriversOrder(array $driversOrder)
57
    {
58 64
        if (empty($driversOrder)) {
59 2
            throw new RedisProxyException('You need to set at least one driver');
60
        }
61 62
        foreach ($driversOrder as $driver) {
62 62
            if (!in_array($driver, $this->supportedDrivers)) {
63 62
                throw new RedisProxyException('Driver "' . $driver . '" is not supported');
64
            }
65
        }
66 60
        $this->driversOrder = $driversOrder;
67 60
        return $this;
68
    }
69
70 60
    private function init()
71
    {
72 60
        $this->prepareDriver();
73 60
        $this->select($this->database);
74 60
    }
75
76 60
    private function prepareDriver()
77
    {
78 60
        if ($this->driver !== null) {
79 60
            return;
80
        }
81
82 60
        foreach ($this->driversOrder as $preferredDriver) {
83 60
            if ($preferredDriver === self::DRIVER_REDIS && extension_loaded('redis')) {
84 30
                $this->driver = new Redis();
85 30
                return;
86
            }
87 30
            if ($preferredDriver === self::DRIVER_PREDIS && class_exists('Predis\Client')) {
88 30
                $this->driver = new Client();
89 30
                return;
90
            }
91
        }
92
        throw new RedisProxyException('No redis library loaded (ext-redis or predis)');
93
    }
94
95 60
    private function connect($host, $port, $timeout = null)
96
    {
97 60
        return $this->driver->connect($host, $port, $timeout);
98
    }
99
100 60
    private function isConnected()
101
    {
102 60
        return $this->driver->isConnected();
103
    }
104
105 60
    public function __call($name, $arguments)
106
    {
107 60
        $this->init();
108 60
        $result = call_user_func_array([$this->driver, $name], $arguments);
109 60
        return $this->transformResult($result);
110
    }
111
112
    /**
113
     * @param integer $database
114
     * @return boolean true on success
115
     * @throws RedisProxyException on failure
116
     */
117 60
    public function select($database)
118
    {
119 60
        $this->prepareDriver();
120 60
        if (!$this->isConnected()) {
121 60
            $this->connect($this->host, $this->port, $this->timeout);
122
        }
123 60
        if ($database == $this->database) {
124 60
            return true;
125
        }
126
        try {
127 12
            $result = $this->driver->select($database);
128 2
        } catch (Exception $e) {
129 2
            throw new RedisProxyException('Invalid DB index');
130
        }
131 10
        $result = $this->transformResult($result);
132 10
        if ($result === false) {
133 2
            throw new RedisProxyException('Invalid DB index');
134
        }
135 8
        $this->database = $database;
136 8
        return $result;
137
    }
138
139
    /**
140
     * @param string|null $section
141
     * @return array
142
     */
143 8
    public function info($section = null)
144
    {
145 8
        $this->init();
146 8
        $section = $section ? strtolower($section) : $section;
147 8
        $result = $section === null ? $this->driver->info() : $this->driver->info($section);
148
149 8
        $databases = $section === null || $section === 'keyspace' ? $this->config('get', 'databases')['databases'] : null;
150 8
        $groupedResult = InfoHelper::createInfoArray($this->driver, $result, $databases);
151 8
        if ($section === null) {
152 4
            return $groupedResult;
153
        }
154 8
        if (isset($groupedResult[$section])) {
155 4
            return $groupedResult[$section];
156
        }
157 4
        throw new RedisProxyException('Info section "' . $section . '" doesn\'t exist');
158
    }
159
160
    /**
161
     * @param string $key
162
     * @return string|null null if hash field is not set
163
     */
164 12
    public function get($key)
165
    {
166 12
        $this->init();
167 12
        $result = $this->driver->get($key);
168 12
        return $this->convertFalseToNull($result);
169
    }
170
171
    /**
172
     * Delete a key(s)
173
     * @param array ...$keys
174
     * @return integer number of deleted keys
175
     */
176 12
    public function del(...$keys)
177
    {
178 12
        $this->init();
179 12
        return $this->driver->del(...$keys);
180
    }
181
182
    /**
183
     * Delete a key(s)
184
     * @param array ...$keys
185
     * @return integer number of deleted keys
186
     */
187 8
    public function delete(...$keys)
188
    {
189 8
        return $this->del(...$keys);
190
    }
191
192
    /**
193
     * Incrementally iterate the keys space
194
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
195
     * @param string $pattern pattern for keys, use * as wild card
196
     * @param integer $count
197
     * @return array|null list of found keys, returns null if $iterator is 0 or '0'
198
     */
199 View Code Duplication
    public function scan(&$iterator, $pattern = null, $count = null)
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...
200
    {
201
        if ((string)$iterator === '0') {
202
            return null;
203
        }
204
        $this->init();
205
        if ($this->driver instanceof Client) {
206
            $returned = $this->driver->scan($iterator, ['match' => $pattern, 'count' => $count]);
207
            $iterator = $returned[0];
208
            return $returned[1];
209
        }
210
        return $this->driver->scan($iterator, $pattern, $count);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->driver->scan($iterator, $pattern, $count); of type array|boolean adds the type boolean to the return on line 210 which is incompatible with the return type documented by RedisProxy\RedisProxy::scan of type array|null.
Loading history...
211
    }
212
213
    /**
214
     * Get the value of a hash field
215
     * @param string $key
216
     * @param string $field
217
     * @return string|null null if hash field is not set
218
     */
219 4
    public function hget($key, $field)
220
    {
221 4
        $this->init();
222 4
        $result = $this->driver->hget($key, $field);
223 4
        return $this->convertFalseToNull($result);
224
    }
225
226
    /**
227
     * Delete one or more hash fields, returns number of deleted fields
228
     * @param array ...$key
229
     * @param array $fields
230
     * @return integer
231
     */
232 8
    public function hdel($key, ...$fields)
233
    {
234 8
        if (is_array($fields[0])) {
235 4
            $fields = $fields[0];
236
        }
237 8
        $res = $this->driver->hdel($key, ...$fields);
238
239 8
        return $res;
240
    }
241
242
    /**
243
     * Incrementally iterate hash fields and associated values
244
     * @param string $key
245
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
246
     * @param string $pattern pattern for fields, use * as wild card
247
     * @param integer $count
248
     * @return array|null list of found fields with associated values, returns null if $iterator is 0 or '0'
249
     */
250 View Code Duplication
    public function hscan($key, &$iterator, $pattern = null, $count = null)
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...
251
    {
252
        if ((string)$iterator === '0') {
253
            return null;
254
        }
255
        $this->init();
256
        if ($this->driver instanceof Client) {
257
            $returned = $this->driver->hscan($key, $iterator, ['match' => $pattern, 'count' => $count]);
258
            $iterator = $returned[0];
259
            return $returned[1];
260
        }
261
        return $this->driver->hscan($key, $iterator, $pattern, $count);
262
    }
263
264
    /**
265
     * Add one or more members to a set
266
     * @param string $key
267
     * @param array ...$members
268
     * @return integer number of new members added to set
269
     */
270 12
    public function sadd($key, ...$members)
271
    {
272 12
        if (is_array($members[0])) {
273 8
            $members = $members[0];
274
        }
275 12
        return $this->driver->sadd($key, ...$members);
276
    }
277
278
    /**
279
     * Remove and return one or multiple random members from a set
280
     * @param string $key
281
     * @param integer $count number of members
282
     * @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
283
     */
284 4
    public function spop($key, $count = 1)
285
    {
286 4
        if ($count == 1 || $count === null) {
287 4
            $result = $this->driver->spop($key);
288 4
            return $this->convertFalseToNull($result);
289
        }
290
291 4
        $members = [];
292 4
        for ($i = 0; $i < $count; ++$i) {
293 4
            $member = $this->driver->spop($key);
294 4
            if (!$member) {
295 4
                break;
296
            }
297 4
            $members[] = $member;
298
        }
299 4
        return empty($members) ? null : $members;
300
    }
301
302
    /**
303
     * Incrementally iterate Set elements
304
     * @param string $key
305
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
306
     * @param string $pattern pattern for member's values, use * as wild card
307
     * @param integer $count
308
     * @return array|null list of found members, returns null if $iterator is 0 or '0'
309
     */
310 4 View Code Duplication
    public function sscan($key, &$iterator, $pattern = null, $count = null)
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...
311
    {
312 4
        if ((string)$iterator === '0') {
313 4
            return null;
314
        }
315 4
        $this->init();
316 4
        if ($this->driver instanceof Client) {
317 2
            $returned = $this->driver->sscan($key, $iterator, ['match' => $pattern, 'count' => $count]);
318 2
            $iterator = $returned[0];
319 2
            return $returned[1];
320
        }
321 2
        return $this->driver->sscan($key, $iterator, $pattern, $count);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->driver->sscan($ke...tor, $pattern, $count); of type array|boolean adds the type boolean to the return on line 321 which is incompatible with the return type documented by RedisProxy\RedisProxy::sscan of type array|null.
Loading history...
322
    }
323
324
    /**
325
     * Incrementally iterate sorted sets elements and associated scores
326
     * @param string $key
327
     * @param mixed $iterator iterator / cursor, use $iterator = null for start scanning, when $iterator is changed to 0 or '0', scanning is finished
328
     * @param string $pattern pattern for element's values, use * as wild card
329
     * @param integer $count
330
     * @return array|null list of found elements, returns null if $iterator is 0 or '0'
331
     */
332 View Code Duplication
    public function zscan($key, &$iterator, $pattern = null, $count = null)
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...
333
    {
334
        if ((string)$iterator === '0') {
335
            return null;
336
        }
337
        $this->init();
338
        if ($this->driver instanceof Client) {
339
            $returned = $this->driver->zscan($key, $iterator, ['match' => $pattern, 'count' => $count]);
340
            $iterator = $returned[0];
341
            return $returned[1];
342
        }
343
        return $this->driver->zscan($key, $iterator, $pattern, $count);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->driver->zscan($ke...tor, $pattern, $count); of type array|boolean adds the type boolean to the return on line 343 which is incompatible with the return type documented by RedisProxy\RedisProxy::zscan of type array|null.
Loading history...
344
    }
345
346 20
    private function convertFalseToNull($result)
347
    {
348 20
        return $this->driver instanceof Redis && $result === false ? null : $result;
349
    }
350
351 60
    private function transformResult($result)
352
    {
353 60
        if ($this->driver instanceof Client && $result instanceof Status) {
354 30
            $result = $result->getPayload() === 'OK';
355
        }
356 60
        return $result;
357
    }
358
}
359