Completed
Push — master ( 6dc361...476812 )
by Alexander
02:07 queued 42s
created

Cache::clear()   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
rs 10
ccs 2
cts 2
cp 1
cc 1
nc 1
nop 0
crap 1
1
<?php
2
namespace Yiisoft\Cache;
3
4
use Psr\SimpleCache\InvalidArgumentException;
5
use Yiisoft\Cache\Dependencies\Dependency;
6
use Yiisoft\Cache\Exceptions\SetCacheException;
7
8
/**
9
 * Cache provides support for the data caching, including cache key composition and dependencies.
10
 * The actual data caching is performed via {@see handler}, which should be configured to be {@see \Psr\SimpleCache\CacheInterface}
11
 * instance.
12
 *
13
 * Application configuration example:
14
 *
15
 * ```php
16
 * return [
17
 *     'components' => [
18
 *         'cache' => [
19
 *             '__class' => Yiisoft\Cache\Cache::class,
20
 *             'handler' => [
21
 *                 '__class' => Yiisoft\Cache\FileCache::class,
22
 *                 'cachePath' => '@runtime/cache',
23
 *             ],
24
 *         ],
25
 *         // ...
26
 *     ],
27
 *     // ...
28
 * ];
29
 * ```
30
 *
31
 * A value can be stored in the cache by calling {@see set()} and be retrieved back
32
 * later (in the same or different request) by {@see get()}. In both operations,
33
 * a key identifying the value is required. An expiration time and/or a {@see Dependency|dependency}
34
 * can also be specified when calling {@see set()}. If the value expires or the dependency
35
 * changes at the time of calling {@see get()}, the cache will return no data.
36
 *
37
 * A typical usage pattern of cache is like the following:
38
 *
39
 * ```php
40
 * $key = 'demo';
41
 * $data = $cache->get($key);
42
 * if ($data === null) {
43
 *     // ...generate $data here...
44
 *     $cache->set($key, $data, $duration, $dependency);
45
 * }
46
 * ```
47
 *
48
 * Because Cache implements the {@see \ArrayAccess} interface, it can be used like an array. For example,
49
 *
50
 * ```php
51
 * $cache['foo'] = 'some data';
52
 * echo $cache['foo'];
53
 * ```
54
 *
55
 * For more details and usage information on Cache, see the [guide article on caching](guide:caching-overview)
56
 * and [PSR-16 specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md).
57
 */
58
final class Cache implements CacheInterface
59
{
60
    /**
61
     * @var \Psr\SimpleCache\CacheInterface actual cache handler.
62
     */
63
    private $handler;
64
65
    /**
66
     * @param \Psr\SimpleCache\CacheInterface cache handler.
67
     */
68 62
    public function __construct(\Psr\SimpleCache\CacheInterface $handler = null)
69
    {
70 62
        $this->handler = $handler;
71
    }
72
73
    /**
74
     * Builds a normalized cache key from a given key.
75
     *
76
     * If the given key is a string containing alphanumeric characters only and no more than 32 characters,
77
     * then the key will be returned back as it is. Otherwise, a normalized key is generated by serializing
78
     * the given key and applying MD5 hashing.
79
     *
80
     * @param mixed $key the key to be normalized
81
     * @return string the generated cache key
82
     */
83 62
    private function buildKey($key): string
84
    {
85 62
        if (\is_string($key)) {
86 62
            return ctype_alnum($key) && mb_strlen($key, '8bit') <= 32 ? $key : md5($key);
87
        }
88
        return md5(json_encode($key));
89
    }
90
91
92 54
    public function get($key, $default = null)
93
    {
94 54
        $key = $this->buildKey($key);
95 54
        $value = $this->handler->get($key);
96
97 54
        if ($value === null) {
98 36
            return $default;
99
        }
100
101 46
        if (\is_array($value) && isset($value[1]) && $value[1] instanceof Dependency) {
102 5
            if ($value[1]->isChanged($this)) {
103 5
                return $default;
104
            }
105 5
            return $value[0];
106
        }
107
108 41
        return $value;
109
    }
110
111
112 4
    public function has($key): bool
113
    {
114 4
        $key = $this->buildKey($key);
115 4
        return $this->handler->has($key);
116
    }
117
118
    /**
119
     * Retrieves multiple values from cache with the specified keys.
120
     * Some caches (such as memcache, apc) allow retrieving multiple cached values at the same time,
121
     * which may improve the performance. In case a cache does not support this feature natively,
122
     * this method will try to simulate it.
123
     * @param string[] $keys list of string keys identifying the cached values
124
     * @param mixed $default Default value to return for keys that do not exist.
125
     * @return iterable list of cached values corresponding to the specified keys. The array
126
     * is returned in terms of (key, value) pairs.
127
     * If a value is not cached or expired, the corresponding array value will be false.
128
     * @throws InvalidArgumentException
129
     */
130 9
    public function getMultiple($keys, $default = null): iterable
131
    {
132 9
        $keyMap = [];
133 9
        foreach ($keys as $key) {
134 9
            $keyMap[$key] = $this->buildKey($key);
135
        }
136 9
        $values = $this->handler->getMultiple(array_values($keyMap));
137 9
        $results = [];
138 9
        foreach ($keyMap as $key => $newKey) {
139 9
            $results[$key] = $default;
140 9
            if (isset($values[$newKey])) {
141 9
                $value = $values[$newKey];
142 9
                if (\is_array($value) && isset($value[1]) && $value[1] instanceof Dependency) {
143
                    if ($value[1]->isChanged($this)) {
144
                        continue;
145
                    }
146
147
                    $value = $value[0];
148
                }
149 9
                $results[$key] = $value;
150
            }
151
        }
152
153 9
        return $results;
154
    }
155
156
    /**
157
     * Stores a value identified by a key into cache.
158
     * If the cache already contains such a key, the existing value and
159
     * expiration time will be replaced with the new ones, respectively.
160
     *
161
     * @param mixed $key a key identifying the value to be cached. This can be a simple string or
162
     * a complex data structure consisting of factors representing the key.
163
     * @param mixed $value the value to be cached
164
     * @param null|int|\DateInterval $ttl the TTL of this value. If not set, default value is used.
165
     * @param Dependency $dependency dependency of the cached value. If the dependency changes,
166
     * the corresponding value in the cache will be invalidated when it is fetched via {@see get()}.
167
     * This parameter is ignored if {@see serializer} is false.
168
     * @return bool whether the value is successfully stored into cache
169
     * @throws InvalidArgumentException
170
     */
171 48
    public function set($key, $value, $ttl = null, Dependency $dependency = null): bool
172
    {
173 48
        if ($dependency !== null) {
174 5
            $dependency->evaluateDependency($this);
175 5
            $value = [$value, $dependency];
176
        }
177 48
        $key = $this->buildKey($key);
178 48
        return $this->handler->set($key, $value, $ttl);
179
    }
180
181
    /**
182
     * Stores multiple values in cache. Each value contains a value identified by a key.
183
     * If the cache already contains such a key, the existing value and
184
     * expiration time will be replaced with the new ones, respectively.
185
     *
186
     * @param array $values the values to be cached, as key-value pairs.
187
     * @param null|int|\DateInterval $ttl the TTL value of this value. If not set, default value is used.
188
     * @param Dependency $dependency dependency of the cached values. If the dependency changes,
189
     * the corresponding values in the cache will be invalidated when it is fetched via {@see get()}.
190
     * This parameter is ignored if {@see serializer} is false.
191
     * @return bool True on success and false on failure.
192
     * @throws InvalidArgumentException
193
     */
194 13
    public function setMultiple($values, $ttl = null, Dependency $dependency = null): bool
195
    {
196 13
        $data = $this->prepareDataForSetOrAddMultiple($values, $dependency);
197 13
        return $this->handler->setMultiple($data, $ttl);
198
    }
199
200
    public function deleteMultiple($keys): bool
201
    {
202
        $actualKeys = [];
203
        foreach ($keys as $key) {
204
            $actualKeys[] = $this->buildKey($key);
205
        }
206
        return $this->handler->deleteMultiple($actualKeys);
207
    }
208
209
    /**
210
     * Stores multiple values in cache. Each value contains a value identified by a key.
211
     * If the cache already contains such a key, the existing value and expiration time will be preserved.
212
     *
213
     * @param array $values the values to be cached, as key-value pairs.
214
     * @param null|int|\DateInterval $ttl the TTL value of this value. If not set, default value is used.
215
     * @param Dependency $dependency dependency of the cached values. If the dependency changes,
216
     * the corresponding values in the cache will be invalidated when it is fetched via {@see get()}.
217
     * This parameter is ignored if {@see serializer} is false.
218
     * @return bool
219
     * @throws InvalidArgumentException
220
     */
221 4
    public function addMultiple(array $values, $ttl = null, Dependency $dependency = null): bool
222
    {
223 4
        $data = $this->prepareDataForSetOrAddMultiple($values, $dependency);
224 4
        $existingValues = $this->handler->getMultiple(array_keys($data));
225 4
        foreach ($existingValues as $key => $value) {
226 4
            if ($value !== null) {
227 4
                unset($data[$key]);
228
            }
229
        }
230 4
        return $this->handler->setMultiple($data, $ttl);
231
    }
232
233 17
    private function prepareDataForSetOrAddMultiple(array $values, ?Dependency $dependency): array
234
    {
235 17
        if ($dependency !== null) {
236
            $dependency->evaluateDependency($this);
237
        }
238
239 17
        $data = [];
240 17
        foreach ($values as $key => $value) {
241 17
            if ($dependency !== null) {
242
                $value = [$value, $dependency];
243
            }
244
245 17
            $key = $this->buildKey($key);
246 17
            $data[$key] = $value;
247
        }
248
249 17
        return $data;
250
    }
251
252
    /**
253
     * Stores a value identified by a key into cache if the cache does not contain this key.
254
     * Nothing will be done if the cache already contains the key.
255
     * @param mixed $key a key identifying the value to be cached. This can be a simple string or
256
     * a complex data structure consisting of factors representing the key.
257
     * @param mixed $value the value to be cached
258
     * @param null|int|\DateInterval $ttl the TTL value of this value. If not set, default value is used.
259
     * @param Dependency $dependency dependency of the cached value. If the dependency changes,
260
     * the corresponding value in the cache will be invalidated when it is fetched via {@see get()}.
261
     * This parameter is ignored if {@see serializer} is false.
262
     * @return bool whether the value is successfully stored into cache
263
     * @throws InvalidArgumentException
264
     */
265 6
    public function add($key, $value, $ttl = null, Dependency $dependency = null): bool
266
    {
267 6
        if ($dependency !== null) {
268
            $dependency->evaluateDependency($this);
269
            $value = [$value, $dependency];
270
        }
271
272 6
        $key = $this->buildKey($key);
273
274 6
        if ($this->handler->has($key)) {
275 4
            return false;
276
        }
277
278 6
        return $this->handler->set($key, $value, $ttl);
279
    }
280
281
    /**
282
     * Deletes a value with the specified key from cache.
283
     * @param mixed $key a key identifying the value to be deleted from cache. This can be a simple string or
284
     * a complex data structure consisting of factors representing the key.
285
     * @return bool if no error happens during deletion
286
     * @throws InvalidArgumentException
287
     */
288 4
    public function delete($key): bool
289
    {
290 4
        $key = $this->buildKey($key);
291
292 4
        return $this->handler->delete($key);
293
    }
294
295
    /**
296
     * Deletes all values from cache.
297
     * Be careful of performing this operation if the cache is shared among multiple applications.
298
     * @return bool whether the flush operation was successful.
299
     */
300 44
    public function clear(): bool
301
    {
302 44
        return $this->handler->clear();
303
    }
304
305
    /**
306
     * Method combines both {@see set()} and {@see get()} methods to retrieve value identified by a $key,
307
     * or to store the result of $callable execution if there is no cache available for the $key.
308
     *
309
     * Usage example:
310
     *
311
     * ```php
312
     * public function getTopProducts($count = 10) {
313
     *     $cache = $this->cache;
314
     *     return $cache->getOrSet(['top-n-products', 'n' => $count], function ($cache) use ($count) {
315
     *         return Products::find()->mostPopular()->limit(10)->all();
316
     *     }, 1000);
317
     * }
318
     * ```
319
     *
320
     * @param mixed $key a key identifying the value to be cached. This can be a simple string or
321
     * a complex data structure consisting of factors representing the key.
322
     * @param callable|\Closure $callable the callable or closure that will be used to generate a value to be cached.
323
     * In case $callable returns `false`, the value will not be cached.
324
     * @param null|int|\DateInterval $ttl the TTL value of this value. If not set, default value is used.
325
     * @param Dependency $dependency dependency of the cached value. If the dependency changes,
326
     * the corresponding value in the cache will be invalidated when it is fetched via {@see get()}.
327
     * This parameter is ignored if {@see serializer} is `false`.
328
     * @return mixed result of $callable execution
329
     * @throws SetCacheException
330
     * @throws InvalidArgumentException
331
     */
332 8
    public function getOrSet($key, callable $callable, $ttl = null, Dependency $dependency = null)
333
    {
334 8
        if (($value = $this->get($key)) !== null) {
335 4
            return $value;
336
        }
337
338 8
        $value = $callable($this);
339 8
        if (!$this->set($key, $value, $ttl, $dependency)) {
340
            throw new SetCacheException($key, $value, $this);
341
        }
342
343 8
        return $value;
344
    }
345
}
346