1 | <?php |
||
2 | |||
3 | /* |
||
4 | * This file is part of the Cache package. |
||
5 | * |
||
6 | * Copyright (c) Daniel González |
||
7 | * |
||
8 | * For the full copyright and license information, please view the LICENSE |
||
9 | * file that was distributed with this source code. |
||
10 | * |
||
11 | * @author Daniel González <[email protected]> |
||
12 | * @author Arnold Daniels <[email protected]> |
||
13 | */ |
||
14 | |||
15 | declare(strict_types=1); |
||
16 | |||
17 | namespace Desarrolla2\Cache; |
||
18 | |||
19 | use Desarrolla2\Cache\Option\PrefixTrait as PrefixOption; |
||
20 | use Desarrolla2\Cache\Option\TtlTrait as TtlOption; |
||
21 | use Desarrolla2\Cache\Packer\PackingTrait as Packing; |
||
22 | use Desarrolla2\Cache\Exception\InvalidArgumentException; |
||
23 | use DateTimeImmutable; |
||
24 | use DateInterval; |
||
25 | use Traversable; |
||
26 | |||
27 | /** |
||
28 | * AbstractAdapter |
||
29 | */ |
||
30 | abstract class AbstractCache implements CacheInterface |
||
31 | { |
||
32 | use PrefixOption; |
||
33 | use TtlOption; |
||
34 | use Packing; |
||
35 | |||
36 | /** |
||
37 | * Make a clone of this object. |
||
38 | * |
||
39 | * @return static |
||
40 | */ |
||
41 | 820 | protected function cloneSelf(): self |
|
42 | { |
||
43 | 820 | return clone $this; |
|
44 | } |
||
45 | |||
46 | /** |
||
47 | * {@inheritdoc} |
||
48 | */ |
||
49 | 819 | public function withOption(string $key, $value): self |
|
50 | { |
||
51 | 819 | return $this->withOptions([$key => $value]); |
|
52 | } |
||
53 | |||
54 | /** |
||
55 | * {@inheritdoc} |
||
56 | */ |
||
57 | 826 | public function withOptions(array $options): self |
|
58 | { |
||
59 | 826 | $cache = $this->cloneSelf(); |
|
60 | |||
61 | 826 | foreach ($options as $key => $value) { |
|
62 | 826 | $method = "set" . str_replace('-', '', $key) . "Option"; |
|
63 | |||
64 | 826 | if (empty($key) || !method_exists($cache, $method)) { |
|
65 | 11 | throw new InvalidArgumentException("unknown option '$key'"); |
|
66 | } |
||
67 | |||
68 | 819 | $cache->$method($value); |
|
69 | } |
||
70 | |||
71 | 812 | return $cache; |
|
72 | } |
||
73 | |||
74 | /** |
||
75 | * {@inheritdoc} |
||
76 | */ |
||
77 | 34 | public function getOption($key) |
|
78 | { |
||
79 | 34 | $method = "get" . str_replace('-', '', $key) . "Option"; |
|
80 | |||
81 | 34 | if (empty($key) || !method_exists($this, $method)) { |
|
82 | throw new InvalidArgumentException("unknown option '$key'"); |
||
83 | } |
||
84 | |||
85 | 34 | return $this->$method(); |
|
86 | } |
||
87 | |||
88 | |||
89 | /** |
||
90 | * Validate the key |
||
91 | * |
||
92 | * @param string $key |
||
93 | * @return void |
||
94 | * @throws InvalidArgumentException |
||
95 | */ |
||
96 | 2047 | protected function assertKey($key): void |
|
97 | { |
||
98 | 2047 | if (!is_string($key)) { |
|
0 ignored issues
–
show
introduced
by
![]() |
|||
99 | 528 | $type = (is_object($key) ? get_class($key) . ' ' : '') . gettype($key); |
|
100 | 528 | throw new InvalidArgumentException("Expected key to be a string, not $type"); |
|
101 | } |
||
102 | |||
103 | 1739 | if ($key === '' || preg_match('~[{}()/\\\\@:]~', $key)) { |
|
104 | 847 | throw new InvalidArgumentException("Invalid key '$key'"); |
|
105 | } |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * Assert that the keys are an array or traversable |
||
110 | * |
||
111 | * @param iterable $subject |
||
112 | * @param string $msg |
||
113 | * @return void |
||
114 | * @throws InvalidArgumentException if subject are not iterable |
||
115 | */ |
||
116 | 926 | protected function assertIterable($subject, $msg): void |
|
117 | { |
||
118 | 926 | $iterable = function_exists('is_iterable') |
|
119 | 926 | ? is_iterable($subject) |
|
120 | 926 | : is_array($subject) || $subject instanceof Traversable; |
|
121 | |||
122 | 926 | if (!$iterable) { |
|
123 | 33 | throw new InvalidArgumentException($msg); |
|
124 | } |
||
125 | } |
||
126 | |||
127 | /** |
||
128 | * Turn the key into a cache identifier |
||
129 | * |
||
130 | * @param string $key |
||
131 | * @return string |
||
132 | * @throws InvalidArgumentException |
||
133 | */ |
||
134 | 1878 | protected function keyToId($key): string |
|
135 | { |
||
136 | 1878 | $this->assertKey($key); |
|
137 | |||
138 | 1140 | return sprintf('%s%s', $this->prefix, $key); |
|
139 | } |
||
140 | |||
141 | /** |
||
142 | * Create a map with keys and ids |
||
143 | * |
||
144 | * @param iterable $keys |
||
145 | * @return array |
||
146 | * @throws InvalidArgumentException |
||
147 | */ |
||
148 | 153 | protected function mapKeysToIds($keys): array |
|
149 | { |
||
150 | 153 | $this->assertIterable($keys, 'keys not iterable'); |
|
151 | |||
152 | 147 | $map = []; |
|
153 | |||
154 | 147 | foreach ($keys as $key) { |
|
155 | 147 | $id = $this->keyToId($key); |
|
156 | 147 | $map[$id] = $key; |
|
157 | } |
||
158 | |||
159 | 39 | return $map; |
|
160 | } |
||
161 | |||
162 | |||
163 | /** |
||
164 | * Pack all values and turn keys into ids |
||
165 | * |
||
166 | * @param iterable $values |
||
167 | * @return array |
||
168 | */ |
||
169 | protected function packValues(iterable $values): array |
||
170 | { |
||
171 | $packed = []; |
||
172 | |||
173 | foreach ($values as $key => $value) { |
||
174 | $id = $this->keyToId(is_int($key) ? (string)$key : $key); |
||
175 | $packed[$id] = $this->pack($value); |
||
176 | } |
||
177 | |||
178 | return $packed; |
||
179 | } |
||
180 | |||
181 | |||
182 | |||
183 | /** |
||
184 | * {@inheritdoc} |
||
185 | */ |
||
186 | 191 | public function getMultiple($keys, $default = null) |
|
187 | { |
||
188 | 191 | $this->assertIterable($keys, 'keys not iterable'); |
|
189 | |||
190 | 185 | $result = []; |
|
191 | |||
192 | 185 | foreach ($keys as $key) { |
|
193 | 185 | $result[$key] = $this->get($key, $default); |
|
194 | } |
||
195 | |||
196 | 77 | return $result; |
|
197 | } |
||
198 | |||
199 | /** |
||
200 | * {@inheritdoc} |
||
201 | */ |
||
202 | 294 | public function setMultiple($values, $ttl = null) |
|
203 | { |
||
204 | 294 | $this->assertIterable($values, 'values not iterable'); |
|
205 | |||
206 | 287 | $success = true; |
|
207 | |||
208 | 287 | foreach ($values as $key => $value) { |
|
209 | 287 | $success = $this->set(is_int($key) ? (string)$key : $key, $value, $ttl) && $success; |
|
210 | } |
||
211 | |||
212 | 98 | return $success; |
|
213 | } |
||
214 | |||
215 | /** |
||
216 | * {@inheritdoc} |
||
217 | */ |
||
218 | 168 | public function deleteMultiple($keys) |
|
219 | { |
||
220 | 168 | $this->assertIterable($keys, 'keys not iterable'); |
|
221 | |||
222 | 160 | $success = true; |
|
223 | |||
224 | 160 | foreach ($keys as $key) { |
|
225 | 160 | $success = $this->delete($key) && $success; |
|
226 | } |
||
227 | |||
228 | 16 | return $success; |
|
229 | } |
||
230 | |||
231 | |||
232 | /** |
||
233 | * Get the current time. |
||
234 | * |
||
235 | * @return int |
||
236 | */ |
||
237 | 89 | protected function currentTimestamp(): int |
|
238 | { |
||
239 | 89 | return time(); |
|
240 | } |
||
241 | |||
242 | /** |
||
243 | * Convert TTL to seconds from now |
||
244 | * |
||
245 | * @param null|int|DateInterval $ttl |
||
246 | * @return int|null |
||
247 | * @throws InvalidArgumentException |
||
248 | */ |
||
249 | 226 | protected function ttlToSeconds($ttl): ?int |
|
250 | { |
||
251 | 226 | if (!isset($ttl)) { |
|
252 | 157 | return $this->ttl; |
|
253 | } |
||
254 | |||
255 | 72 | if ($ttl instanceof DateInterval) { |
|
256 | 6 | $reference = new DateTimeImmutable(); |
|
257 | 6 | $endTime = $reference->add($ttl); |
|
258 | |||
259 | 6 | $ttl = $endTime->getTimestamp() - $reference->getTimestamp(); |
|
260 | } |
||
261 | |||
262 | 72 | if (!is_int($ttl)) { |
|
0 ignored issues
–
show
|
|||
263 | 60 | $type = (is_object($ttl) ? get_class($ttl) . ' ' : '') . gettype($ttl); |
|
264 | 60 | throw new InvalidArgumentException("ttl should be of type int or DateInterval, not $type"); |
|
265 | } |
||
266 | |||
267 | 12 | return isset($this->ttl) ? min($ttl, $this->ttl) : $ttl; |
|
268 | } |
||
269 | |||
270 | /** |
||
271 | * Convert TTL to epoch timestamp |
||
272 | * |
||
273 | * @param null|int|DateInterval $ttl |
||
274 | * @return int|null |
||
275 | * @throws InvalidArgumentException |
||
276 | */ |
||
277 | 587 | protected function ttlToTimestamp($ttl): ?int |
|
278 | { |
||
279 | 587 | if (!isset($ttl)) { |
|
280 | 402 | return isset($this->ttl) ? time() + $this->ttl : null; |
|
281 | } |
||
282 | |||
283 | 192 | if (is_int($ttl)) { |
|
284 | 32 | return time() + (isset($this->ttl) ? min($ttl, $this->ttl) : $ttl); |
|
285 | } |
||
286 | |||
287 | 176 | if ($ttl instanceof DateInterval) { |
|
0 ignored issues
–
show
|
|||
288 | 16 | $timestamp = (new DateTimeImmutable())->add($ttl)->getTimestamp(); |
|
289 | |||
290 | 16 | return isset($this->ttl) ? min($timestamp, time() + $this->ttl) : $timestamp; |
|
291 | } |
||
292 | |||
293 | 160 | $type = (is_object($ttl) ? get_class($ttl) . ' ' : '') . gettype($ttl); |
|
294 | 160 | throw new InvalidArgumentException("ttl should be of type int or DateInterval, not $type"); |
|
295 | } |
||
296 | } |
||
297 |