Cache::setAndGet()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

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