Test Failed
Pull Request — master (#74)
by Evgeniy
01:53
created

Cache::prepareDataForSetOrAddMultiple()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 7
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 11
ccs 8
cts 8
cp 1
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Cache;
6
7
use DateInterval;
8
use DateTime;
9
use InvalidArgumentException;
10
use Yiisoft\Cache\Dependency\Dependency;
11
use Yiisoft\Cache\Exception\SetCacheException;
12
use Yiisoft\Cache\Metadata\CacheItems;
13
14
use function gettype;
15
use function is_array;
16
use function is_int;
17
18
/**
19
 * Cache provides support for the data caching, including cache key composition and dependencies.
20
 * The actual data caching is performed via {@see Cache::$handler}, which should be configured
21
 * to be {@see \Psr\SimpleCache\CacheInterface} instance.
22
 *
23
 * A value can be stored in the cache by calling {@see CacheInterface::set()} and be retrieved back
24
 * later (in the same or different request) by {@see CacheInterface::get()}. In both operations,
25
 * a key identifying the value is required. An expiration time and/or a {@see Dependency}
26
 * can also be specified when calling {@see CacheInterface::set()}. If the value expires or the dependency
27
 * changes at the time of calling {@see CacheInterface::get()}, the cache will return no data.
28
 *
29
 * A typical usage pattern of cache is like the following:
30
 *
31
 * ```php
32
 * $key = 'demo';
33
 * $data = $cache->get($key);
34
 * if ($data === null) {
35
 *     // ...generate $data here...
36
 *     $cache->set($key, $data, $ttl, $dependency);
37
 * }
38
 * ```
39
 *
40
 * For more details and usage information on Cache, see
41
 * [PSR-16 specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md).
42
 */
43
final class Cache implements CacheInterface
44
{
45
    /**
46
     * @var \Psr\SimpleCache\CacheInterface actual cache handler.
47
     */
48
    private \Psr\SimpleCache\CacheInterface $handler;
49
50
    private CacheItems $metadata;
51
    private CacheKeyNormalizer $keyNormalizer;
52
53
    /**
54
     * @var string a string prefixed to every cache key so that it is unique globally in the whole cache storage.
55
     * It is recommended that you set a unique cache key prefix for each application if the same cache
56
     * storage is being used by different applications.
57
     */
58 106
    private string $keyPrefix;
59
60 106
    /**
61 106
     * @var int|null default TTL for a cache entry. null meaning infinity, negative or zero results in cache key deletion.
62
     * This value is used by {@see set()} and {@see setMultiple()}, if the duration is not explicitly given.
63
     */
64
    private ?int $defaultTtl;
65
66
    /**
67
     * @param \Psr\SimpleCache\CacheInterface $handler
68
     * @param DateInterval|int|null $defaultTtl
69
     * @param string $keyPrefix
70 103
     */
71
    public function __construct(\Psr\SimpleCache\CacheInterface $handler, $defaultTtl = null, string $keyPrefix = '')
72 103
    {
73 1
        $this->handler = $handler;
74
        $this->metadata = new CacheItems();
75 102
        $this->keyNormalizer = new CacheKeyNormalizer();
76
        $this->keyPrefix = $keyPrefix;
77
        $this->defaultTtl = $this->normalizeTtl($defaultTtl);
78 81
    }
79
80 81
    public function getOrSet($key, callable $callable, $ttl = null, Dependency $dependency = null, float $beta = 1.0)
81 81
    {
82 81
        $key = $this->buildKey($key);
83
84
        if (!$this->metadata->expired($key, $beta)) {
85 20
            $value = $this->getValueOrDefaultIfDependencyChanged($this->handler->get($key));
86
87 20
            if ($value !== null) {
88 20
                return $value;
89
            }
90
        }
91
92
        $ttl = ($ttl = $this->normalizeTtl($ttl)) ?? $this->defaultTtl;
0 ignored issues
show
Unused Code introduced by
The assignment to $ttl is dead and can be removed.
Loading history...
93
        $value = $this->addDependencyToValue($callable, $dependency);
94
95
        if (!$this->handler->set($key, $value, $ttl)) {
96
            throw new SetCacheException($key, $value, $this);
97
        }
98
99
        $this->metadata->set($key, $ttl);
100
        return $value;
101
    }
102
103
    public function remove($key): bool
104
    {
105
        $key = $this->buildKey($key);
106
107 16
        if ($this->handler->delete($key)) {
108
            $this->metadata->remove($key);
109 16
            return true;
110 16
        }
111 16
112 16
        return false;
113
    }
114
115
    public function clear(): bool
116
    {
117
        if ($this->handler->clear()) {
118
            $this->metadata->clear();
119
            return true;
120
        }
121
122
        return false;
123
    }
124
125
    /**
126
     * Returns array of value and dependency or just value if dependency is null.
127
     *
128
     * @param callable $callable
129
     * @param Dependency|null $dependency
130
     *
131 92
     * @return mixed
132
     */
133 92
    private function addDependencyToValue(callable $callable, ?Dependency $dependency)
134 91
    {
135 91
        $value = $callable($this);
136 91
137
        if ($dependency === null) {
138 91
            return $value;
139
        }
140
141
        $dependency->evaluateDependency($this);
142
        return [$value, $dependency];
143
    }
144
145
    /**
146
     * Returns value if there is no dependency or it has not been changed and default value otherwise.
147
     *
148
     * @param mixed $value
149
     *
150
     * @return mixed
151
     */
152
    private function getValueOrDefaultIfDependencyChanged($value)
153
    {
154
        if (is_array($value) && isset($value[1]) && $value[1] instanceof Dependency) {
155
            /** @var Dependency $dependency */
156 19
            [$value, $dependency] = $value;
157
158 19
            if ($dependency->isChanged($this)) {
159 19
                return null;
160 19
            }
161
        }
162
163 1
        return $value;
164
    }
165 1
166 1
    /**
167
     * Builds a normalized cache key from a given key by appending key prefix.
168
     *
169
     * @param mixed $key The key to be normalized.
170
     *
171
     * @return string The generated cache key.
172
     */
173
    private function buildKey($key): string
174
    {
175
        return $this->keyPrefix . $this->keyNormalizer->normalize($key);
176
    }
177
178
    /**
179
     * Normalizes cache TTL handling `null` value and {@see DateInterval} objects.
180
     *
181
     * @param DateInterval|int|null $ttl raw TTL.
182
     *
183 1
     * @throws InvalidArgumentException For invalid TTL.
184
     *
185 1
     * @return int|null TTL value as UNIX timestamp or null meaning infinity.
186 1
     */
187 1
    private function normalizeTtl($ttl): ?int
188
    {
189 1
        if ($ttl === null) {
190
            return null;
191
        }
192 20
193
        if ($ttl instanceof DateInterval) {
194 20
            return (new DateTime('@0'))->add($ttl)->getTimestamp();
195 20
        }
196 20
197 20
        if (is_int($ttl)) {
0 ignored issues
show
introduced by
The condition is_int($ttl) is always true.
Loading history...
198 20
            return $ttl;
199 20
        }
200
201
        throw new InvalidArgumentException(sprintf(
202 20
            'Invalid TTL "%s" specified. It must be a \DateInterval instance, an integer, or null.',
203
            gettype($ttl),
204
        ));
205
    }
206
}
207