Passed
Push — master ( c2b4c4...fd18cf )
by Alexander
03:23 queued 01:03
created

Cache::getValue()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 10.1359

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 19
ccs 6
cts 10
cp 0.6
rs 8.8333
cc 7
nc 4
nop 2
crap 10.1359
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Cache;
6
7
use DateInterval;
8
use DateTime;
9
use Yiisoft\Cache\Dependency\Dependency;
10
use Yiisoft\Cache\Exception\InvalidArgumentException;
11
use Yiisoft\Cache\Exception\RemoveCacheException;
12
use Yiisoft\Cache\Exception\SetCacheException;
13
use Yiisoft\Cache\Metadata\CacheItem;
14
use Yiisoft\Cache\Metadata\CacheItems;
15
16
use function gettype;
17
use function is_array;
18
use function is_int;
19
20
/**
21
 * Cache provides support for the data caching, including cache key composition and dependencies, and uses
22
 * "Probably early expiration" for cache stampede prevention. The actual data caching is performed via
23
 * PSR-16 {@see \Psr\SimpleCache\CacheInterface} instance passed to constructor.
24
 * You can use PSR-16 methods via {@see Cache::psr()}.
25
 *
26
 * @see \Yiisoft\Cache\CacheInterface
27
 */
28
final class Cache implements CacheInterface
29
{
30
    /**
31
     * @var DependencyAwareCache Decorator over the actual cache handler.
32
     */
33
    private DependencyAwareCache $psr;
34
35
    /**
36
     * @var CacheItems The items that store the metadata of each cache.
37
     */
38
    private CacheItems $items;
39
40
    /**
41
     * @var CacheKeyNormalizer Normalizes the cache key into a valid string.
42
     */
43
    private CacheKeyNormalizer $keyNormalizer;
44
45
    /**
46
     * @var int|null The default TTL for a cache entry. null meaning infinity, negative or zero results in the
47
     * cache key deletion. This value is used by {@see getOrSet()}, if the duration is not explicitly given.
48
     */
49
    private ?int $defaultTtl;
50
51
    /**
52
     * @param \Psr\SimpleCache\CacheInterface $handler The actual cache handler.
53
     * @param DateInterval|int|null $defaultTtl The default TTL for a cache entry.
54
     * null meaning infinity, negative orzero results in the cache key deletion.
55
     * This value is used by {@see getOrSet()}, if the duration is not explicitly given.
56
     */
57 82
    public function __construct(\Psr\SimpleCache\CacheInterface $handler, $defaultTtl = null)
58
    {
59 82
        $this->psr = new DependencyAwareCache($this, $handler);
60 82
        $this->items = new CacheItems();
61 82
        $this->keyNormalizer = new CacheKeyNormalizer();
62 82
        $this->defaultTtl = $this->normalizeTtl($defaultTtl);
63 76
    }
64
65 28
    public function psr(): \Psr\SimpleCache\CacheInterface
66
    {
67 28
        return $this->psr;
68
    }
69
70 47
    public function getOrSet($key, callable $callable, $ttl = null, Dependency $dependency = null, float $beta = 1.0)
71
    {
72 47
        $key = $this->keyNormalizer->normalize($key);
73 46
        $value = $this->getValue($key, $beta);
74
75 46
        return $value ?? $this->setAndGet($key, $callable, $ttl, $dependency);
76
    }
77
78 15
    public function remove($key): void
79
    {
80 15
        $key = $this->keyNormalizer->normalize($key);
81
82 14
        if (!$this->psr->delete($key)) {
83 2
            throw new RemoveCacheException($key);
84
        }
85
86 12
        $this->items->remove($key);
87 12
    }
88
89
    /**
90
     * Gets the cache value.
91
     *
92
     * @param string $key The unique key of this item in the cache.
93
     * @param float $beta The value for calculating the range that is used for "Probably early expiration" algorithm.
94
     *
95
     * @return mixed|null The cache value or `null` if the cache is outdated or a dependency has been changed.
96
     */
97 46
    private function getValue(string $key, float $beta)
98
    {
99 46
        if ($this->items->expired($key, $beta, $this)) {
100 4
            return null;
101
        }
102
103 46
        $value = $this->psr->get($key);
104
105 46
        if (is_array($value) && isset($value[1]) && $value[1] instanceof CacheItem) {
106
            [$value, $item] = $value;
107
108
            if ($item->key() !== $key || $item->expired($beta, $this)) {
109
                return null;
110
            }
111
112
            $this->items->set($item);
113
        }
114
115 46
        return $value;
116
    }
117
118
    /**
119
     * Sets the cache value and metadata, and returns the cache value.
120
     *
121
     * @param string $key The unique key of this item in the cache.
122
     * @param callable $callable The callable or closure that will be used to generate a value to be cached.
123
     * @param DateInterval|int|null $ttl The TTL of this value. If not set, default value is used.
124
     * @param Dependency|null $dependency The dependency of the cache value.
125
     *
126
     * @throws InvalidArgumentException Must be thrown if the `$key` or `$ttl` is not a legal value.
127
     * @throws SetCacheException Must be thrown if the data could not be set in the cache.
128
     *
129
     * @return mixed|null The cache value.
130
     */
131 46
    private function setAndGet(string $key, callable $callable, $ttl, ?Dependency $dependency)
132
    {
133 46
        $ttl = $this->normalizeTtl($ttl);
134 40
        $ttl ??= $this->defaultTtl;
135 40
        $value = $callable($this->psr);
136
137 40
        if ($dependency !== null) {
138 9
            $dependency->evaluateDependency($this);
139
        }
140
141 40
        $item = new CacheItem($key, $ttl, $dependency);
142
143 40
        if (!$this->psr->set($key, [$value, $item], $ttl)) {
144 2
            throw new SetCacheException($key, $value, $item);
145
        }
146
147 38
        $this->items->set($item);
148 38
        return $value;
149
    }
150
151
    /**
152
     * Normalizes cache TTL handling `null` value and {@see DateInterval} objects.
153
     *
154
     * @param mixed $ttl raw TTL.
155
     *
156
     * @throws InvalidArgumentException For invalid TTL.
157
     *
158
     * @return int|null TTL value as UNIX timestamp or null meaning infinity.
159
     */
160 82
    private function normalizeTtl($ttl): ?int
161
    {
162 82
        if ($ttl === null) {
163 76
            return null;
164
        }
165
166 19
        if ($ttl instanceof DateInterval) {
167 2
            return (new DateTime('@0'))->add($ttl)->getTimestamp();
168
        }
169
170 17
        if (is_int($ttl)) {
171 5
            return $ttl;
172
        }
173
174 12
        throw new InvalidArgumentException(sprintf(
175 12
            'Invalid TTL "%s" specified. It must be a \DateInterval instance, an integer, or null.',
176 12
            gettype($ttl),
177
        ));
178
    }
179
}
180