Completed
Push — master ( 26da3a...b9da65 )
by Marco
03:37
created

PhpRedisCache   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 425
Duplicated Lines 7.53 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 59.6%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 41
c 1
b 0
f 0
lcom 1
cbo 2
dl 32
loc 425
ccs 90
cts 151
cp 0.596
rs 8.2769

10 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 54 5
C get() 0 52 8
B delete() 9 42 6
B flush() 0 26 3
A getInstance() 0 5 1
A setNamespaceKey() 17 17 3
A getNamespaceKey() 0 15 2
A getRedisStatus() 0 5 1
C set() 0 66 9
B status() 6 44 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like PhpRedisCache often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PhpRedisCache, and based on these observations, apply Extract Interface, too.

1
<?php namespace Comodojo\Cache;
2
3
use \Comodojo\Cache\CacheObject\CacheObject;
4
use \Redis;
5
use \Comodojo\Exception\CacheException;
6
use \RedisException;
7
use \Exception;
8
9
/**
10
 * Redis cache class using PhpRedis extension
11
 * 
12
 * @package     Comodojo Spare Parts
13
 * @author      Marco Giovinazzi <[email protected]>
14
 * @license     MIT
15
 *
16
 * LICENSE:
17
 * 
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
 * THE SOFTWARE.
25
 */
26
27
class PhpRedisCache extends CacheObject {
28
29
    /**
30
     * Internal phpredis handler
31
     *
32
     * @var \Redis
33
     */
34
    private $instance = null;
35
36
    /**
37
     * Class constructor
38
     *
39
     * @param   string          $server         Server address (or IP)
40
     * @param   integer         $port           (optional) Server port
41
     * @param   integer         $timeout        (optional) Timeout
42
     * @param   \Monolog\Logger $logger         Logger instance
43
     * 
44
     * @throws \Comodojo\Exception\CacheException
45
     */
46 135
    public function __construct( $server, $port=6379, $timeout=0, \Monolog\Logger $logger=null ) {
47
48 135
        if ( empty($server) ) throw new CacheException("Invalid or unspecified memcached server");
49
        
50 135
        if ( self::getRedisStatus() === false ) {
51
52
            $this->raiseError("PhpRedis extension not available, disabling cache administratively");
53
54
            $this->disable();
55
            
56
            return;
57
58
        }
59
60 135
        $this->instance = new Redis();
61
62 135
        $port = filter_var($port, FILTER_VALIDATE_INT, array(
63
            "options" => array(
64 135
                "min_range" => 1,
65 135
                "max_range" => 65535,
66 135
                "default" => 6379 )
67 135
            )
68 135
        );
69
70 135
        $weight = filter_var($timeout, FILTER_VALIDATE_INT, array(
71
            "options" => array(
72 135
                "min_range" => 0,
73 135
                "default" => 0 )
74 135
            )
75 135
        );
76
77 135
        if ( $this->instance->connect($server, $port, $weight) === false ) {
78
79
            $this->raiseError( "Error communicating with server", array( $this->instance->getLastError() ) );
80
81
            $this->disable();
82
83
        } else {
84
85
            try {
86
            
87 135
                parent::__construct( $logger );
88
                
89
            }
90
            
91 135
            catch ( CacheException $ce ) {
92
                
93
                throw $ce;
94
                
95
            }
96
97
        }
98
99 135
    }
100
101
    /**
102
     * Set cache element
103
     *
104
     * This method will throw only logical exceptions.
105
     * In case of failures, it will return a boolean false.
106
     *
107
     * @param   string  $name    Name for cache element
108
     * @param   mixed   $data    Data to cache
109
     * @param   int     $ttl     Time to live
110
     *
111
     * @return  bool
112
     * @throws \Comodojo\Exception\CacheException
113
     */
114 60
    public function set($name, $data, $ttl=null) {
115
116 60
        if ( empty($name) ) throw new CacheException("Name of object cannot be empty");
117
        
118 60
        if ( is_null($data) ) throw new CacheException("Object content cannot be null");
119
120 60
        if ( !$this->isEnabled() ) return false;
121
122 60
        $this->resetErrorState();
123
124
        try {
125
            
126 60
            $this->setTtl($ttl);
127
128 60
            $namespace = $this->getNamespaceKey();
129
130 60
            if ( $namespace === false ) $namespace = $this->setNamespaceKey();
131
132 60
            if ( $namespace === false ) {
133
134
                $this->raiseError( "Error writing cache (PhpRedis), exiting gracefully", array( $this->instance->getLastError() ) );
135
136
                $this->setErrorState();
137
138
                $return = false;
139
140
            } else {
141
142 60
                $shadowName = $namespace."-".md5($name);
143
            
144 60
                $shadowTtl = $this->ttl;
145
146 60
                $shadowData = serialize($data);
147
                
148 60
                $return = $this->instance->setex($shadowName, $shadowTtl, $shadowData);
149
150 60
                if ( $return === false ) {
151
152
                    $this->raiseError( "Error writing cache (PhpRedis), exiting gracefully", array( $this->instance->getLastError() ) );
153
154
                    $this->setErrorState();
155
156
                }
157
158
            }
159
160 60
        } catch (CacheException $ce) {
161
            
162
            throw $ce;
163
164
        } catch (RedisException $re ) {
165
166
            $this->raiseError("Server unreachable (PhpRedis), exiting gracefully", array(
167
                "RESULTCODE" => $re->getCode(),
168
                "RESULTMESSAGE" => $re->getMessage()
169
            ));
170
171
            $this->setErrorState();
172
173
            return false;
174
175
        }
176
177 60
        return $return;
178
179
    }
180
181
    /**
182
     * Get cache element
183
     *
184
     * This method will throw only logical exceptions.
185
     * In case of failures, it will return a null value.
186
     * In case of cache not found, it will return a null value.
187
     *
188
     * @param   string  $name    Name for cache element
189
     *
190
     * @return  mixed
191
     * @throws \Comodojo\Exception\CacheException
192
     */
193 25
    public function get($name) {
194
195 25
        if ( empty($name) ) throw new CacheException("Name of object cannot be empty");
196
197 25
        if ( !$this->isEnabled() ) return null;
198
199 25
        $this->resetErrorState();
200
201
        try {
202
            
203 25
            $namespace = $this->getNamespaceKey();
204
205 25
            if ( $namespace === false ) {
206
207 10
                $return = false;
208
209 10
            } else {
210
211 19
                $shadowName = $namespace."-".md5($name);
212
213 19
                $return = $this->instance->get($shadowName);
214
215 19
                if ( $return === false ) {
216
217 4
                    $this->raiseError( "Error reading cache (PhpRedis), exiting gracefully", array( $this->instance->getLastError() ) );
218
219 4
                    $this->setErrorState();
220
221 4
                }
222
223
            }
224
225 25
        } catch (CacheException $ce) {
226
            
227
            throw $ce;
228
229
        } catch (RedisException $re ) {
230
231
            $this->raiseError("Server unreachable (PhpRedis), exiting gracefully", array(
232
                "RESULTCODE" => $re->getCode(),
233
                "RESULTMESSAGE" => $re->getMessage()
234
            ));
235
236
            $this->setErrorState();
237
238
            return null;
239
240
        }
241
242 25
        return $return === false ? null : unserialize($return);
243
244
    }
245
246
    /**
247
     * Delete cache object (or entire namespace if $name is null)
248
     *
249
     * This method will throw only logical exceptions.
250
     * In case of failures, it will return a boolean false.
251
     *
252
     * @param   string  $name    Name for cache element
253
     *
254
     * @return  bool
255
     * @throws \Comodojo\Exception\CacheException
256
     */
257 15
    public function delete($name=null) {
258
259 15
        if ( !$this->isEnabled() ) return false;
260
261 15
        $this->resetErrorState();
262
263
        try {
264
265 15
            $namespace = $this->getNamespaceKey();
266
267 15
            if ( $namespace === false ) return true;
268
269 15 View Code Duplication
            if ( empty($name) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
270
271
                $this->instance->delete($this->getNamespace());
272
273
            } else {
274
275 15
                $this->instance->delete($namespace."-".md5($name));
276
277
            }
278
279 15
        } catch (CacheException $ce) {
280
            
281
            throw $ce;
282
283
        } catch (RedisException $re ) {
284
285
            $this->raiseError("Server unreachable (PhpRedis), exiting gracefully", array(
286
                "RESULTCODE" => $re->getCode(),
287
                "RESULTMESSAGE" => $re->getMessage()
288
            ));
289
290
            $this->setErrorState();
291
292
            return false;
293
294
        }
295
296 15
        return true;
297
298
    }
299
300
    /**
301
     * Clean cache objects in all namespaces
302
     *
303
     * This method will throw only logical exceptions.
304
     *
305
     * @return  bool
306
     * @throws \Comodojo\Exception\CacheException
307
     */
308 15
    public function flush() {
309
310 15
        if ( !$this->isEnabled() ) return false;
311
312 15
        $this->resetErrorState();
313
314
        try {
315
316 15
            $this->instance->flushDB();
317
318 15
        } catch (RedisException $re ) {
319
320
            $this->raiseError("Server unreachable (PhpRedis), exiting gracefully", array(
321
                "RESULTCODE" => $re->getCode(),
322
                "RESULTMESSAGE" => $re->getMessage()
323
            ));
324
325
            $this->setErrorState();
326
327
            return false;
328
329
        }
330
331 15
        return true;
332
333
    }
334
335
    /**
336
     * Get cache status
337
     *
338
     * @return  array
339
     */
340 15
    public function status() {
341
342 15
        $enabled = $this->isEnabled();
343
344 15 View Code Duplication
        if ( !$enabled ) return array(
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
345
            "provider"  => "phpredis",
346
            "enabled"   => $enabled,
347
            "objects"   => null,
348
            "options"   => array()
349
        );
350
351 15
        $objects = 0;
352
353 15
        $options = array();
354
355 15
        $this->resetErrorState();
356
357
        try {
358
359 15
            $objects = $this->instance->dbSize();
360
361 15
            $options = $this->instance->info();
362
363 15
        } catch (RedisException $re ) {
364
365
            $this->raiseError("Server unreachable (PhpRedis), exiting gracefully", array(
366
                "RESULTCODE" => $re->getCode(),
367
                "RESULTMESSAGE" => $re->getMessage()
368
            ));
369
370
            $this->setErrorState();
371
372
            $enabled = false;
373
374
        }
375
376
        return array(
377 15
            "provider"  => "phpredis",
378 15
            "enabled"   => $enabled,
379 15
            "objects"   => $objects,
380
            "options"   => $options
381 15
        );
382
383
    }
384
385
    /**
386
     * Get the current memcached instance
387
     *
388
     * @return  \Memcached
389
     */
390
    final public function getInstance() {
391
392
        return $this->instance;
393
394
    }
395
396
    /**
397
     * Set key for namespace
398
     *
399
     * @return  string
400
     */
401 30 View Code Duplication
    private function setNamespaceKey() {
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...
402
403 30
        $uId = self::getUniqueId();
404
405
        try {
406
407 30
            $return = $this->instance->set($this->getNamespace(), $uId);    
408
409 30
        } catch (RedisException $re ) {
410
411
            throw $re;
412
413
        }
414
415 30
        return $return === false ? false : $uId;
416
417
    }
418
419
    /**
420
     * Get key for namespace
421
     *
422
     * @return  string
423
     */
424 91
    private function getNamespaceKey() {
425
426
        try {
427
428 91
            $return = $this->instance->get($this->getNamespace());
429
430 91
        } catch (RedisException $re ) {
431
432
            throw $re;
433
434
        }
435
436 91
        return $return;
437
438
    }
439
    
440
    /**
441
     * Check PhpRedis availability
442
     *
443
     * @return  bool
444
     */
445 135
    private static function getRedisStatus() {
446
        
447 135
        return class_exists('Redis');
448
        
449
    }
450
451
}