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

SimpleCache   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 273
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 71
c 0
b 0
f 0
dl 0
loc 273
rs 9.28
wmc 39

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getMultiple() 0 15 5
A getValues() 0 10 3
A deleteValues() 0 8 3
A normalizeKey() 0 5 3
A deleteMultiple() 0 8 2
A get() 0 9 2
A setMultiple() 0 13 4
A __construct() 0 3 1
A setSerializer() 0 3 1
A setKeyPrefix() 0 6 2
A setValues() 0 9 3
A set() 0 10 3
A delete() 0 4 1
A has() 0 4 1
A createDefaultSerializer() 0 3 1
A setDefaultTtl() 0 3 1
A normalizeTtl() 0 9 3
1
<?php
2
namespace Yiisoft\CacheOld;
3
4
use Psr\SimpleCache\CacheInterface as PsrCacheInterface;
5
use Yiisoft\CacheOld\Exception\InvalidArgumentException;
6
use Yiisoft\CacheOld\Serializer\PhpSerializer;
7
use Yiisoft\CacheOld\Serializer\SerializerInterface;
8
9
/**
10
 * SimpleCache is the base class for cache classes implementing pure PSR-16 {@see \Psr\SimpleCache\CacheInterface}.
11
 * This class handles cache key normalization, default TTL specification normalization, data serialization.
12
 *
13
 * Derived classes should implement the following methods which do the actual cache storage operations:
14
 *
15
 * - {@see SimpleCache::hasValue()}: check if value with a key exists in cache
16
 * - {@see SimpleCache::getValue()}: retrieve the value with a key (if any) from cache
17
 * - {@see SimpleCache::setValue()}: store the value with a key into cache
18
 * - {@see SimpleCache::deleteValue()}: delete the value with the specified key from cache
19
 * - {@see SimpleCache::clear()}: delete all values from cache
20
 *
21
 * Additionally, you may override the following methods in case backend supports getting any/or setting multiple keys
22
 * at once:
23
 *
24
 * - {@see SimpleCache::getValues()}: retrieve multiple values from cache
25
 * - {@see SimpleCache::setValues()}: store multiple values into cache
26
 * - {@see SimpleCache::deleteValues()}: delete multiple values from cache
27
 *
28
 * Check [PSR-16 specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md)
29
 * before implementing your own backend.
30
 */
31
abstract class SimpleCache implements PsrCacheInterface
32
{
33
    /**
34
     * @var int|null default TTL for a cache entry. null meaning infinity, negative or zero results in cache key deletion.
35
     * This value is used by {@see set()} and {@see setMultiple()}, if the duration is not explicitly given.
36
     */
37
    private $defaultTtl;
38
39
    /**
40
     * @var string a string prefixed to every cache key so that it is unique globally in the whole cache storage.
41
     * It is recommended that you set a unique cache key prefix for each application if the same cache
42
     * storage is being used by different applications.
43
     */
44
    private $keyPrefix = '';
45
46
    /**
47
     * @var SerializerInterface the serializer to be used for serializing and unserializing of the cached data.
48
     */
49
    private $serializer;
50
51
    /**
52
     * @var SerializerInterface the serializer to be used for serializing and unserializing of the cached data.
53
     * Serializer should be an instance of {@see SerializerInterface} or its DI compatible configuration.
54
     * @see setSerializer
55
     */
56
    public function __construct(?SerializerInterface $serializer = null)
57
    {
58
        $this->setSerializer($serializer ?? $this->createDefaultSerializer());
59
    }
60
61
    /**
62
     * Creates a default serializer, when nothing is given
63
     * @return SerializerInterface
64
     */
65
    protected function createDefaultSerializer(): SerializerInterface
66
    {
67
        return new PhpSerializer();
68
    }
69
70
    /**
71
     * @var SerializerInterface the serializer to be used for serializing and unserializing of the cached data.
72
     *
73
     */
74
    public function setSerializer(SerializerInterface $serializer): void
75
    {
76
        $this->serializer = $serializer;
77
    }
78
79
    public function get($key, $default = null)
80
    {
81
        $key = $this->normalizeKey($key);
82
        $value = $this->getValue($key, $default);
83
        if ($value === $default) {
84
            return $default;
85
        }
86
87
        return $this->serializer->unserialize($value);
88
    }
89
90
    public function getMultiple($keys, $default = null): iterable
91
    {
92
        $keyMap = [];
93
        foreach ($keys as $originalKey) {
94
            $keyMap[$originalKey] = $this->normalizeKey($originalKey);
95
        }
96
        $values = $this->getValues(array_values($keyMap), $default);
97
        $results = [];
98
        foreach ($keyMap as $originalKey => $normalizedKey) {
99
            $results[$originalKey] = $default;
100
            if (isset($values[$normalizedKey]) && $values[$normalizedKey] !== $default) {
101
                $results[$originalKey] = $this->serializer->unserialize($values[$normalizedKey]);
102
            }
103
        }
104
        return $results;
105
    }
106
107
    public function has($key): bool
108
    {
109
        $key = $this->normalizeKey($key);
110
        return $this->hasValue($key);
111
    }
112
113
    abstract protected function hasValue(string $key): bool;
114
115
    public function set($key, $value, $ttl = null): bool
116
    {
117
        if ($ttl !== null && $ttl <= 0) {
118
            return $this->delete($key);
119
        }
120
121
        $value = $this->serializer->serialize($value);
122
        $key = $this->normalizeKey($key);
123
        $ttl = $this->normalizeTtl($ttl);
124
        return $this->setValue($key, $value, $ttl);
125
    }
126
127
    public function setMultiple($values, $ttl = null): bool
128
    {
129
        $data = [];
130
        foreach ($values as $key => $value) {
131
            if ($ttl !== null && $ttl <= 0) {
132
                return $this->delete($key);
133
            }
134
135
            $value = $this->serializer->serialize($value);
136
            $key = $this->normalizeKey($key);
137
            $data[$key] = $value;
138
        }
139
        return $this->setValues($data, $this->normalizeTtl($ttl));
140
    }
141
142
    public function delete($key): bool
143
    {
144
        $key = $this->normalizeKey($key);
145
        return $this->deleteValue($key);
146
    }
147
148
    public function deleteMultiple($keys): bool
149
    {
150
        $normalizedKeys = [];
151
        foreach ($keys as $key) {
152
            $normalizedKeys[] = $this->normalizeKey($key);
153
        }
154
155
        return $this->deleteValues($normalizedKeys);
156
    }
157
158
    /**
159
     * Builds a normalized cache key from a given key.
160
     *
161
     * The given key will be type-casted to string.
162
     * If the result string does not contain alphanumeric characters only or has more than 32 characters,
163
     * then the hash of the key will be used.
164
     * The result key will be returned back prefixed with {@see $keyPrefix}.
165
     *
166
     * @param mixed $key the key to be normalized
167
     * @return string the generated cache key
168
     */
169
    private function normalizeKey($key): string
170
    {
171
        $key = (string)$key;
172
        $key = ctype_alnum($key) && \strlen($key) <= 32 ? $key : md5($key);
173
        return $this->keyPrefix . $key;
174
    }
175
176
    /**
177
     * Normalizes cache TTL handling `null` value and {@see \DateInterval} objects.
178
     * @param int|\DateInterval|null $ttl raw TTL.
179
     * @return int|null TTL value as UNIX timestamp or null meaning infinity
180
     * @throws \Exception
181
     */
182
    protected function normalizeTtl($ttl): ?int
183
    {
184
        if ($ttl === null) {
185
            return $this->defaultTtl;
186
        }
187
        if ($ttl instanceof \DateInterval) {
188
            return (new \DateTime('@0'))->add($ttl)->getTimestamp();
189
        }
190
        return $ttl;
191
    }
192
193
    /**
194
     * Retrieves a value from cache with a specified key.
195
     * This method should be implemented by child classes to retrieve the data
196
     * from specific cache storage.
197
     * @param string $key a unique key identifying the cached value
198
     * @param mixed $default default value to return if value is not in the cache or expired
199
     * @return mixed the value stored in cache. $default is returned if the value is not in the cache or expired. Most often
200
     * value is a string. If you have disabled {@see $serializer}}, it could be something else.
201
     */
202
    abstract protected function getValue(string $key, $default = null);
203
204
    /**
205
     * Stores a value identified by a key in cache.
206
     * This method should be implemented by child classes to store the data
207
     * in specific cache storage.
208
     * @param string $key the key identifying the value to be cached
209
     * @param mixed $value the value to be cached. Most often it's a string. If you have disabled {@see $serializer},
210
     * it could be something else.
211
     * @param int|null $ttl the number of seconds in which the cached value will expire. Null means infinity.
212
     * Negative value will result in deleting a value.
213
     * @return bool true if the value is successfully stored into cache, false otherwise
214
     */
215
    abstract protected function setValue(string $key, $value, ?int $ttl): bool;
216
217
    /**
218
     * Deletes a value with the specified key from cache
219
     * This method should be implemented by child classes to delete the data from actual cache storage.
220
     * @param string $key the key of the value to be deleted
221
     * @return bool if no error happens during deletion
222
     */
223
    abstract protected function deleteValue(string $key): bool;
224
225
    /**
226
     * Deletes multiple values from cache
227
     * This method should be overridden by child classes in case storage supports efficient deletion of multiple keys
228
     * @param iterable $keys
229
     * @return bool if no error happens during deletion
230
     */
231
    protected function deleteValues(iterable $keys): bool
232
    {
233
        foreach ($keys as $key) {
234
            if (!$this->deleteValue($key)) {
235
                return false;
236
            }
237
        }
238
        return true;
239
    }
240
241
    /**
242
     * Retrieves multiple values from cache with the specified keys.
243
     * The default implementation calls {@see getValue()} multiple times to retrieve
244
     * the cached values one by one. If the underlying cache storage supports multiget,
245
     * this method should be overridden to exploit that feature.
246
     *
247
     * @param iterable $keys a list of keys identifying the cached values
248
     * @param mixed $default default value to return if value is not in the cache or expired
249
     * @return iterable a list of cached values indexed by the keys
250
     */
251
    protected function getValues(iterable $keys, $default = null): iterable
252
    {
253
        $results = [];
254
        foreach ($keys as $key) {
255
            $value = $this->getValue($key, $default);
256
            if ($value !== false) {
257
                $results[$key] = $value;
258
            }
259
        }
260
        return $results;
261
    }
262
263
    /**
264
     * Stores multiple key-value pairs in cache.
265
     * The default implementation calls {@see setValue()} multiple times store values one by one. If the underlying cache
266
     * storage supports multi-set, this method should be overridden to exploit that feature.
267
     *
268
     * @param iterable $values a list where key corresponds to cache key while value is the value stored
269
     * @param int|null $ttl the number of seconds in which the cached values will expire. Null means infinity.
270
     * Negative value will result in deleting a value.
271
     * @return bool `true` on success and `false` on failure.
272
     */
273
    protected function setValues(iterable $values, ?int $ttl): bool
274
    {
275
        $result = true;
276
        foreach ($values as $key => $value) {
277
            if (!$this->setValue($key, $value, $ttl)) {
278
                $result = false;
279
            }
280
        }
281
        return $result;
282
    }
283
284
    /**
285
     * @param int|null $defaultTtl default TTL for a cache entry. null meaning infinity, negative or zero results in cache key deletion.
286
     * This value is used by {@see set()} and {@see setMultiple()}, if the duration is not explicitly given.
287
     */
288
    public function setDefaultTtl(?int $defaultTtl): void
289
    {
290
        $this->defaultTtl = $defaultTtl;
291
    }
292
293
    /**
294
     * @param string $keyPrefix a string prefixed to every cache key so that it is unique globally in the whole cache storage.
295
     * It is recommended that you set a unique cache key prefix for each application if the same cache
296
     * storage is being used by different applications.
297
     */
298
    public function setKeyPrefix(string $keyPrefix): void
299
    {
300
        if (!ctype_alnum($keyPrefix)) {
301
            throw new InvalidArgumentException('Cache key prefix should be alphanumeric');
302
        }
303
        $this->keyPrefix = $keyPrefix;
304
    }
305
}
306