Completed
Push — master ( b2545f...e44389 )
by Divine Niiquaye
02:25
created

CacheItemPool::__construct()   B

Complexity

Conditions 11
Paths 1

Size

Total Lines 58
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 37
nc 1
nop 1
dl 0
loc 58
rs 7.3166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of BiuradPHP opensource projects.
7
 *
8
 * PHP version 7.1 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Biurad\Cache;
19
20
use BadMethodCallException;
21
use Closure;
22
use Exception;
23
use Generator;
24
use Psr\Cache\CacheItemInterface;
25
use Psr\Cache\CacheItemPoolInterface;
26
use Psr\SimpleCache\CacheInterface;
27
use stdClass;
28
use Traversable;
29
30
class CacheItemPool implements CacheItemPoolInterface
31
{
32
    /**
33
     * @var CacheInterface
34
     */
35
    protected $pool;
36
37
    /**
38
     * @var Closure needs to be set by class, signature is function(string <key>, mixed <value>, bool <isHit>)
39
     */
40
    private $createCacheItem;
41
42
    /**
43
     * @var Closure needs to be set by class, signature is function(array <deferred>, array <&expiredIds>)
44
     */
45
    private $mergeByLifetime;
46
47
    /** @var array<string,CacheItemInterface> */
48
    private $deferred = [];
49
50
    /** @var array<string,string> */
51
    private $ids = [];
52
53
    /** @var stdclass */
54
    private $miss;
55
56
    /**
57
     * Cache Constructor.
58
     *
59
     * @psalm-suppress InaccessibleProperty
60
     * @psalm-suppress PossiblyUndefinedVariable
61
     *
62
     * @param CacheInterface $psr16
63
     */
64
    public function __construct(CacheInterface $psr16)
65
    {
66
        $this->pool = $psr16;
67
        $this->miss = new stdClass();
68
69
        $this->createCacheItem = Closure::bind(
70
            static function ($key, $value, $isHit) {
71
                $item = new CacheItem();
72
                $item->key = $key;
0 ignored issues
show
Bug introduced by
The property key is declared protected in Biurad\Cache\CacheItem and cannot be accessed from this context.
Loading history...
73
                $item->value = $v = $value;
0 ignored issues
show
Bug introduced by
The property value is declared protected in Biurad\Cache\CacheItem and cannot be accessed from this context.
Loading history...
74
                $item->isHit = $isHit;
0 ignored issues
show
Bug introduced by
The property isHit is declared protected in Biurad\Cache\CacheItem and cannot be accessed from this context.
Loading history...
75
                $item->defaultLifetime = 0;
0 ignored issues
show
Bug introduced by
The property defaultLifetime is declared protected in Biurad\Cache\CacheItem and cannot be accessed from this context.
Loading history...
76
                // Detect wrapped values that encode for their expiry and creation duration
77
                // For compactness, these values are packed in the key of an array using
78
                // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
79
                if (
80
                    \is_array($v) &&
81
                    1 === \count($v) &&
82
                    10 === \strlen($k = (string) \key($v)) &&
83
                    "\x9D" === $k[0] &&
84
                    "\0" === $k[5] &&
85
                    "\x5F" === $k[9]
86
                ) {
87
                    $item->value = $v[$k];
88
                }
89
90
                return $item;
91
            },
92
            null,
93
            CacheItem::class
94
        );
95
        $getId                 = Closure::fromCallable([$this, 'getId']);
96
        $this->mergeByLifetime = Closure::bind(
97
            static function ($deferred, &$expiredIds) use ($getId) {
98
                $byLifetime = [];
99
                $now = \microtime(true);
100
                $expiredIds = [];
101
102
                foreach ($deferred as $key => $item) {
103
                    $key = (string) $key;
104
105
                    if (null === $item->expiry) {
106
                        $ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0;
107
                    } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
108
                        $expiredIds[] = $getId($key);
109
110
                        continue;
111
                    }
112
113
                    // For compactness, expiry and creation duration are packed in the key of an array,
114
                    // using magic numbers as separators
115
                    $byLifetime[$ttl][$getId($key)] = $item->value;
116
                }
117
118
                return $byLifetime;
119
            },
120
            null,
121
            CacheItem::class
122
        );
123
    }
124
125
    public function __destruct()
126
    {
127
        if (\count($this->deferred) > 0) {
128
            $this->commit();
129
        }
130
    }
131
132
    public function __sleep(): void
133
    {
134
        throw new BadMethodCallException('Cannot serialize ' . __CLASS__);
135
    }
136
137
    public function __wakeup(): void
138
    {
139
        throw new BadMethodCallException('Cannot unserialize ' . __CLASS__);
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145
    public function getItem($key)
146
    {
147
        if (\count($this->deferred) > 0) {
148
            $this->commit();
149
        }
150
151
        $id    = $this->getId($key);
152
        $f     = $this->createCacheItem;
153
        $isHit = false;
154
        $value = null;
155
156
        try {
157
            foreach ($this->doFetch([$id]) as $value) {
158
                $isHit = true;
159
            }
160
161
            return $f($key, $value, $isHit);
162
        } catch (Exception $e) {
163
            return $f($key, null, false);
164
        }
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     *
170
     * @return array<string,CacheItemInterface>|Traversable
171
     */
172
    public function getItems(array $keys = [])
173
    {
174
        if (\count($this->deferred) > 0) {
175
            $this->commit();
176
        }
177
        $kIds = [];
178
179
        foreach ($keys as $key) {
180
            $kIds[] = $this->getId($key);
181
        }
182
183
        $items  = $this->doFetch($kIds);
184
        $kIds   = \array_combine($kIds, $keys);
185
186
        return $this->generateItems($items, $kIds);
0 ignored issues
show
Bug introduced by
It seems like $kIds can also be of type false; however, parameter $keys of Biurad\Cache\CacheItemPool::generateItems() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

186
        return $this->generateItems($items, /** @scrutinizer ignore-type */ $kIds);
Loading history...
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     */
192
    public function hasItem($key)
193
    {
194
        $id = $this->getId($key);
195
196
        if (isset($this->deferred[$key])) {
197
            $this->commit();
198
        }
199
200
        return $this->doHave($id);
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206
    public function clear()
207
    {
208
        $this->deferred = [];
209
210
        return $this->doClear();
211
    }
212
213
    /**
214
     * {@inheritdoc}
215
     */
216
    public function deleteItem($key)
217
    {
218
        return $this->deleteItems([$key]);
219
    }
220
221
    /**
222
     * {@inheritdoc}
223
     */
224
    public function deleteItems(array $keys)
225
    {
226
        $kIds = [];
227
228
        foreach ($keys as $key) {
229
            $kIds[$key] = $this->getId($key);
230
            unset($this->deferred[$key]);
231
        }
232
233
        return $this->doDelete($kIds);
234
    }
235
236
    /**
237
     * {@inheritdoc}
238
     */
239
    public function save(CacheItemInterface $item)
240
    {
241
        $this->saveDeferred($item);
242
243
        return $this->commit();
244
    }
245
246
    /**
247
     * {@inheritdoc}
248
     */
249
    public function saveDeferred(CacheItemInterface $item)
250
    {
251
        $this->deferred[$item->getKey()] = $item;
252
253
        return true;
254
    }
255
256
    /**
257
     * {@inheritdoc}
258
     */
259
    public function commit()
260
    {
261
        $ok             = true;
262
        $byLifetime     = $this->mergeByLifetime;
263
        $this->deferred = $expiredIds = [];
264
        $byLifetime     = $byLifetime($this->deferred, $expiredIds);
265
266
        if (\count($expiredIds) > 0) {
267
            $this->doDelete($expiredIds);
268
        }
269
270
        foreach ($byLifetime as $lifetime => $values) {
271
            if ($this->doSave($values, $lifetime)) {
272
                continue;
273
            }
274
275
            $ok = false;
276
        }
277
278
        return $ok;
279
    }
280
281
    /**
282
     * Fetches several cache items.
283
     *
284
     * @param array<mixed,string> $ids The cache identifiers to fetch
285
     *
286
     * @return iterable<string,CacheItemInterface> The corresponding values found in the cache
287
     */
288
    protected function doFetch(array $ids)
289
    {
290
        $fetched = $this->pool->getMultiple($ids, $this->miss);
291
292
        if ($fetched instanceof Generator) {
293
            $fetched = $fetched->getReturn();
294
        }
295
296
        foreach ($fetched as $key => $value) {
297
            if ($this->miss !== $value) {
298
                yield $key => $value;
299
            }
300
        }
301
    }
302
303
    /**
304
     * Confirms if the cache contains specified cache item.
305
     *
306
     * @param string $id The identifier for which to check existence
307
     *
308
     * @return bool True if item exists in the cache, false otherwise
309
     */
310
    protected function doHave(string $id)
311
    {
312
        return $this->pool->has($id);
313
    }
314
315
    /**
316
     * Deletes all items in the pool.
317
     *
318
     * @return bool True if the pool was successfully cleared, false otherwise
319
     */
320
    protected function doClear()
321
    {
322
        return $this->pool->clear();
323
    }
324
325
    /**
326
     * Removes multiple items from the pool.
327
     *
328
     * @param array<string,string> $ids An array of identifiers that should be removed from the pool
329
     *
330
     * @return bool True if the items were successfully removed, false otherwise
331
     */
332
    protected function doDelete(array $ids)
333
    {
334
        return $this->pool->deleteMultiple($ids);
335
    }
336
337
    /**
338
     * Persists several cache items immediately.
339
     *
340
     * @param array<string,mixed> $values   The values to cache, indexed by their cache identifier
341
     * @param int                 $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning
342
     *
343
     * @return bool a boolean stating if caching succeeded or not
344
     */
345
    protected function doSave(array $values, int $lifetime)
346
    {
347
        return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
348
    }
349
350
    /**
351
     * @param iterable<string,mixed> $items
352
     * @param array<string,string>   $keys
353
     *
354
     * @return array<string,CacheItemInterface>|Traversable
355
     */
356
    private function generateItems(iterable $items, array &$keys)
357
    {
358
        $f = $this->createCacheItem;
359
360
        foreach ($items as $id => $value) {
361
            if (!isset($keys[$id])) {
362
                $id = \key($keys);
363
            }
364
            $key = $keys[$id];
365
            unset($keys[$id]);
366
367
            yield $key => $f($key, $value, true);
368
        }
369
370
        foreach ($keys as $key) {
371
            yield $key => $f($key, null, false);
372
        }
373
    }
374
375
    /**
376
     * @param mixed $key
377
     *
378
     * @return string
379
     */
380
    private function getId($key)
381
    {
382
        if (\is_string($key) && isset($this->ids[$key])) {
383
            return $this->ids[$key];
384
        }
385
        CacheItem::validateKey($key);
386
        $this->ids[$key] = $key;
387
388
        return $key;
389
    }
390
}
391