Completed
Pull Request — master (#16)
by Alexander
01:24
created

SimpleCache   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 251
Duplicated Lines 0 %

Test Coverage

Coverage 82.89%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 33
eloc 64
c 4
b 0
f 0
dl 0
loc 251
ccs 63
cts 76
cp 0.8289
rs 9.76

16 Methods

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