Passed
Push — v9 ( e84ae2...80c47f )
by Georges
03:02
created

CacheItemPoolTrait   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 453
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 150
c 2
b 0
f 0
dl 0
loc 453
rs 6
wmc 55

15 Methods

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

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
            if (\preg_match('~([' . \preg_quote(self::$unsupportedKeyChars, '~') . ']+)~', $key, $matches)) {
132
                throw new PhpfastcacheInvalidArgumentException(
133
                    'Unsupported key character detected: "' . $matches[1] . '". 
134
                    Please check: https://github.com/PHPSocialNetwork/phpfastcache/wiki/%5BV6%5D-Unsupported-characters-in-key-identifiers'
135
                );
136
            }
137
138
            $cacheSlamsSpendSeconds = 0;
139
            $itemClass = self::getItemClass();
140
            /** @var $item ExtendedCacheItemInterface */
141
            $item = new $itemClass($this, $key, $this->eventManager);
142
143
            getItemDriverRead:
144
            {
145
                $driverArray = $this->driverRead($item);
146
147
            if ($driverArray) {
148
                if (!\is_array($driverArray)) {
0 ignored issues
show
introduced by
The condition is_array($driverArray) is always true.
Loading history...
149
                    throw new PhpfastcacheCoreException(
150
                        sprintf(
151
                            'The driverRead method returned an unexpected variable type: %s',
152
                            \gettype($driverArray)
153
                        )
154
                    );
155
                }
156
                $driverData = $this->driverUnwrapData($driverArray);
157
158
                if ($this->getConfig()->isPreventCacheSlams()) {
159
                    while ($driverData instanceof ItemBatch) {
160
                        if ($driverData->getItemDate()->getTimestamp() + $this->getConfig()->getCacheSlamsTimeout() < \time()) {
161
                            /**
162
                             * The timeout has been reached
163
                             * Consider that the batch has
164
                             * failed and serve an empty item
165
                             * to avoid get stuck with a
166
                             * batch item stored in driver
167
                             */
168
                            goto getItemDriverExpired;
169
                        }
170
171
                        $this->eventManager->dispatch(Event::CACHE_GET_ITEM_IN_SLAM_BATCH, $this, $driverData, $cacheSlamsSpendSeconds);
172
173
                        /**
174
                         * Wait for a second before
175
                         * attempting to get exit
176
                         * the current batch process
177
                         */
178
                        \sleep(1);
179
                        $cacheSlamsSpendSeconds++;
180
181
                        goto getItemDriverRead;
182
                    }
183
                }
184
185
                $item->set($driverData);
186
                $item->expiresAt($this->driverUnwrapEdate($driverArray));
187
188
                if ($this->getConfig()->isItemDetailedDate()) {
189
                    /**
190
                     * If the itemDetailedDate has been
191
                     * set after caching, we MUST inject
192
                     * a new DateTime object on the fly
193
                     */
194
                    $item->setCreationDate($this->driverUnwrapCdate($driverArray) ?: new DateTime());
195
                    $item->setModificationDate($this->driverUnwrapMdate($driverArray) ?: new DateTime());
196
                }
197
198
                $item->setTags($this->driverUnwrapTags($driverArray));
199
200
                getItemDriverExpired:
201
                if ($item->isExpired()) {
202
                    /**
203
                     * Using driverDelete() instead of delete()
204
                     * to avoid infinite loop caused by
205
                     * getItem() call in delete() method
206
                     * As we MUST return an item in any
207
                     * way, we do not de-register here
208
                     */
209
                    $this->driverDelete($item);
210
211
                    /**
212
                     * Reset the Item
213
                     */
214
                    $item->set(null)
215
                        ->expiresAfter((int) abs($this->getConfig()->getDefaultTtl()))
216
                        ->setHit(false)
217
                        ->setTags([]);
218
                    if ($this->getConfig()->isItemDetailedDate()) {
219
                        /**
220
                         * If the itemDetailedDate has been
221
                         * set after caching, we MUST inject
222
                         * a new DateTime object on the fly
223
                         */
224
                        $item->setCreationDate(new DateTime());
225
                        $item->setModificationDate(new DateTime());
226
                    }
227
                } else {
228
                    $item->setHit(true);
229
                }
230
            } else {
231
                $item->expiresAfter((int) abs($this->getConfig()->getDefaultTtl()));
232
            }
233
            }
234
        } else {
235
            $item = $this->itemInstances[$key];
236
        }
237
238
239
        if ($item !== null) {
240
            $this->eventManager->dispatch(Event::CACHE_GET_ITEM, $this, $item);
241
242
            $item->isHit() ? $this->getIO()->incReadHit() : $this->getIO()->incReadMiss();
243
        }
244
245
        return $item;
246
    }
247
248
    /**
249
     * @param string $key
250
     * @return bool
251
     * @throws PhpfastcacheCoreException
252
     * @throws PhpfastcacheDriverException
253
     * @throws PhpfastcacheInvalidArgumentException
254
     * @throws PhpfastcacheLogicException
255
     */
256
    public function hasItem(string $key): bool
257
    {
258
        return $this->getItem($key)->isHit();
259
    }
260
261
    /**
262
     * @return bool
263
     * @throws PhpfastcacheCoreException
264
     * @throws PhpfastcacheDriverException
265
     * @throws PhpfastcacheLogicException
266
     * @throws PhpfastcacheIOException
267
     */
268
    public function clear(): bool
269
    {
270
        $this->eventManager->dispatch(Event::CACHE_CLEAR_ITEM, $this, $this->itemInstances);
271
272
        $this->getIO()->incWriteHit();
273
        // Faster than detachAllItems()
274
        $this->itemInstances = [];
275
276
        return $this->driverClear();
277
    }
278
279
    /**
280
     * @inheritDoc
281
     * @throws PhpfastcacheCoreException
282
     * @throws PhpfastcacheDriverException
283
     * @throws PhpfastcacheInvalidArgumentException
284
     * @throws PhpfastcacheLogicException
285
     */
286
    public function deleteItems(array $keys): bool
287
    {
288
        $return = true;
289
        foreach ($keys as $key) {
290
            $result = $this->deleteItem($key);
291
            if ($result !== true) {
292
                $return = false;
293
            }
294
        }
295
296
        return $return;
297
    }
298
299
    /**
300
     * @param string $key
301
     * @return bool
302
     * @throws PhpfastcacheCoreException
303
     * @throws PhpfastcacheDriverException
304
     * @throws PhpfastcacheInvalidArgumentException
305
     * @throws PhpfastcacheLogicException
306
     */
307
    public function deleteItem(string $key): bool
308
    {
309
        $item = $this->getItem($key);
310
        if ($item->isHit() && $this->driverDelete($item)) {
311
            $item->setHit(false);
312
            $this->getIO()->incWriteHit();
313
314
            $this->eventManager->dispatch(Event::CACHE_DELETE_ITEM, $this, $item);
315
316
            /**
317
             * De-register the item instance
318
             * then collect gc cycles
319
             */
320
            $this->deregisterItem($key);
321
322
            /**
323
             * Perform a tag cleanup to avoid memory leaks
324
             */
325
            if (!\str_starts_with($key, TaggableCacheItemPoolInterface::DRIVER_TAGS_KEY_PREFIX)) {
326
                $this->cleanItemTags($item);
327
            }
328
329
            return true;
330
        }
331
332
        return false;
333
    }
334
335
    /**
336
     * @param CacheItemInterface $item
337
     * @return bool
338
     */
339
    public function saveDeferred(CacheItemInterface $item): bool
340
    {
341
        if (!\array_key_exists($item->getKey(), $this->itemInstances)) {
342
            $this->itemInstances[$item->getKey()] = $item;
343
        } elseif (\spl_object_hash($item) !== \spl_object_hash($this->itemInstances[$item->getKey()])) {
344
            throw new RuntimeException('Spl object hash mismatches ! You probably tried to save a detached item which has been already retrieved from cache.');
345
        }
346
347
        $this->eventManager->dispatch(Event::CACHE_SAVE_DEFERRED_ITEM, $this, $item);
348
        $this->deferredList[$item->getKey()] = $item;
349
350
        return true;
351
    }
352
353
    /**
354
     * @return bool
355
     * @throws PhpfastcacheCoreException
356
     * @throws PhpfastcacheDriverException
357
     * @throws PhpfastcacheInvalidArgumentException
358
     * @throws PhpfastcacheLogicException
359
     */
360
    public function commit(): bool
361
    {
362
        $this->eventManager->dispatch(Event::CACHE_COMMIT_ITEM, $this, new EventReferenceParameter($this->deferredList));
363
364
        if (\count($this->deferredList)) {
365
            $return = true;
366
            foreach ($this->deferredList as $key => $item) {
367
                $result = $this->save($item);
368
                if ($result !== true) {
369
                    unset($this->deferredList[$key]);
370
                    $return = $result;
371
                }
372
            }
373
374
            return $return;
375
        }
376
        return false;
377
    }
378
379
    /**
380
     * @param CacheItemInterface $item
381
     * @return bool
382
     * @throws PhpfastcacheCoreException
383
     * @throws PhpfastcacheDriverException
384
     * @throws PhpfastcacheIOException
385
     * @throws PhpfastcacheInvalidArgumentException
386
     * @throws PhpfastcacheLogicException
387
     */
388
    public function save(CacheItemInterface $item): bool
389
    {
390
        /**
391
         * @var ExtendedCacheItemInterface $item
392
         *
393
         * Replace array_key_exists by isset
394
         * due to performance issue on huge
395
         * loop dispatching operations
396
         */
397
        if (!isset($this->itemInstances[$item->getKey()])) {
398
            if ($this->getConfig()->isUseStaticItemCaching()) {
399
                $this->itemInstances[$item->getKey()] = $item;
400
            }
401
        } elseif (\spl_object_hash($item) !== \spl_object_hash($this->itemInstances[$item->getKey()])) {
402
            throw new RuntimeException('Spl object hash mismatches ! You probably tried to save a detached item which has been already retrieved from cache.');
403
        }
404
405
        $this->eventManager->dispatch(Event::CACHE_SAVE_ITEM, $this, $item);
406
407
        if ($this->getConfig()->isPreventCacheSlams()) {
408
            /**
409
             * @var $itemBatch ExtendedCacheItemInterface
410
             */
411
            $itemClassName = self::getItemClass();
412
            $itemBatch = new $itemClassName($this, $item->getKey(), $this->eventManager);
413
            $itemBatch->set(new ItemBatch($item->getKey(), new DateTime()))
414
                ->expiresAfter($this->getConfig()->getCacheSlamsTimeout());
415
416
            /**
417
             * To avoid SPL mismatches
418
             * we have to re-attach the
419
             * original item to the pool
420
             */
421
            $this->driverWrite($itemBatch);
422
            $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

422
            $this->/** @scrutinizer ignore-call */ 
423
                   detachItem($itemBatch);
Loading history...
423
            $this->attachItem($item);
424
        }
425
426
427
        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

427
        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...
428
            $item->setHit(true);
429
            if ($this->getConfig()->isItemDetailedDate()) {
430
                $item->setModificationDate(new \DateTime());
431
            }
432
433
            $this->getIO()->incWriteHit();
434
435
            return true;
436
        }
437
438
        return false;
439
    }
440
441
    /**
442
     * @return DriverIO
443
     */
444
    public function getIO(): DriverIO
445
    {
446
        return $this->IO;
447
    }
448
449
    /**
450
     * @internal This method de-register an item from $this->itemInstances
451
     */
452
    protected function deregisterItem(string $item): static
453
    {
454
        unset($this->itemInstances[$item]);
455
456
        if (\gc_enabled()) {
457
            \gc_collect_cycles();
458
        }
459
460
        return $this;
461
    }
462
463
    /**
464
     * @throws PhpfastcacheLogicException
465
     */
466
    public function attachItem(CacheItemInterface $item): static
467
    {
468
        if (isset($this->itemInstances[$item->getKey()]) && \spl_object_hash($item) !== \spl_object_hash($this->itemInstances[$item->getKey()])) {
469
            throw new PhpfastcacheLogicException(
470
                'The item already exists and cannot be overwritten because the Spl object hash mismatches ! 
471
                You probably tried to re-attach a detached item which has been already retrieved from cache.'
472
            );
473
        }
474
475
        if (!$this->getConfig()->isUseStaticItemCaching()) {
476
            throw new PhpfastcacheLogicException(
477
                'The static item caching option (useStaticItemCaching) is disabled so you cannot attach an item.'
478
            );
479
        }
480
481
        $this->itemInstances[$item->getKey()] = $item;
482
483
        return $this;
484
    }
485
486
    public function isAttached(CacheItemInterface $item): bool
487
    {
488
        if (isset($this->itemInstances[$item->getKey()])) {
489
            return \spl_object_hash($item) === \spl_object_hash($this->itemInstances[$item->getKey()]);
490
        }
491
        return false;
492
    }
493
}
494