Passed
Pull Request — master (#77)
by Evgeniy
01:59
created

Cache::handler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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