Completed
Push — v5 ( 7bca9d...b778e1 )
by Georges
02:46
created

DriverAbstract   C

Complexity

Total Complexity 70

Size/Duplication

Total Lines 550
Duplicated Lines 20.36 %

Coupling/Cohesion

Components 2
Dependencies 2

Importance

Changes 24
Bugs 2 Features 4
Metric Value
c 24
b 2
f 4
dl 112
loc 550
rs 5.6163
wmc 70
lcom 2
cbo 2

35 Methods

Rating   Name   Duplication   Size   Complexity  
A __destruct() 0 7 3
A driverPreWrap() 0 8 1
A driverUnwrapData() 0 4 1
A driverUnwrapTags() 0 4 1
A driverUnwrapTime() 0 4 1
A getDriverName() 0 6 2
B driverWriteTags() 0 57 5
A getTagKey() 0 4 1
A getTagKeys() 0 8 2
A getItemsByTag() 0 15 3
A getItemsByTags() 0 9 2
A deleteItemsByTag() 0 16 4
A setup() 0 11 2
A readfile() 0 21 4
A encode() 0 4 1
A decode() 0 9 2
A isPHPModule() 0 12 3
A isExistingDriver() 0 4 1
A _getTagName() 0 4 1
A encodeFilename() 0 6 1
A deleteItemsByTags() 12 12 3
A incrementItemsByTag() 12 12 4
A incrementItemsByTags() 13 13 3
A decrementItemsByTag() 12 12 4
A decrementItemsByTags() 13 13 3
A appendItemsByTag() 12 12 3
A appendItemsByTags() 13 13 3
A prependItemsByTag() 12 12 3
A prependItemsByTags() 13 13 3
driverRead() 0 1 ?
driverWrite() 0 1 ?
driverClear() 0 1 ?
driverConnect() 0 1 ?
driverDelete() 0 1 ?
driverIsHit() 0 1 ?

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

1
<?php
2
/**
3
 *
4
 * This file is part of phpFastCache.
5
 *
6
 * @license MIT License (MIT)
7
 *
8
 * For full copyright and license information, please see the docs/CREDITS.txt file.
9
 *
10
 * @author Khoa Bui (khoaofgod)  <[email protected]> http://www.phpfastcache.com
11
 * @author Georges.L (Geolim4)  <[email protected]>
12
 *
13
 */
14
15
namespace phpFastCache\Core;
16
17
use InvalidArgumentException;
18
use phpFastCache\Cache\ExtendedCacheItemInterface;
19
use phpFastCache\Cache\ExtendedCacheItemPoolInterface;
20
use phpFastCache\Exceptions\phpFastCacheDriverException;
21
use phpFastCache\CacheManager;
22
use Psr\Cache\CacheItemInterface;
23
24
/**
25
 * Class DriverAbstract
26
 * @package phpFastCache\Core
27
 */
28
abstract class DriverAbstract implements ExtendedCacheItemPoolInterface
29
{
30
    const DRIVER_CHECK_FAILURE      = '%s is not installed or misconfigured, cannot continue.';
31
    const DRIVER_TAGS_KEY_PREFIX    = '_TAG_';
32
    const DRIVER_DATA_WRAPPER_INDEX = 'd';
33
    const DRIVER_TIME_WRAPPER_INDEX = 't';
34
    const DRIVER_TAGS_WRAPPER_INDEX = 'g';
35
36
    /**
37
     * @var array
38
     */
39
    public $extension_dir = '_extensions';
40
41
    /**
42
     * @var array
43
     */
44
    public $tmp = [];
45
46
    /**
47
     * @var array default options, this will be merge to Driver's Options
48
     */
49
    public $config = [];
50
51
    /**
52
     * @var bool
53
     */
54
    public $fallback = false;
55
56
    /**
57
     * @var mixed Instance of driver service
58
     */
59
    public $instance;
60
61
62
    public function __destruct()
63
    {
64
        // clean up the memory and don't want for PHP clean for caching method "phpfastcache"
65
        if (isset($this->config[ 'instance' ]) && (int) $this->config[ 'cache_method' ] === 3) {
66
            CacheManager::cleanCachingMethod($this->config[ 'instance' ]);
67
        }
68
    }
69
70
    /**
71
     * @param $keyword
72
     * @return string
73
     */
74
    protected function encodeFilename($keyword)
75
    {
76
        // return trim(trim(preg_replace('/[^a-zA-Z0-9]+/', '_', $keyword), '_'));
0 ignored issues
show
Unused Code Comprehensibility introduced by
66% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
77
        // return rtrim(base64_encode($keyword), '=');
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
78
        return md5($keyword);
79
    }
80
81
    /**
82
     * @param $config_name
83
     * @param string $value
84
     */
85
    public function setup($config_name, $value = '')
86
    {
87
        /**
88
         * Config for class
89
         */
90
        if (is_array($config_name)) {
91
            $this->config = array_merge($this->config, $config_name);
92
        } else {
93
            $this->config[ $config_name ] = $value;
94
        }
95
    }
96
97
98
    /**
99
     * @param $file
100
     * @return string
101
     * @throws \Exception
102
     */
103
    protected function readfile($file)
104
    {
105
        if (function_exists('file_get_contents')) {
106
            return file_get_contents($file);
0 ignored issues
show
Security File Exposure introduced by
$file can contain request data and is used in file inclusion context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
107
        } else {
108
            $string = '';
109
110
            $file_handle = @fopen($file, 'r');
111
            if (!$file_handle) {
112
                throw new phpFastCacheDriverException("Can't Read File", 96);
113
114
            }
115
            while (!feof($file_handle)) {
116
                $line = fgets($file_handle);
117
                $string .= $line;
118
            }
119
            fclose($file_handle);
120
121
            return $string;
122
        }
123
    }
124
125
    /**
126
     * Encode data types such as object/array
127
     * for driver that does not support
128
     * non-scalar value
129
     * @param $data
130
     * @return string
131
     */
132
    protected function encode($data)
133
    {
134
        return serialize($data);
135
    }
136
137
    /**
138
     * Decode data types such as object/array
139
     * for driver that does not support
140
     * non-scalar value
141
     * @param $value
142
     * @return mixed
143
     */
144
    protected function decode($value)
145
    {
146
        $x = @unserialize($value);
0 ignored issues
show
Security Object Injection introduced by
$value can contain request data and is used in unserialized context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
147
        if ($x == false) {
148
            return $value;
149
        } else {
150
            return $x;
151
        }
152
    }
153
154
    /**
155
     * Check phpModules or CGI
156
     * @return bool
157
     */
158
    protected function isPHPModule()
159
    {
160
        if (PHP_SAPI === 'apache2handler') {
161
            return true;
162
        } else {
163
            if (strpos(PHP_SAPI, 'handler') !== false) {
164
                return true;
165
            }
166
        }
167
168
        return false;
169
    }
170
171
172
    /**
173
     * @param $class
174
     * @return bool
175
     */
176
    protected function isExistingDriver($class)
177
    {
178
        return class_exists("\\phpFastCache\\Drivers\\{$class}");
179
    }
180
181
182
    /**
183
     * @param $tag
184
     * @return string
185
     */
186
    protected function _getTagName($tag)
187
    {
188
        return "__tag__" . $tag;
189
    }
190
191
    /**
192
     * @param \phpFastCache\Cache\ExtendedCacheItemInterface $item
193
     * @return array
194
     */
195
    public function driverPreWrap(ExtendedCacheItemInterface $item)
196
    {
197
        return [
198
          self::DRIVER_DATA_WRAPPER_INDEX => $item->get(),
199
          self::DRIVER_TIME_WRAPPER_INDEX => $item->getExpirationDate(),
200
          self::DRIVER_TAGS_WRAPPER_INDEX => $item->getTags(),
201
        ];
202
    }
203
204
    /**
205
     * @param array $wrapper
206
     * @return mixed
207
     */
208
    public function driverUnwrapData(array $wrapper)
209
    {
210
        return $wrapper[ self::DRIVER_DATA_WRAPPER_INDEX ];
211
    }
212
213
    /**
214
     * @param array $wrapper
215
     * @return mixed
216
     */
217
    public function driverUnwrapTags(array $wrapper)
218
    {
219
        return $wrapper[ self::DRIVER_TAGS_WRAPPER_INDEX ];
220
    }
221
222
223
    /**
224
     * @param array $wrapper
225
     * @return \DateTime
226
     */
227
    public function driverUnwrapTime(array $wrapper)
228
    {
229
        return $wrapper[ self::DRIVER_TIME_WRAPPER_INDEX ];
230
    }
231
232
    /**
233
     * @return string
234
     */
235
    public function getDriverName()
236
    {
237
        static $driverName;
238
239
        return ($driverName ?: $driverName = ucfirst(substr(strrchr((new \ReflectionObject($this))->getNamespaceName(), '\\'), 1)));
240
    }
241
242
    /**
243
     * @param \phpFastCache\Cache\ExtendedCacheItemInterface $item
244
     * @return bool
245
     */
246
    public function driverWriteTags(ExtendedCacheItemInterface $item)
247
    {
248
        $tagsItems = $this->getItems($this->getTagKeys($item->getTags()));
249
250
        foreach ($tagsItems as $tagsItem) {
251
            $data = $tagsItem->get();
252
            $expTimestamp = $item->getExpirationDate()->getTimestamp();
253
254
            /**
255
             * Using the key will
256
             * avoid to use array_unique
257
             * that has slow performances
258
             */
259
260
            $tagsItem->set(array_merge((array) $data, [$item->getKey() => $expTimestamp]));
261
262
            /**
263
             * Set the expiration date
264
             * of the $tagsItem based
265
             * on the older $item
266
             * expiration date
267
             */
268
            if ($expTimestamp > $tagsItem->getExpirationDate()->getTimestamp()) {
269
                $tagsItem->expiresAt($item->getExpirationDate());
270
            }
271
            $this->driverWrite($tagsItem);
272
        }
273
274
        /**
275
         * Also update removed tags to
276
         * keep the index up to date
277
         */
278
        $tagsItems = $this->getItems($this->getTagKeys($item->getRemovedTags()));
279
280
        foreach ($tagsItems as $tagsItem) {
281
            $data = (array) $tagsItem->get();
282
283
            unset($data[ $item->getKey() ]);
284
            $tagsItem->set($data);
285
286
            /**
287
             * Recalculate the expiration date
288
             *
289
             * If the $tagsItem does not have
290
             * any cache item references left
291
             * then remove it from tagsItems index
292
             */
293
            if (count($data)) {
294
                $tagsItem->expiresAt(max($data));
295
                $this->driverWrite($tagsItem);
296
            } else {
297
                $this->driverDelete($tagsItem);
298
            }
299
        }
300
301
        return true;
302
    }
303
304
    /**
305
     * @param $key
306
     * @return string
307
     */
308
    public function getTagKey($key)
309
    {
310
        return self::DRIVER_TAGS_KEY_PREFIX . $key;
311
    }
312
313
    /**
314
     * @param $key
315
     * @return string
316
     */
317
    public function getTagKeys(array $keys)
318
    {
319
        foreach ($keys as &$key) {
320
            $key = $this->getTagKey($key);
321
        }
322
323
        return $keys;
324
    }
325
326
    /**
327
     * @param string $tagName
328
     * @return \phpFastCache\Cache\ExtendedCacheItemInterface[]
329
     * @throws InvalidArgumentException
330
     */
331
    public function getItemsByTag($tagName)
332
    {
333
        if (is_string($tagName)) {
334
            $driverResponse = $this->driverRead($this->getTagKey($tagName));
335
            if ($driverResponse) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $driverResponse of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
336
                $items = (array) $this->driverUnwrapData($driverResponse);
337
338
                return $this->getItems(array_unique(array_keys($items)));
339
            } else {
340
                return [];
341
            }
342
        } else {
343
            throw new InvalidArgumentException('$tagName must be a string');
344
        }
345
    }
346
347
    /**
348
     * @param array $tagNames
349
     * @return \phpFastCache\Cache\ExtendedCacheItemInterface[]
350
     * @throws InvalidArgumentException
351
     */
352
    public function getItemsByTags(array $tagNames)
353
    {
354
        $items = [];
355
        foreach (array_unique($tagNames) as $tagName) {
356
            $items = array_merge($items, $this->getItemsByTag($tagName));
357
        }
358
359
        return $items;
360
    }
361
362
    /**
363
     * @param string $tagName
364
     * @return bool|null
365
     * @throws InvalidArgumentException
366
     */
367
    public function deleteItemsByTag($tagName)
368
    {
369
        if (is_string($tagName)) {
370
            $return = null;
371
            foreach ($this->getItemsByTag($tagName) as $item) {
372
                $result = $this->driverDelete($item);
373
                if ($return !== false) {
374
                    $return = $result;
375
                }
376
            }
377
378
            return $return;
379
        } else {
380
            throw new InvalidArgumentException('$tagName must be a string');
381
        }
382
    }
383
384
    /**
385
     * @param array $tagNames
386
     * @return bool|null
387
     * @throws InvalidArgumentException
388
     */
389 View Code Duplication
    public function deleteItemsByTags(array $tagNames)
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...
390
    {
391
        $return = null;
392
        foreach ($tagNames as $tagName) {
393
            $result = $this->deleteItemsByTag($tagName);
394
            if ($return !== false) {
395
                $return = $result;
396
            }
397
        }
398
399
        return $return;
400
    }
401
402
    /**
403
     * @inheritdoc
404
     */
405 View Code Duplication
    public function incrementItemsByTag($tagName, $step = 1)
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...
406
    {
407
        if (is_string($tagName) && is_int($step)) {
408
            foreach ($this->getItemsByTag($tagName) as $item) {
409
                $item->increment($step);
410
                $this->saveDeferred($item);
411
            }
412
            return $this->commit();
413
        } else {
414
            throw new InvalidArgumentException('$tagName must be a string and $step an integer');
415
        }
416
    }
417
418
    /**
419
     * @inheritdoc
420
     */
421 View Code Duplication
    public function incrementItemsByTags(array $tagNames, $step = 1)
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...
422
    {
423
        $return = null;
424
        foreach ($tagNames as $tagName)
425
        {
426
            $result = $this->incrementItemsByTag($tagName, $step);
427
            if ($return !== false) {
428
                $return = $result;
429
            }
430
        }
431
432
        return $return;
433
    }
434
435
    /**
436
     * @inheritdoc
437
     */
438 View Code Duplication
    public function decrementItemsByTag($tagName, $step = 1)
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...
439
    {
440
        if (is_string($tagName) && is_int($step)) {
441
            foreach ($this->getItemsByTag($tagName) as $item) {
442
                $item->decrement($step);
443
                $this->saveDeferred($item);
444
            }
445
            return $this->commit();
446
        } else {
447
            throw new InvalidArgumentException('$tagName must be a string and $step an integer');
448
        }
449
    }
450
451
    /**
452
     * @inheritdoc
453
     */
454 View Code Duplication
    public function decrementItemsByTags(array $tagNames, $step = 1)
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...
455
    {
456
        $return = null;
457
        foreach ($tagNames as $tagName)
458
        {
459
            $result = $this->decrementItemsByTag($tagName, $step);
460
            if ($return !== false) {
461
                $return = $result;
462
            }
463
        }
464
465
        return $return;
466
    }
467
468
    /**
469
     * @inheritdoc
470
     */
471 View Code Duplication
    public function appendItemsByTag($tagName, $data)
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...
472
    {
473
        if (is_string($tagName)) {
474
            foreach ($this->getItemsByTag($tagName) as $item) {
475
                $item->append($data);
476
                $this->saveDeferred($item);
477
            }
478
            return $this->commit();
479
        } else {
480
            throw new InvalidArgumentException('$tagName must be a string');
481
        }
482
    }
483
484
    /**
485
     * @inheritdoc
486
     */
487 View Code Duplication
    public function appendItemsByTags(array $tagNames, $data)
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...
488
    {
489
        $return = null;
490
        foreach ($tagNames as $tagName)
491
        {
492
            $result = $this->decrementItemsByTag($tagName, $data);
0 ignored issues
show
Documentation introduced by
$data is of type array|string, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
493
            if ($return !== false) {
494
                $return = $result;
495
            }
496
        }
497
498
        return $return;
499
    }
500
501
    /**
502
     * @inheritdoc
503
     */
504 View Code Duplication
    public function prependItemsByTag($tagName, $data)
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...
505
    {
506
        if (is_string($tagName)) {
507
            foreach ($this->getItemsByTag($tagName) as $item) {
508
                $item->prepend($data);
509
                $this->saveDeferred($item);
510
            }
511
            return $this->commit();
512
        } else {
513
            throw new InvalidArgumentException('$tagName must be a string');
514
        }
515
    }
516
517
    /**
518
     * @inheritdoc
519
     */
520 View Code Duplication
    public function prependItemsByTags(array $tagNames, $data)
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...
521
    {
522
        $return = null;
523
        foreach ($tagNames as $tagName)
524
        {
525
            $result = $this->decrementItemsByTag($tagName, $data);
0 ignored issues
show
Documentation introduced by
$data is of type array|string, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
526
            if ($return !== false) {
527
                $return = $result;
528
            }
529
        }
530
531
        return $return;
532
    }
533
534
    /**
535
     * Abstract Drivers Methods
536
     */
537
538
    /**
539
     * @param string $key
540
     * @return array [
541
     *      'd' => 'THE ITEM DATA'
542
     *      't' => 'THE ITEM DATE EXPIRATION'
543
     *      'g' => 'THE ITEM TAGS'
544
     * ]
545
     *
546
     */
547
    abstract public function driverRead($key);
548
549
    /**
550
     * @param \Psr\Cache\CacheItemInterface $item
551
     * @return mixed
552
     */
553
    abstract public function driverWrite(CacheItemInterface $item);
554
555
    /**
556
     * @return bool
557
     */
558
    abstract public function driverClear();
559
560
    /**
561
     * @return bool
562
     */
563
    abstract public function driverConnect();
564
565
    /**
566
     * @param \Psr\Cache\CacheItemInterface $item
567
     * @return bool
568
     */
569
    abstract public function driverDelete(CacheItemInterface $item);
570
571
    /**
572
     * @param \Psr\Cache\CacheItemInterface $item
573
     * @return bool
574
     */
575
    abstract public function driverIsHit(CacheItemInterface $item);
576
577
}