Passed
Push — master ( eac3f8...2377e1 )
by Alexander
01:46
created

SimpleCache::getMultiple()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5

Importance

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