Passed
Push — master ( b0ec1a...8763d4 )
by Divine Niiquaye
01:43
created

FastCache::save()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 63
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 35
c 0
b 0
f 0
nc 4
nop 3
dl 0
loc 63
rs 8.4266

How to fix   Long Method   

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 BiuradPHP\Cache;
19
20
use BiuradPHP\Cache\Exceptions\CacheException;
21
use BiuradPHP\Cache\Exceptions\InvalidArgumentException;
22
use BiuradPHP\Cache\Interfaces\FastCacheInterface;
23
use Cache\Adapter\Common\CacheItem as PhpCacheItem;
24
use Cache\Adapter\Common\PhpCachePool;
25
use Closure;
26
use Generator;
27
use Psr\Cache\CacheItemInterface;
28
use Psr\Cache\CacheItemPoolInterface;
29
use Psr\SimpleCache\CacheInterface;
30
use stdClass;
31
use Throwable;
32
33
/**
34
 * Implements the cache for a application.
35
 */
36
class FastCache implements FastCacheInterface
37
{
38
    /** @internal */
39
    public const NAMESPACE_SEPARATOR = "\x00";
40
41
    public const NAMESPACE = 'CACHE_KEY[%s]';
42
43
    /** @var CacheInterface|CacheItemPoolInterface */
44
    private $storage;
45
46
    /** @var string */
47
    private $namespace;
48
49
    /** @var array<string,mixed> */
50
    private $computing = [];
51
52
    /**
53
     * @param CacheInterface|CacheItemPoolInterface $storage
54
     * @param string                                $namespace
55
     */
56
    final public function __construct($storage, string $namespace = self::NAMESPACE)
57
    {
58
        if (
59
            !($storage instanceof CacheInterface || $storage instanceof CacheItemPoolInterface)
0 ignored issues
show
introduced by
$storage is always a sub-type of Psr\Cache\CacheItemPoolInterface.
Loading history...
60
        ) {
61
            throw new CacheException('$storage can only implements psr-6 or psr-16 cache interface');
62
        }
63
64
        $this->storage   = $storage;
65
        $this->namespace = $namespace . self::NAMESPACE_SEPARATOR;
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71
    public function getStorage()
72
    {
73
        return $this->storage;
74
    }
75
76
    /**
77
     * Returns cache namespace.
78
     */
79
    public function getNamespace(): string
80
    {
81
        return (string) \substr(\sprintf($this->namespace, null), 0, -1);
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87
    public function derive(string $namespace): FastCache
88
    {
89
        return new static($this->storage, $this->namespace . $namespace);
90
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95
    public function load($key, callable $fallback = null, ?float $beta = null)
96
    {
97
        $data = $this->doFetch($this->generateKey($key));
98
99
        if ($data instanceof CacheItemInterface) {
100
            $data = $data->isHit() ? $data->get() : null;
101
        }
102
103
        if (null === $data && $fallback) {
104
            return $this->save($key, $fallback, $beta);
105
        }
106
107
        return $data;
108
    }
109
110
    /**
111
     * {@inheritDoc}
112
     */
113
    public function bulkLoad(array $keys, callable $fallback = null, ?float $beta = null): array
114
    {
115
        if (0 === \count($keys)) {
116
            return [];
117
        }
118
119
        foreach ($keys as $key) {
120
            if (!\is_scalar($key)) {
121
                throw new InvalidArgumentException('Only scalar keys are allowed in bulkLoad()');
122
            }
123
        }
124
        $storageKeys = \array_map([$this, 'generateKey'], $keys);
125
        $cacheData   = $this->doFetch($storageKeys, true);
126
        $result      = [];
127
128
        if ($cacheData instanceof Generator) {
129
            $cacheData = \iterator_to_array($cacheData);
130
        }
131
132
        foreach ($keys as $i => $key) {
133
            $storageKey = $storageKeys[$i];
134
135
            if (isset($cacheData[$storageKey])) {
136
                $result[$key] = $cacheData[$storageKey];
137
            } elseif ($fallback) {
138
                $result[$key] = $this->save(
139
                    $key,
140
                    function (CacheItemInterface $item, bool $save) use ($key, $fallback) {
141
                        return $fallback(...[$key, &$item, &$save]);
142
                    },
143
                    $beta
144
                );
145
            } else {
146
                $result[$key] = null;
147
            }
148
        }
149
150
        return \array_map(
151
            function ($value) {
152
                if ($value instanceof CacheItemInterface) {
153
                    return $value->get();
154
                }
155
156
                return $value;
157
            },
158
            $result
159
        );
160
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165
    public function save($key, ?callable $callback = null, ?float $beta = null)
166
    {
167
        $key = $this->generateKey($key);
168
169
        if (null === $callback) {
170
            $this->doDelete($key);
171
172
            return false;
173
        }
174
175
        if (0 > $beta = $beta ?? 1.0) {
176
            throw new InvalidArgumentException(
177
                \sprintf(
178
                    'Argument "$beta" provided to "%s::get()" must be a positive number, %f given.',
179
                    static::class,
180
                    $beta
181
                )
182
            );
183
        }
184
185
        static $setExpired;
186
187
        $setExpired = Closure::bind(
188
            static function (CacheItem $item) {
189
                if (null === $item->expiry) {
0 ignored issues
show
Bug introduced by
The property expiry is declared protected in BiuradPHP\Cache\CacheItem and cannot be accessed from this context.
Loading history...
190
                    return null;
191
                }
192
193
                return (int) (0.1 + $item->expiry - \microtime(true));
194
            },
195
            null,
196
            CacheItem::class
197
        );
198
199
        if ($this->storage instanceof PhpCachePool) {
200
            $setExpired = static function (PhpCacheItem $item) {
201
                return $item->getExpirationTimestamp();
202
            };
203
        }
204
205
        $callback = function (CacheItemInterface $item, bool $save) use ($key, $callback) {
206
            // don't wrap nor save recursive calls
207
            if (isset($this->computing[$key])) {
208
                $value = $callback(...[&$item, &$save]);
209
                $save  = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $save is dead and can be removed.
Loading history...
210
211
                return $value;
212
            }
213
214
            $this->computing[$key] = $key;
215
216
            try {
217
                return $value = $callback(...[&$item, &$save]);
0 ignored issues
show
Unused Code introduced by
The assignment to $value is dead and can be removed.
Loading history...
218
            } catch (Throwable $e) {
219
                $this->doDelete($key);
220
221
                throw $e;
222
            } finally {
223
                unset($this->computing[$key]);
224
            }
225
        };
226
227
        return $this->doSave($this->storage, $key, $callback, $setExpired, $beta);
228
    }
229
230
    /**
231
     * {@inheritdoc}
232
     */
233
    public function delete($key): void
234
    {
235
        $this->save($key, null);
236
    }
237
238
    /**
239
     * {@inheritdoc}
240
     */
241
    public function call(callable $callback)
242
    {
243
        $key = \func_get_args();
244
245
        if (\is_array($callback) && \is_object($callback[0])) {
246
            $key[0][0] = \get_class($callback[0]);
247
        }
248
249
        return $this->load($key, function (CacheItemInterface $item, bool $save) use ($callback, $key) {
250
            $dependencies = \array_merge(\array_slice($key, 1), [&$item, &$save]);
251
252
            return $callback(...$dependencies);
253
        });
254
    }
255
256
    /**
257
     * {@inheritdoc}
258
     */
259
    public function wrap(callable $callback, ?float $beta = null): callable
260
    {
261
        return function () use ($callback, $beta) {
262
            $key = [$callback, \func_get_args()];
263
264
            if (\is_array($callback) && \is_object($callback[0])) {
265
                $key[0][0] = \get_class(\current($callback));
266
            }
267
268
            if (null === $data = $this->load($key)) {
269
                $data = $this->save(
270
                    $key,
271
                    function (CacheItemInterface $item, bool $save) use ($callback, $key) {
272
                        $dependencies = \array_merge($key[1], [&$item, &$save]);
273
274
                        return $callback(...$dependencies);
275
                    },
276
                    $beta
277
                );
278
            }
279
280
            return $data;
281
        };
282
    }
283
284
    /**
285
     * {@inheritdoc}
286
     */
287
    public function start($key): ?OutputHelper
288
    {
289
        $data = $this->load($key);
290
291
        if (null === $data) {
292
            return new OutputHelper($this, $key);
293
        }
294
        echo $data;
295
296
        return null;
297
    }
298
299
    /**
300
     * Generates internal cache key.
301
     *
302
     * @param mixed $key
303
     *
304
     * @return string
305
     */
306
    protected function generateKey($key): string
307
    {
308
        if (\is_array($key) && \current($key) instanceof Closure) {
309
            $key = \spl_object_id($key[0]);
310
        }
311
312
        $key = \md5(\is_scalar($key) ? (string) $key : \serialize($key));
313
314
        return \strpos($this->namespace, '%s') ? \sprintf($this->namespace, $key) : $this->namespace . $key;
315
    }
316
317
    /**
318
     * Save cache item.
319
     *
320
     * @param CacheInterface|CacheItemPoolInterface $storage
321
     * @param string                                $key
322
     * @param Closure                               $callback
323
     * @param Closure                               $setExpired
324
     * @param null|float                            $beta
325
     *
326
     * @return mixed The corresponding values found in the cache
327
     */
328
    protected function doSave($storage, string $key, Closure $callback, Closure $setExpired, ?float $beta)
329
    {
330
        if ($storage instanceof CacheItemPoolInterface) {
331
            $item = $this->storage->getItem($key);
0 ignored issues
show
Bug introduced by
The method getItem() does not exist on Psr\SimpleCache\CacheInterface. It seems like you code against a sub-type of Psr\SimpleCache\CacheInterface such as Cache\Adapter\Common\AbstractCachePool. ( Ignorable by Annotation )

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

331
            /** @scrutinizer ignore-call */ 
332
            $item = $this->storage->getItem($key);
Loading history...
332
333
            if (!$item->isHit() || \INF === $beta) {
334
                $save   = true;
335
                $result = $callback(...[$item, $save]);
336
337
                if (true === $save) {
0 ignored issues
show
introduced by
The condition true === $save is always true.
Loading history...
338
                    if (!$result instanceof CacheItemInterface) {
339
                        $item->set($result);
340
                        $this->storage->save($item);
0 ignored issues
show
Bug introduced by
The method save() does not exist on Psr\SimpleCache\CacheInterface. It seems like you code against a sub-type of Psr\SimpleCache\CacheInterface such as Cache\Adapter\Common\AbstractCachePool. ( Ignorable by Annotation )

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

340
                        $this->storage->/** @scrutinizer ignore-call */ 
341
                                        save($item);
Loading history...
341
                    } else {
342
                        $this->storage->save($result);
343
                    }
344
                }
345
            }
346
347
            return $item->get();
348
        }
349
350
        $save   = true;
351
        $item   = $storage instanceof PhpCachePool ? new PhpCacheItem($key) : new CacheItem();
352
        $result = $callback(...[$item, $save]);
353
354
        if ($result instanceof CacheItemInterface) {
355
            $result = $result->get();
356
        }
357
358
        $storage->set($key, $result, $setExpired($item));
359
360
        return $result;
361
    }
362
363
    /**
364
     * Fetch cache item.
365
     *
366
     * @param array|string $id The cache identifier to fetch
367
     *
368
     * @return mixed The corresponding values found in the cache
369
     */
370
    protected function doFetch($ids, bool $multiple = false)
371
    {
372
        if ($this->storage instanceof CacheItemPoolInterface) {
373
            return !$multiple ? $this->storage->getItem($ids) : $this->storage->getItems($ids);
374
        }
375
376
        return !$multiple ? $this->storage->get($ids) : $this->storage->getMultiple($ids, new stdClass());
377
    }
378
379
    /**
380
     * Remove an item from cache.
381
     *
382
     * @param string $id An identifier that should be removed from cache
383
     *
384
     * @return bool True if the items were successfully removed, false otherwise
385
     */
386
    protected function doDelete(string $id)
387
    {
388
        if ($this->storage instanceof CacheItemPoolInterface) {
389
            return $this->storage->deleteItem($id);
390
        }
391
392
        return $this->storage->delete($id);
393
    }
394
}
395