Passed
Pull Request — master (#869)
by Georges
05:32 queued 01:40
created

CacheItemPoolTrait   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 455
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 146
dl 0
loc 455
rs 6
c 3
b 0
f 0
wmc 55

17 Methods

Rating   Name   Duplication   Size   Complexity  
A setItem() 0 18 3
A getItems() 0 9 2
A __construct() 0 4 1
A validateCacheKey() 0 5 2
B save() 0 51 8
A attachItem() 0 18 4
A getIO() 0 3 1
A deleteItem() 0 26 4
A handleExpiredCacheItem() 0 31 3
A clear() 0 9 1
B getItem() 0 82 11
A deleteItems() 0 11 3
A deregisterItem() 0 9 2
A hasItem() 0 3 1
A isAttached() 0 6 2
A commit() 0 17 4
A saveDeferred() 0 12 3

How to fix   Complexity   

Complex Class

Complex classes like CacheItemPoolTrait 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.

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

1
<?php
2
3
/**
4
 *
5
 * This file is part of Phpfastcache.
6
 *
7
 * @license MIT License (MIT)
8
 *
9
 * For full copyright and license information, please see the docs/CREDITS.txt and LICENCE files.
10
 *
11
 * @author Georges.L (Geolim4)  <[email protected]>
12
 * @author Contributors  https://github.com/PHPSocialNetwork/phpfastcache/graphs/contributors
13
 */
14
15
declare(strict_types=1);
16
17
namespace Phpfastcache\Core\Pool;
18
19
use DateTime;
20
use Phpfastcache\Config\ConfigurationOptionInterface;
21
use Phpfastcache\Core\Item\ExtendedCacheItemInterface;
22
use Phpfastcache\Entities\DriverIO;
23
use Phpfastcache\Entities\ItemBatch;
24
use Phpfastcache\Event\Event;
25
use Phpfastcache\Event\EventManagerInterface;
26
use Phpfastcache\Event\EventReferenceParameter;
27
use Phpfastcache\Exceptions\PhpfastcacheCoreException;
28
use Phpfastcache\Exceptions\PhpfastcacheDriverException;
29
use Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException;
30
use Phpfastcache\Exceptions\PhpfastcacheIOException;
31
use Phpfastcache\Exceptions\PhpfastcacheLogicException;
32
use Psr\Cache\CacheItemInterface;
33
use RuntimeException;
34
35
/**
36
 * @method string[] driverUnwrapTags(array $wrapper)
37
 * @method void cleanItemTags(ExtendedCacheItemInterface $item)
38
 */
39
trait CacheItemPoolTrait
40
{
41
    use DriverBaseTrait {
42
        DriverBaseTrait::__construct as __driverBaseConstruct;
43
    }
44
45
    /**
46
     * @var string
47
     */
48
    protected static string $unsupportedKeyChars = '{}()/\@:';
49
50
    /**
51
     * @var ExtendedCacheItemInterface[]|CacheItemInterface[]
52
     */
53
    protected array $deferredList = [];
54
55
    /**
56
     * @var ExtendedCacheItemInterface[]|CacheItemInterface[]
57
     */
58
    protected array $itemInstances = [];
59
60
    protected DriverIO $IO;
61
62
    public function __construct(ConfigurationOptionInterface $config, string $instanceId, EventManagerInterface $em)
63
    {
64
        $this->IO = new DriverIO();
65
        $this->__driverBaseConstruct($config, $instanceId, $em);
66
    }
67
68
    /**
69
     * @throws PhpfastcacheLogicException
70
     * @throws PhpfastcacheInvalidArgumentException
71
     */
72
    public function setItem(CacheItemInterface $item): static
73
    {
74
        if (self::getItemClass() === $item::class) {
75
            if (!$this->getConfig()->isUseStaticItemCaching()) {
76
                throw new PhpfastcacheLogicException(
77
                    'The static item caching option (useStaticItemCaching) is disabled so you cannot attach an item.'
78
                );
79
            }
80
81
            $this->itemInstances[$item->getKey()] = $item;
82
83
            return $this;
84
        }
85
        throw new PhpfastcacheInvalidArgumentException(
86
            \sprintf(
87
                'Invalid cache item class "%s" for driver "%s".',
88
                get_class($item),
89
                get_class($this)
90
            )
91
        );
92
    }
93
94
    /**
95
     * @inheritDoc
96
     * @throws PhpfastcacheCoreException
97
     * @throws PhpfastcacheDriverException
98
     * @throws PhpfastcacheInvalidArgumentException
99
     * @throws PhpfastcacheLogicException
100
     */
101
    public function getItems(array $keys = []): iterable
102
    {
103
        $collection = [];
104
105
        foreach ($keys as $key) {
106
            $collection[$key] = $this->getItem($key);
107
        }
108
109
        return $collection;
110
    }
111
112
    /**
113
     * @param string $key
114
     * @return ExtendedCacheItemInterface
115
     * @throws PhpfastcacheCoreException
116
     * @throws PhpfastcacheInvalidArgumentException
117
     * @throws PhpfastcacheLogicException
118
     * @throws PhpfastcacheDriverException
119
     *
120
     * @SuppressWarnings(PHPMD.NPathComplexity)
121
     * @SuppressWarnings(PHPMD.GotoStatement)
122
     */
123
    public function getItem(string $key): ExtendedCacheItemInterface
124
    {
125
        /**
126
         * Replace array_key_exists by isset
127
         * due to performance issue on huge
128
         * loop dispatching operations
129
         */
130
        if (!isset($this->itemInstances[$key]) || !$this->getConfig()->isUseStaticItemCaching()) {
131
            $this->validateCacheKey($key);
132
133
            $cacheSlamsSpendSeconds = 0;
134
135
            $itemClass = self::getItemClass();
136
            /** @var $item ExtendedCacheItemInterface */
137
            $item = new $itemClass($this, $key, $this->eventManager);
138
            // $item = new (self::getItemClass())($this, $key, $this->eventManager);
139
            // Uncomment above when this one will be fixed: https://github.com/phpmd/phpmd/issues/952
140
141
            getItemDriverRead:
142
            {
143
                $driverArray = $this->driverRead($item);
144
145
            if ($driverArray) {
146
                $driverData = $this->driverUnwrapData($driverArray);
147
148
                if ($this->getConfig()->isPreventCacheSlams()) {
149
                    while ($driverData instanceof ItemBatch) {
150
                        if ($driverData->getItemDate()->getTimestamp() + $this->getConfig()->getCacheSlamsTimeout() < \time()) {
151
                            /**
152
                             * The timeout has been reached
153
                             * Consider that the batch has
154
                             * failed and serve an empty item
155
                             * to avoid get stuck with a
156
                             * batch item stored in driver
157
                             */
158
                            goto getItemDriverExpired;
159
                        }
160
161
                        $this->eventManager->dispatch(Event::CACHE_GET_ITEM_IN_SLAM_BATCH, $this, $driverData, $cacheSlamsSpendSeconds);
162
163
                        /**
164
                         * Wait for a second before
165
                         * attempting to get exit
166
                         * the current batch process
167
                         */
168
                        \sleep(1);
169
                        $cacheSlamsSpendSeconds++;
170
171
                        goto getItemDriverRead;
172
                    }
173
                }
174
175
                $item->set($driverData);
176
                $item->expiresAt($this->driverUnwrapEdate($driverArray));
177
178
                if ($this->getConfig()->isItemDetailedDate()) {
179
                    /**
180
                     * If the itemDetailedDate has been
181
                     * set after caching, we MUST inject
182
                     * a new DateTime object on the fly
183
                     */
184
                    $item->setCreationDate($this->driverUnwrapCdate($driverArray) ?: new DateTime());
185
                    $item->setModificationDate($this->driverUnwrapMdate($driverArray) ?: new DateTime());
186
                }
187
188
                $item->setTags($this->driverUnwrapTags($driverArray));
189
190
                getItemDriverExpired:
191
                $this->handleExpiredCacheItem($item);
192
            } else {
193
                $item->expiresAfter((int) abs($this->getConfig()->getDefaultTtl()));
194
            }
195
            }
196
        } else {
197
            $item = $this->itemInstances[$key];
198
        }
199
200
        $this->eventManager->dispatch(Event::CACHE_GET_ITEM, $this, $item);
201
202
        $item->isHit() ? $this->getIO()->incReadHit() : $this->getIO()->incReadMiss();
203
204
        return $item;
205
    }
206
207
    /**
208
     * @param string $key
209
     * @return bool
210
     * @throws PhpfastcacheCoreException
211
     * @throws PhpfastcacheDriverException
212
     * @throws PhpfastcacheInvalidArgumentException
213
     * @throws PhpfastcacheLogicException
214
     */
215
    public function hasItem(string $key): bool
216
    {
217
        return $this->getItem($key)->isHit();
218
    }
219
220
    /**
221
     * @return bool
222
     * @throws PhpfastcacheCoreException
223
     * @throws PhpfastcacheDriverException
224
     * @throws PhpfastcacheLogicException
225
     * @throws PhpfastcacheIOException
226
     */
227
    public function clear(): bool
228
    {
229
        $this->eventManager->dispatch(Event::CACHE_CLEAR_ITEM, $this, $this->itemInstances);
230
231
        $this->getIO()->incWriteHit();
232
        // Faster than detachAllItems()
233
        $this->itemInstances = [];
234
235
        return $this->driverClear();
236
    }
237
238
    /**
239
     * @inheritDoc
240
     * @throws PhpfastcacheCoreException
241
     * @throws PhpfastcacheDriverException
242
     * @throws PhpfastcacheInvalidArgumentException
243
     * @throws PhpfastcacheLogicException
244
     */
245
    public function deleteItems(array $keys): bool
246
    {
247
        $return = true;
248
        foreach ($keys as $key) {
249
            $result = $this->deleteItem($key);
250
            if ($result !== true) {
251
                $return = false;
252
            }
253
        }
254
255
        return $return;
256
    }
257
258
    /**
259
     * @param string $key
260
     * @return bool
261
     * @throws PhpfastcacheCoreException
262
     * @throws PhpfastcacheDriverException
263
     * @throws PhpfastcacheInvalidArgumentException
264
     * @throws PhpfastcacheLogicException
265
     */
266
    public function deleteItem(string $key): bool
267
    {
268
        $item = $this->getItem($key);
269
        if ($item->isHit() && $this->driverDelete($item)) {
270
            $item->setHit(false);
271
            $this->getIO()->incWriteHit();
272
273
            $this->eventManager->dispatch(Event::CACHE_DELETE_ITEM, $this, $item);
274
275
            /**
276
             * De-register the item instance
277
             * then collect gc cycles
278
             */
279
            $this->deregisterItem($key);
280
281
            /**
282
             * Perform a tag cleanup to avoid memory leaks
283
             */
284
            if (!\str_starts_with($key, TaggableCacheItemPoolInterface::DRIVER_TAGS_KEY_PREFIX)) {
285
                $this->cleanItemTags($item);
286
            }
287
288
            return true;
289
        }
290
291
        return false;
292
    }
293
294
    /**
295
     * @param CacheItemInterface $item
296
     * @return bool
297
     */
298
    public function saveDeferred(CacheItemInterface $item): bool
299
    {
300
        if (!\array_key_exists($item->getKey(), $this->itemInstances)) {
301
            $this->itemInstances[$item->getKey()] = $item;
302
        } elseif (\spl_object_hash($item) !== \spl_object_hash($this->itemInstances[$item->getKey()])) {
303
            throw new RuntimeException('Spl object hash mismatches ! You probably tried to save a detached item which has been already retrieved from cache.');
304
        }
305
306
        $this->eventManager->dispatch(Event::CACHE_SAVE_DEFERRED_ITEM, $this, $item);
307
        $this->deferredList[$item->getKey()] = $item;
308
309
        return true;
310
    }
311
312
    /**
313
     * @return bool
314
     * @throws PhpfastcacheCoreException
315
     * @throws PhpfastcacheDriverException
316
     * @throws PhpfastcacheInvalidArgumentException
317
     * @throws PhpfastcacheLogicException
318
     */
319
    public function commit(): bool
320
    {
321
        $this->eventManager->dispatch(Event::CACHE_COMMIT_ITEM, $this, new EventReferenceParameter($this->deferredList));
322
323
        if (\count($this->deferredList)) {
324
            $return = true;
325
            foreach ($this->deferredList as $key => $item) {
326
                $result = $this->save($item);
327
                if ($result !== true) {
328
                    unset($this->deferredList[$key]);
329
                    $return = $result;
330
                }
331
            }
332
333
            return $return;
334
        }
335
        return false;
336
    }
337
338
    /**
339
     * @param CacheItemInterface $item
340
     * @return bool
341
     * @throws PhpfastcacheCoreException
342
     * @throws PhpfastcacheDriverException
343
     * @throws PhpfastcacheIOException
344
     * @throws PhpfastcacheInvalidArgumentException
345
     * @throws PhpfastcacheLogicException
346
     */
347
    public function save(CacheItemInterface $item): bool
348
    {
349
        /**
350
         * @var ExtendedCacheItemInterface $item
351
         *
352
         * Replace array_key_exists by isset
353
         * due to performance issue on huge
354
         * loop dispatching operations
355
         */
356
        if (!isset($this->itemInstances[$item->getKey()])) {
357
            if ($this->getConfig()->isUseStaticItemCaching()) {
358
                $this->itemInstances[$item->getKey()] = $item;
359
            }
360
        } elseif (\spl_object_hash($item) !== \spl_object_hash($this->itemInstances[$item->getKey()])) {
361
            throw new RuntimeException('Spl object hash mismatches ! You probably tried to save a detached item which has been already retrieved from cache.');
362
        }
363
364
        $this->eventManager->dispatch(Event::CACHE_SAVE_ITEM, $this, $item);
365
366
        if ($this->getConfig()->isPreventCacheSlams()) {
367
            /**
368
             * @var $itemBatch ExtendedCacheItemInterface
369
             */
370
            $itemClassName = self::getItemClass();
371
            $itemBatch = new $itemClassName($this, $item->getKey(), $this->eventManager);
372
            $itemBatch->set(new ItemBatch($item->getKey(), new DateTime()))
373
                ->expiresAfter($this->getConfig()->getCacheSlamsTimeout());
374
375
            /**
376
             * To avoid SPL mismatches
377
             * we have to re-attach the
378
             * original item to the pool
379
             */
380
            $this->driverWrite($itemBatch);
381
            $this->detachItem($itemBatch);
0 ignored issues
show
Bug introduced by
It seems like detachItem() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

381
            $this->/** @scrutinizer ignore-call */ 
382
                   detachItem($itemBatch);
Loading history...
382
            $this->attachItem($item);
383
        }
384
385
386
        if ($this->driverWrite($item) && $this->driverWriteTags($item)) {
0 ignored issues
show
Bug introduced by
The method driverWriteTags() does not exist on Phpfastcache\Core\Pool\CacheItemPoolTrait. Did you maybe mean driverWrite()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

386
        if ($this->driverWrite($item) && $this->/** @scrutinizer ignore-call */ driverWriteTags($item)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
387
            $item->setHit(true);
388
            if ($this->getConfig()->isItemDetailedDate()) {
389
                $item->setModificationDate(new \DateTime());
390
            }
391
392
            $this->getIO()->incWriteHit();
393
394
            return true;
395
        }
396
397
        return false;
398
    }
399
400
    /**
401
     * @return DriverIO
402
     */
403
    public function getIO(): DriverIO
404
    {
405
        return $this->IO;
406
    }
407
408
    /**
409
     * @internal This method de-register an item from $this->itemInstances
410
     */
411
    protected function deregisterItem(string $item): static
412
    {
413
        unset($this->itemInstances[$item]);
414
415
        if (\gc_enabled()) {
416
            \gc_collect_cycles();
417
        }
418
419
        return $this;
420
    }
421
422
    /**
423
     * @throws PhpfastcacheLogicException
424
     */
425
    public function attachItem(CacheItemInterface $item): static
426
    {
427
        if (isset($this->itemInstances[$item->getKey()]) && \spl_object_hash($item) !== \spl_object_hash($this->itemInstances[$item->getKey()])) {
428
            throw new PhpfastcacheLogicException(
429
                'The item already exists and cannot be overwritten because the Spl object hash mismatches ! 
430
                You probably tried to re-attach a detached item which has been already retrieved from cache.'
431
            );
432
        }
433
434
        if (!$this->getConfig()->isUseStaticItemCaching()) {
435
            throw new PhpfastcacheLogicException(
436
                'The static item caching option (useStaticItemCaching) is disabled so you cannot attach an item.'
437
            );
438
        }
439
440
        $this->itemInstances[$item->getKey()] = $item;
441
442
        return $this;
443
    }
444
445
    public function isAttached(CacheItemInterface $item): bool
446
    {
447
        if (isset($this->itemInstances[$item->getKey()])) {
448
            return \spl_object_hash($item) === \spl_object_hash($this->itemInstances[$item->getKey()]);
449
        }
450
        return false;
451
    }
452
453
    protected function validateCacheKey(string $key): void
454
    {
455
        if (\preg_match('~([' . \preg_quote(self::$unsupportedKeyChars, '~') . ']+)~', $key, $matches)) {
456
            throw new PhpfastcacheInvalidArgumentException(
457
                'Unsupported key character detected: "' . $matches[1] . '". 
458
                    Please check: https://github.com/PHPSocialNetwork/phpfastcache/wiki/%5BV6%5D-Unsupported-characters-in-key-identifiers'
459
            );
460
        }
461
    }
462
463
    protected function handleExpiredCacheItem(ExtendedCacheItemInterface $item): void
464
    {
465
        if ($item->isExpired()) {
466
            /**
467
             * Using driverDelete() instead of delete()
468
             * to avoid infinite loop caused by
469
             * getItem() call in delete() method
470
             * As we MUST return an item in any
471
             * way, we do not de-register here
472
             */
473
            $this->driverDelete($item);
474
475
            /**
476
             * Reset the Item
477
             */
478
            $item->set(null)
479
                ->expiresAfter((int) abs($this->getConfig()->getDefaultTtl()))
480
                ->setHit(false)
481
                ->setTags([]);
482
483
            if ($this->getConfig()->isItemDetailedDate()) {
484
                /**
485
                 * If the itemDetailedDate has been
486
                 * set after caching, we MUST inject
487
                 * a new DateTime object on the fly
488
                 */
489
                $item->setCreationDate(new DateTime());
490
                $item->setModificationDate(new DateTime());
491
            }
492
        } else {
493
            $item->setHit(true);
494
        }
495
    }
496
}
497