Completed
Push — master ( d561b1...6dc361 )
by Alexander
01:26
created

Cache   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 298
Duplicated Lines 0 %

Test Coverage

Coverage 84.44%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 42
eloc 77
c 4
b 0
f 0
dl 0
loc 298
ccs 76
cts 90
cp 0.8444
rs 9.0399

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A deleteMultiple() 0 7 2
A addMultiple() 0 10 3
B getMultiple() 0 24 8
A buildKey() 0 6 4
A get() 0 17 6
A has() 0 4 1
A getOrSet() 0 12 3
A delete() 0 5 1
A prepareDataForSetOrAddMultiple() 0 17 4
A add() 0 14 3
A clear() 0 3 1
A setMultiple() 0 4 1
A setHandler() 0 7 2
A set() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like Cache often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Cache, and based on these observations, apply Extract Interface, too.

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->setHandler($handler);
71
    }
72
73
    /**
74
     * @param \Psr\SimpleCache\CacheInterface|array cache handler.
75
     */
76 62
    public function setHandler(\Psr\SimpleCache\CacheInterface $handler = null): self
77
    {
78 62
        if ($handler) {
79 62
            $this->handler = $handler;
80
        }
81
82 62
        return $this;
83
    }
84
85
    /**
86
     * Builds a normalized cache key from a given key.
87
     *
88
     * If the given key is a string containing alphanumeric characters only and no more than 32 characters,
89
     * then the key will be returned back as it is. Otherwise, a normalized key is generated by serializing
90
     * the given key and applying MD5 hashing.
91
     *
92
     * @param mixed $key the key to be normalized
93
     * @return string the generated cache key
94
     */
95 62
    private function buildKey($key): string
96
    {
97 62
        if (\is_string($key)) {
98 62
            return ctype_alnum($key) && mb_strlen($key, '8bit') <= 32 ? $key : md5($key);
99
        }
100
        return md5(json_encode($key));
101
    }
102
103
104 54
    public function get($key, $default = null)
105
    {
106 54
        $key = $this->buildKey($key);
107 54
        $value = $this->handler->get($key);
108
109 54
        if ($value === null) {
110 36
            return $default;
111
        }
112
113 46
        if (\is_array($value) && isset($value[1]) && $value[1] instanceof Dependency) {
114 5
            if ($value[1]->isChanged($this)) {
115 5
                return $default;
116
            }
117 5
            return $value[0];
118
        }
119
120 41
        return $value;
121
    }
122
123
124 4
    public function has($key): bool
125
    {
126 4
        $key = $this->buildKey($key);
127 4
        return $this->handler->has($key);
128
    }
129
130
    /**
131
     * Retrieves multiple values from cache with the specified keys.
132
     * Some caches (such as memcache, apc) allow retrieving multiple cached values at the same time,
133
     * which may improve the performance. In case a cache does not support this feature natively,
134
     * this method will try to simulate it.
135
     * @param string[] $keys list of string keys identifying the cached values
136
     * @param mixed $default Default value to return for keys that do not exist.
137
     * @return iterable list of cached values corresponding to the specified keys. The array
138
     * is returned in terms of (key, value) pairs.
139
     * If a value is not cached or expired, the corresponding array value will be false.
140
     * @throws InvalidArgumentException
141
     */
142 9
    public function getMultiple($keys, $default = null): iterable
143
    {
144 9
        $keyMap = [];
145 9
        foreach ($keys as $key) {
146 9
            $keyMap[$key] = $this->buildKey($key);
147
        }
148 9
        $values = $this->handler->getMultiple(array_values($keyMap));
149 9
        $results = [];
150 9
        foreach ($keyMap as $key => $newKey) {
151 9
            $results[$key] = $default;
152 9
            if (isset($values[$newKey])) {
153 9
                $value = $values[$newKey];
154 9
                if (\is_array($value) && isset($value[1]) && $value[1] instanceof Dependency) {
155
                    if ($value[1]->isChanged($this)) {
156
                        continue;
157
                    }
158
159
                    $value = $value[0];
160
                }
161 9
                $results[$key] = $value;
162
            }
163
        }
164
165 9
        return $results;
166
    }
167
168
    /**
169
     * Stores a value identified by a key into cache.
170
     * If the cache already contains such a key, the existing value and
171
     * expiration time will be replaced with the new ones, respectively.
172
     *
173
     * @param mixed $key a key identifying the value to be cached. This can be a simple string or
174
     * a complex data structure consisting of factors representing the key.
175
     * @param mixed $value the value to be cached
176
     * @param null|int|\DateInterval $ttl the TTL of this value. If not set, default value is used.
177
     * @param Dependency $dependency dependency of the cached value. If the dependency changes,
178
     * the corresponding value in the cache will be invalidated when it is fetched via {@see get()}.
179
     * This parameter is ignored if {@see serializer} is false.
180
     * @return bool whether the value is successfully stored into cache
181
     * @throws InvalidArgumentException
182
     */
183 48
    public function set($key, $value, $ttl = null, Dependency $dependency = null): bool
184
    {
185 48
        if ($dependency !== null) {
186 5
            $dependency->evaluateDependency($this);
187 5
            $value = [$value, $dependency];
188
        }
189 48
        $key = $this->buildKey($key);
190 48
        return $this->handler->set($key, $value, $ttl);
191
    }
192
193
    /**
194
     * Stores multiple values in cache. Each value contains a value identified by a key.
195
     * If the cache already contains such a key, the existing value and
196
     * expiration time will be replaced with the new ones, respectively.
197
     *
198
     * @param array $values the values to be cached, as key-value pairs.
199
     * @param null|int|\DateInterval $ttl the TTL value of this value. If not set, default value is used.
200
     * @param Dependency $dependency dependency of the cached values. If the dependency changes,
201
     * the corresponding values in the cache will be invalidated when it is fetched via {@see get()}.
202
     * This parameter is ignored if {@see serializer} is false.
203
     * @return bool True on success and false on failure.
204
     * @throws InvalidArgumentException
205
     */
206 13
    public function setMultiple($values, $ttl = null, Dependency $dependency = null): bool
207
    {
208 13
        $data = $this->prepareDataForSetOrAddMultiple($values, $dependency);
209 13
        return $this->handler->setMultiple($data, $ttl);
210
    }
211
212
    public function deleteMultiple($keys): bool
213
    {
214
        $actualKeys = [];
215
        foreach ($keys as $key) {
216
            $actualKeys[] = $this->buildKey($key);
217
        }
218
        return $this->handler->deleteMultiple($actualKeys);
219
    }
220
221
    /**
222
     * Stores multiple values in cache. Each value contains a value identified by a key.
223
     * If the cache already contains such a key, the existing value and expiration time will be preserved.
224
     *
225
     * @param array $values the values to be cached, as key-value pairs.
226
     * @param null|int|\DateInterval $ttl the TTL value of this value. If not set, default value is used.
227
     * @param Dependency $dependency dependency of the cached values. If the dependency changes,
228
     * the corresponding values in the cache will be invalidated when it is fetched via {@see get()}.
229
     * This parameter is ignored if {@see serializer} is false.
230
     * @return bool
231
     * @throws InvalidArgumentException
232
     */
233 4
    public function addMultiple(array $values, $ttl = null, Dependency $dependency = null): bool
234
    {
235 4
        $data = $this->prepareDataForSetOrAddMultiple($values, $dependency);
236 4
        $existingValues = $this->handler->getMultiple(array_keys($data));
237 4
        foreach ($existingValues as $key => $value) {
238 4
            if ($value !== null) {
239 4
                unset($data[$key]);
240
            }
241
        }
242 4
        return $this->handler->setMultiple($data, $ttl);
243
    }
244
245 17
    private function prepareDataForSetOrAddMultiple(array $values, ?Dependency $dependency): array
246
    {
247 17
        if ($dependency !== null) {
248
            $dependency->evaluateDependency($this);
249
        }
250
251 17
        $data = [];
252 17
        foreach ($values as $key => $value) {
253 17
            if ($dependency !== null) {
254
                $value = [$value, $dependency];
255
            }
256
257 17
            $key = $this->buildKey($key);
258 17
            $data[$key] = $value;
259
        }
260
261 17
        return $data;
262
    }
263
264
    /**
265
     * Stores a value identified by a key into cache if the cache does not contain this key.
266
     * Nothing will be done if the cache already contains the key.
267
     * @param mixed $key a key identifying the value to be cached. This can be a simple string or
268
     * a complex data structure consisting of factors representing the key.
269
     * @param mixed $value the value to be cached
270
     * @param null|int|\DateInterval $ttl the TTL value of this value. If not set, default value is used.
271
     * @param Dependency $dependency dependency of the cached value. If the dependency changes,
272
     * the corresponding value in the cache will be invalidated when it is fetched via {@see get()}.
273
     * This parameter is ignored if {@see serializer} is false.
274
     * @return bool whether the value is successfully stored into cache
275
     * @throws InvalidArgumentException
276
     */
277 6
    public function add($key, $value, $ttl = null, Dependency $dependency = null): bool
278
    {
279 6
        if ($dependency !== null) {
280
            $dependency->evaluateDependency($this);
281
            $value = [$value, $dependency];
282
        }
283
284 6
        $key = $this->buildKey($key);
285
286 6
        if ($this->handler->has($key)) {
287 4
            return false;
288
        }
289
290 6
        return $this->handler->set($key, $value, $ttl);
291
    }
292
293
    /**
294
     * Deletes a value with the specified key from cache.
295
     * @param mixed $key a key identifying the value to be deleted from cache. This can be a simple string or
296
     * a complex data structure consisting of factors representing the key.
297
     * @return bool if no error happens during deletion
298
     * @throws InvalidArgumentException
299
     */
300 4
    public function delete($key): bool
301
    {
302 4
        $key = $this->buildKey($key);
303
304 4
        return $this->handler->delete($key);
305
    }
306
307
    /**
308
     * Deletes all values from cache.
309
     * Be careful of performing this operation if the cache is shared among multiple applications.
310
     * @return bool whether the flush operation was successful.
311
     */
312 44
    public function clear(): bool
313
    {
314 44
        return $this->handler->clear();
315
    }
316
317
    /**
318
     * Method combines both {@see set()} and {@see get()} methods to retrieve value identified by a $key,
319
     * or to store the result of $callable execution if there is no cache available for the $key.
320
     *
321
     * Usage example:
322
     *
323
     * ```php
324
     * public function getTopProducts($count = 10) {
325
     *     $cache = $this->cache;
326
     *     return $cache->getOrSet(['top-n-products', 'n' => $count], function ($cache) use ($count) {
327
     *         return Products::find()->mostPopular()->limit(10)->all();
328
     *     }, 1000);
329
     * }
330
     * ```
331
     *
332
     * @param mixed $key a key identifying the value to be cached. This can be a simple string or
333
     * a complex data structure consisting of factors representing the key.
334
     * @param callable|\Closure $callable the callable or closure that will be used to generate a value to be cached.
335
     * In case $callable returns `false`, the value will not be cached.
336
     * @param null|int|\DateInterval $ttl the TTL value of this value. If not set, default value is used.
337
     * @param Dependency $dependency dependency of the cached value. If the dependency changes,
338
     * the corresponding value in the cache will be invalidated when it is fetched via {@see get()}.
339
     * This parameter is ignored if {@see serializer} is `false`.
340
     * @return mixed result of $callable execution
341
     * @throws SetCacheException
342
     * @throws InvalidArgumentException
343
     */
344 8
    public function getOrSet($key, callable $callable, $ttl = null, Dependency $dependency = null)
345
    {
346 8
        if (($value = $this->get($key)) !== null) {
347 4
            return $value;
348
        }
349
350 8
        $value = $callable($this);
351 8
        if (!$this->set($key, $value, $ttl, $dependency)) {
352
            throw new SetCacheException($key, $value, $this);
353
        }
354
355 8
        return $value;
356
    }
357
}
358