Passed
Pull Request — master (#30)
by Alexander
02:06
created

Cache::getMultiple()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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