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

FileCache   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 494
Duplicated Lines 27.13 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 53.16%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 51
c 4
b 0
f 0
lcom 1
cbo 2
dl 134
loc 494
ccs 84
cts 158
cp 0.5316
rs 8.3206

13 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 0 41 8
C set() 0 39 7
B get() 0 23 6
B setXattr() 31 31 3
B setGhost() 33 33 3
B getXattr() 23 49 5
B getGhost() 21 47 5
A checkCacheFolder() 0 5 1
A checkXattrSupport() 0 5 1
A checkXattrFilesystemSupport() 0 5 1
B delete() 13 35 5
B flush() 13 27 4
B status() 0 28 2

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 FileCache 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 FileCache, and based on these observations, apply Extract Interface, too.

1
<?php namespace Comodojo\Cache;
2
3
use \Comodojo\Cache\CacheObject\CacheObject;
4
use \Comodojo\Exception\CacheException;
5
use \Exception;
6
7
/**
8
 * File cache class
9
 * 
10
 * @package     Comodojo Spare Parts
11
 * @author      Marco Giovinazzi <[email protected]>
12
 * @license     MIT
13
 *
14
 * LICENSE:
15
 * 
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
 * THE SOFTWARE.
23
 */
24
25
class FileCache extends CacheObject {
26
 
27
    /**
28
     * Current cache folder
29
     *
30
     * @var string
31
     */
32
    private $cache_folder = null;
33
 
34
    /**
35
     * Class constructor
36
     *
37
     * @throws \Comodojo\Exception\CacheException
38
     */
39 186
    public function __construct($cache_folder = null, \Monolog\Logger $logger = null) {
40
41 186
        if ( !empty($cache_folder) && is_string($cache_folder) ) {
42
            
43 186
            $this->cache_folder = $cache_folder[strlen($cache_folder) - 1] == "/" ? $cache_folder : ($cache_folder."/");
44
            
45 186
        } else if ( defined("COMODOJO_CACHE_FOLDER") ) {
46
            
47
            $folder = COMODOJO_CACHE_FOLDER;
48
            
49
            $this->cache_folder = $folder[strlen($folder) - 1] == "/" ? $folder : ($folder."/");
50
            
51
        } else {
52
        
53
            throw new CacheException("Invalid or unspecified cache folder");
54
            
55
        }
56
57 186
        if ( self::checkCacheFolder($this->cache_folder) === false ) {
58
59
            $this->raiseError("Cache folder not writeable, disabling cache administratively");
60
61
            $this->disable();
62
63
        } else {
64
65
            try {
66
            
67 186
                parent::__construct($logger);
68
                
69
            }
70
            
71 186
            catch (CacheException $ce) {
72
                
73
                throw $ce;
74
                
75
            }
76
77
        }
78
79 186
    }
80
    
81
    /**
82
     * Set cache element
83
     *
84
     * This method will throw only logical exceptions.
85
     * In case of failures, it will return a boolean false.
86
     *
87
     * @param   string  $name    Name for cache element
88
     * @param   mixed   $data    Data to cache
89
     * @param   int     $ttl     Time to live
90
     *
91
     * @return  bool
92
     * @throws \Comodojo\Exception\CacheException
93
     */
94 72
    public function set($name, $data, $ttl = null) {
95
96 72
        if ( empty($name) ) throw new CacheException("Name of object cannot be empty");
97
98 72
        if ( is_null($data) ) throw new CacheException("Object content cannot be null");
99
100 72
        if ( !$this->isEnabled() ) return false;
101
102 72
        $this->resetErrorState();
103
        
104
        try {
105
            
106 72
            $this->setTtl($ttl);
107
        
108 72
            $shadowName = $this->cache_folder.md5($name)."-".$this->getNamespace();
109
            
110 72
            $shadowData = serialize($data);
111
    
112 72
            $shadowTtl = $this->getTime() + $this->ttl;
113
        
114 72
            if ( self::checkXattrSupport() && self::checkXattrFilesystemSupport($this->cache_folder) ) {
115
116
                $return = $this->setXattr($shadowName, $shadowData, $shadowTtl);
117
118
            } else {
119
120 72
                $return = $this->setGhost($shadowName, $shadowData, $shadowTtl);
121
122
            }
123
124 72
        } catch (CacheException $ce) {
125
            
126
            throw $ce;
127
128
        }
129
130 72
        return $return;
131
132
    }
133
    
134
    /**
135
     * Get cache element
136
     *
137
     * This method will throw only logical exceptions.
138
     * In case of failures, it will return a null value.
139
     * In case of cache not found, it will return a null value.
140
     *
141
     * @param   string  $name    Name for cache element
142
     *
143
     * @return  mixed
144
     * @throws \Comodojo\Exception\CacheException
145
     */
146 26
    public function get($name) {
147
148 26
        if ( empty($name) ) throw new CacheException("Name of object cannot be empty");
149
150 26
        if ( !$this->isEnabled() ) return null;
151
152 26
        $this->resetErrorState();
153
154 26
        $shadowName = $this->cache_folder.md5($name)."-".$this->getNamespace();
155
    
156 26
        if ( self::checkXattrSupport() && self::checkXattrFilesystemSupport($this->cache_folder) ) {
157
158
            $return = $this->getXattr($shadowName, $this->getTime());
159
160
        } else {
161
162 26
            $return = $this->getGhost($shadowName, $this->getTime());
163
164
        }
165
166 26
        return is_null($return) ? $return : unserialize($return);
167
168
    }
169
    
170
    /**
171
     * Delete cache object (or entire namespace if $name is null)
172
     *
173
     * This method will throw only logical exceptions.
174
     * In case of failures, it will return a boolean false.
175
     *
176
     * @param   string  $name    Name for cache element
177
     *
178
     * @return  bool
179
     */
180 18
    public function delete($name = null) {
181
182 18
        if ( !$this->isEnabled() ) return false;
183
184 18
        $this->resetErrorState();
185
186 18
        $return = true;
187
188 18
        if ( is_null($name) ) {
189
190
            $filesList = glob($this->cache_folder."*-".$this->getNamespace().".{cache,expire}", GLOB_BRACE);
191
192
        } else {
193
194 18
            $filesList = glob($this->cache_folder.md5($name)."-".$this->getNamespace().".{cache,expire}", GLOB_BRACE);
195
196
        }
197
198 18 View Code Duplication
        foreach ( $filesList as $file ) {
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...
199
200 18
            if ( unlink($file) === false ) {
201
202
                $this->raiseError("Failed to unlink cache file (File), exiting gracefully", pathinfo($file));
203
204
                $this->setErrorState();
205
                
206
                $return = false;
207
208
            }
209
210 18
        }
211
212 18
        return $return;
213
214
    }
215
216
    /**
217
     * Clean cache objects in all namespaces
218
     *
219
     * This method will throw only logical exceptions.
220
     *
221
     * @return  bool
222
     */
223 18
    public function flush() {
224
225 18
        if ( !$this->isEnabled() ) return false;
226
227 18
        $this->resetErrorState();
228
229 18
        $return = true;
230
231 18
        $filesList = glob($this->cache_folder."*.{cache,expire}", GLOB_BRACE);
232
233 18 View Code Duplication
        foreach ( $filesList as $file ) {
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...
234
235 18
            if ( unlink($file) === false ) {
236
237
                $this->raiseError("Failed to unlink whole cache (File), exiting gracefully", pathinfo($file));
238
239
                $this->setErrorState();
240
241
                $return = false;
242
243
            }
244
245 18
        }
246
247 18
        return $return;
248
249
    }
250
251
    /**
252
     * Get cache status
253
     *
254
     * @return  array
255
     */
256 18
    public function status() {
257
258 18
        $filesList = glob($this->cache_folder."*.cache");
259
260 18
        if ( self::checkXattrSupport() ) {
261
262
            $options = array(
263
                "xattr_supported"   =>  true,
264
                "xattr_enabled"     =>  $this->checkXattrFilesystemSupport($this->cache_folder)
265
            );
266
267
        } else {
268
269
            $options = array(
270 18
                "xattr_supported"   =>  false,
271
                "xattr_enabled"     =>  false
272 18
            );
273
274
        }
275
276
        return array(
277 18
            "provider"  => "file",
278 18
            "enabled"   => $this->isEnabled(),
279 18
            "objects"   => count($filesList),
280
            "options"   => $options
281 18
        );
282
283
    }
284
285
    /**
286
     * Set a cache element using xattr
287
     *
288
     * @param   string  $name    Name for cache element
289
     * @param   mixed   $data    Data to cache
290
     * @param   int     $ttl     Time to live
291
     *
292
     * @return  bool
293
     */
294 View Code Duplication
    private function setXattr($name, $data, $ttl) {
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...
295
296
        $cacheFile = $name.".cache";
297
298
        $cached = file_put_contents($cacheFile, $data, LOCK_EX);
299
300
        if ( $cached === false ) {
301
302
            $this->raiseError("Error writing cache object (File), exiting gracefully", pathinfo($cacheFile));
303
304
            $this->setErrorState();
305
306
            return false;
307
308
        }
309
310
        $tagged = xattr_set($cacheFile, "EXPIRE", $ttl, XATTR_DONTFOLLOW);
311
312
        if ( $tagged === false ) {
313
314
            $this->raiseError("Error writing cache ttl (File) (XATTR), exiting gracefully", pathinfo($cacheFile));
315
316
            $this->setErrorState();
317
318
            return false;
319
320
        }
321
322
        return true;
323
324
    }
325
326
    /**
327
     * Set a cache element using ghost file
328
     *
329
     * @param   string  $name    Name for cache element
330
     * @param   mixed   $data    Data to cache
331
     * @param   int     $ttl     Time to live
332
     *
333
     * @return  bool
334
     */
335 72 View Code Duplication
    private function setGhost($name, $data, $ttl) {
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...
336
337 72
        $cacheFile = $name.".cache";
338
339 72
        $cacheGhost = $name.".expire";
340
341 72
        $cached = file_put_contents($cacheFile, $data, LOCK_EX);
342
        
343 72
        if ( $cached === false ) {
344
345
            $this->raiseError("Error writing cache object (File), exiting gracefully", pathinfo($cacheFile));
346
347
            $this->setErrorState();
348
349
            return false;
350
351
        }
352
353 72
        $tagged = file_put_contents($cacheGhost, $ttl, LOCK_EX);
354
355 72
        if ( $tagged === false ) {
356
357
            $this->raiseError("Error writing cache ttl (File) (GHOST), exiting gracefully", pathinfo($cacheGhost));
358
359
            $this->setErrorState();
360
361
            return false;
362
363
        }
364
365 72
        return true;
366
367
    }
368
369
    /**
370
     * Get a cache element using xattr
371
     *
372
     * @param   string  $name    Name for cache element
373
     * @param   int     $time
374
     *
375
     * @return  mixed
376
     */
377
    private function getXattr($name, $time) {
378
379
        $cacheFile = $name.".cache";
380
381
        if ( file_exists($cacheFile) ) {
382
383
            $expire = xattr_get($cacheFile, "EXPIRE", XATTR_DONTFOLLOW);
384
385
            if ( $expire === false ) {
386
                
387
                $this->raiseError("Error reading cache ttl (File) (XATTR), exiting gracefully", pathinfo($cacheFile));
388
389
                $this->setErrorState();
390
391
                $return = null;
392
393 View Code Duplication
            } else if ( $expire < $time ) {
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...
394
                
395
                $return = null;
396
                
397
            } else {
398
399
                $data = file_get_contents($cacheFile);
400
401
                if ( $data === false ) {
402
                    
403
                    $this->raiseError("Error reading cache content (File), exiting gracefully", pathinfo($cacheFile));
404
405
                    $this->setErrorState();
406
                    
407
                    $return = null;
408
                    
409
                } else {
410
                    
411
                    $return = $data;
412
                    
413
                }
414
                
415
            }
416
417
        } else {
418
            
419
            $return = null;
420
        
421
        }
422
        
423
        return $return;
424
425
    }
426
427
    /**
428
     * Get a cache element using ghost file
429
     *
430
     * @param   string  $name    Name for cache element
431
     * @param   int     $time
432
     *
433
     * @return  mixed
434
     */
435 26
    private function getGhost($name, $time) {
436
437 26
        $cacheFile = $name.".cache";
438
439 26
        $cacheGhost = $name.".expire";
440
441 26
        if ( file_exists($cacheFile) ) {
442
443 18
            $expire = file_get_contents($cacheGhost);
444
445 18
            if ( $expire === false ) {
446
447
                $this->raiseError("Error reading cache ttl (File) (GHOST), exiting gracefully", pathinfo($cacheGhost));
448
                
449
                $return = null;
450
451 18 View Code Duplication
            } else if ( intval($expire) < $time ) {
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...
452
                
453 5
                $return = null;
454
                
455 5
            } else {
456
457 13
                $data = file_get_contents($cacheFile);
458
459 13
                if ( $data === false ) {
460
                    
461
                    $this->raiseError("Error reading cache content (File), exiting gracefully", pathinfo($cacheFile));
462
                    
463
                    $return = null;
464
                    
465
                } else {
466
                    
467 13
                    $return = $data;
468
                    
469
                }
470
                
471
            }
472
473 18
        } else {
474
            
475 11
            $return = null;
476
        
477
        }
478
        
479 26
        return $return;
480
481
    }
482
    
483
    /**
484
     * Check if cache folder is writable
485
     *
486
     * @param   string  $folder
487
     *
488
     * @return  bool
489
     */
490 186
    private static function checkCacheFolder($folder) {
491
        
492 186
        return is_writable($folder);
493
        
494
    }
495
    
496
    /**
497
     * Check xattr (extension) support
498
     *
499
     * @return  bool
500
     */
501 105
    private static function checkXattrSupport() {
502
        
503 105
        return function_exists("xattr_supported");
504
        
505
    }
506
    
507
    /**
508
     * Check xattr (filesystem) support
509
     *
510
     * @return  bool
511
     */
512
    private static function checkXattrFilesystemSupport($folder) {
513
        
514
        return xattr_supported($folder);
515
        
516
    }
517
    
518
}
519