summaryrefslogtreecommitdiff
path: root/vendor/symfony/cache/Traits/AbstractAdapterTrait.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/symfony/cache/Traits/AbstractAdapterTrait.php')
-rw-r--r--vendor/symfony/cache/Traits/AbstractAdapterTrait.php377
1 files changed, 377 insertions, 0 deletions
diff --git a/vendor/symfony/cache/Traits/AbstractAdapterTrait.php b/vendor/symfony/cache/Traits/AbstractAdapterTrait.php
new file mode 100644
index 0000000..222bc54
--- /dev/null
+++ b/vendor/symfony/cache/Traits/AbstractAdapterTrait.php
@@ -0,0 +1,377 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Cache\Traits;
13
14use Psr\Cache\CacheItemInterface;
15use Psr\Log\LoggerAwareTrait;
16use Symfony\Component\Cache\CacheItem;
17use Symfony\Component\Cache\Exception\InvalidArgumentException;
18
19/**
20 * @author Nicolas Grekas <p@tchwork.com>
21 *
22 * @internal
23 */
24trait AbstractAdapterTrait
25{
26 use LoggerAwareTrait;
27
28 /**
29 * needs to be set by class, signature is function(string <key>, mixed <value>, bool <isHit>).
30 */
31 private static \Closure $createCacheItem;
32
33 /**
34 * needs to be set by class, signature is function(array <deferred>, string <namespace>, array <&expiredIds>).
35 */
36 private static \Closure $mergeByLifetime;
37
38 private string $namespace = '';
39 private int $defaultLifetime;
40 private string $namespaceVersion = '';
41 private bool $versioningIsEnabled = false;
42 private array $deferred = [];
43 private array $ids = [];
44
45 /**
46 * The maximum length to enforce for identifiers or null when no limit applies.
47 */
48 protected ?int $maxIdLength = null;
49
50 /**
51 * Fetches several cache items.
52 *
53 * @param array $ids The cache identifiers to fetch
54 */
55 abstract protected function doFetch(array $ids): iterable;
56
57 /**
58 * Confirms if the cache contains specified cache item.
59 *
60 * @param string $id The identifier for which to check existence
61 */
62 abstract protected function doHave(string $id): bool;
63
64 /**
65 * Deletes all items in the pool.
66 *
67 * @param string $namespace The prefix used for all identifiers managed by this pool
68 */
69 abstract protected function doClear(string $namespace): bool;
70
71 /**
72 * Removes multiple items from the pool.
73 *
74 * @param array $ids An array of identifiers that should be removed from the pool
75 */
76 abstract protected function doDelete(array $ids): bool;
77
78 /**
79 * Persists several cache items immediately.
80 *
81 * @param array $values The values to cache, indexed by their cache identifier
82 * @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning
83 *
84 * @return array|bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not
85 */
86 abstract protected function doSave(array $values, int $lifetime): array|bool;
87
88 public function hasItem(mixed $key): bool
89 {
90 $id = $this->getId($key);
91
92 if (isset($this->deferred[$key])) {
93 $this->commit();
94 }
95
96 try {
97 return $this->doHave($id);
98 } catch (\Exception $e) {
99 CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached: '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
100
101 return false;
102 }
103 }
104
105 public function clear(string $prefix = ''): bool
106 {
107 $this->deferred = [];
108 if ($cleared = $this->versioningIsEnabled) {
109 if ('' === $namespaceVersionToClear = $this->namespaceVersion) {
110 foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) {
111 $namespaceVersionToClear = $v;
112 }
113 }
114 $namespaceToClear = $this->namespace.$namespaceVersionToClear;
115 $namespaceVersion = self::formatNamespaceVersion(mt_rand());
116 try {
117 $e = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0);
118 } catch (\Exception $e) {
119 }
120 if (true !== $e && [] !== $e) {
121 $cleared = false;
122 $message = 'Failed to save the new namespace'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
123 CacheItem::log($this->logger, $message, ['exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]);
124 } else {
125 $this->namespaceVersion = $namespaceVersion;
126 $this->ids = [];
127 }
128 } else {
129 $namespaceToClear = $this->namespace.$prefix;
130 }
131
132 try {
133 return $this->doClear($namespaceToClear) || $cleared;
134 } catch (\Exception $e) {
135 CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]);
136
137 return false;
138 }
139 }
140
141 public function deleteItem(mixed $key): bool
142 {
143 return $this->deleteItems([$key]);
144 }
145
146 public function deleteItems(array $keys): bool
147 {
148 $ids = [];
149
150 foreach ($keys as $key) {
151 $ids[$key] = $this->getId($key);
152 unset($this->deferred[$key]);
153 }
154
155 try {
156 if ($this->doDelete($ids)) {
157 return true;
158 }
159 } catch (\Exception) {
160 }
161
162 $ok = true;
163
164 // When bulk-delete failed, retry each item individually
165 foreach ($ids as $key => $id) {
166 try {
167 $e = null;
168 if ($this->doDelete([$id])) {
169 continue;
170 }
171 } catch (\Exception $e) {
172 }
173 $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
174 CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
175 $ok = false;
176 }
177
178 return $ok;
179 }
180
181 public function getItem(mixed $key): CacheItem
182 {
183 $id = $this->getId($key);
184
185 if (isset($this->deferred[$key])) {
186 $this->commit();
187 }
188
189 $isHit = false;
190 $value = null;
191
192 try {
193 foreach ($this->doFetch([$id]) as $value) {
194 $isHit = true;
195 }
196
197 return (self::$createCacheItem)($key, $value, $isHit);
198 } catch (\Exception $e) {
199 CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
200 }
201
202 return (self::$createCacheItem)($key, null, false);
203 }
204
205 public function getItems(array $keys = []): iterable
206 {
207 $ids = [];
208 $commit = false;
209
210 foreach ($keys as $key) {
211 $ids[] = $this->getId($key);
212 $commit = $commit || isset($this->deferred[$key]);
213 }
214
215 if ($commit) {
216 $this->commit();
217 }
218
219 try {
220 $items = $this->doFetch($ids);
221 } catch (\Exception $e) {
222 CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
223 $items = [];
224 }
225 $ids = array_combine($ids, $keys);
226
227 return $this->generateItems($items, $ids);
228 }
229
230 public function save(CacheItemInterface $item): bool
231 {
232 if (!$item instanceof CacheItem) {
233 return false;
234 }
235 $this->deferred[$item->getKey()] = $item;
236
237 return $this->commit();
238 }
239
240 public function saveDeferred(CacheItemInterface $item): bool
241 {
242 if (!$item instanceof CacheItem) {
243 return false;
244 }
245 $this->deferred[$item->getKey()] = $item;
246
247 return true;
248 }
249
250 /**
251 * Enables/disables versioning of items.
252 *
253 * When versioning is enabled, clearing the cache is atomic and doesn't require listing existing keys to proceed,
254 * but old keys may need garbage collection and extra round-trips to the back-end are required.
255 *
256 * Calling this method also clears the memoized namespace version and thus forces a resynchronization of it.
257 *
258 * @return bool the previous state of versioning
259 */
260 public function enableVersioning(bool $enable = true): bool
261 {
262 $wasEnabled = $this->versioningIsEnabled;
263 $this->versioningIsEnabled = $enable;
264 $this->namespaceVersion = '';
265 $this->ids = [];
266
267 return $wasEnabled;
268 }
269
270 public function reset(): void
271 {
272 if ($this->deferred) {
273 $this->commit();
274 }
275 $this->namespaceVersion = '';
276 $this->ids = [];
277 }
278
279 public function __sleep(): array
280 {
281 throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
282 }
283
284 public function __wakeup(): void
285 {
286 throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
287 }
288
289 public function __destruct()
290 {
291 if ($this->deferred) {
292 $this->commit();
293 }
294 }
295
296 private function generateItems(iterable $items, array &$keys): \Generator
297 {
298 $f = self::$createCacheItem;
299
300 try {
301 foreach ($items as $id => $value) {
302 if (!isset($keys[$id])) {
303 throw new InvalidArgumentException(sprintf('Could not match value id "%s" to keys "%s".', $id, implode('", "', $keys)));
304 }
305 $key = $keys[$id];
306 unset($keys[$id]);
307 yield $key => $f($key, $value, true);
308 }
309 } catch (\Exception $e) {
310 CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
311 }
312
313 foreach ($keys as $key) {
314 yield $key => $f($key, null, false);
315 }
316 }
317
318 /**
319 * @internal
320 */
321 protected function getId(mixed $key): string
322 {
323 if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
324 $this->ids = [];
325 $this->namespaceVersion = '1'.static::NS_SEPARATOR;
326 try {
327 foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) {
328 $this->namespaceVersion = $v;
329 }
330 $e = true;
331 if ('1'.static::NS_SEPARATOR === $this->namespaceVersion) {
332 $this->namespaceVersion = self::formatNamespaceVersion(time());
333 $e = $this->doSave([static::NS_SEPARATOR.$this->namespace => $this->namespaceVersion], 0);
334 }
335 } catch (\Exception $e) {
336 }
337 if (true !== $e && [] !== $e) {
338 $message = 'Failed to save the new namespace'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
339 CacheItem::log($this->logger, $message, ['exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]);
340 }
341 }
342
343 if (\is_string($key) && isset($this->ids[$key])) {
344 return $this->namespace.$this->namespaceVersion.$this->ids[$key];
345 }
346 \assert('' !== CacheItem::validateKey($key));
347 $this->ids[$key] = $key;
348
349 if (\count($this->ids) > 1000) {
350 $this->ids = \array_slice($this->ids, 500, null, true); // stop memory leak if there are many keys
351 }
352
353 if (null === $this->maxIdLength) {
354 return $this->namespace.$this->namespaceVersion.$key;
355 }
356 if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) {
357 // Use xxh128 to favor speed over security, which is not an issue here
358 $this->ids[$key] = $id = substr_replace(base64_encode(hash('xxh128', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2));
359 $id = $this->namespace.$this->namespaceVersion.$id;
360 }
361
362 return $id;
363 }
364
365 /**
366 * @internal
367 */
368 public static function handleUnserializeCallback(string $class): never
369 {
370 throw new \DomainException('Class not found: '.$class);
371 }
372
373 private static function formatNamespaceVersion(int $value): string
374 {
375 return strtr(substr_replace(base64_encode(pack('V', $value)), static::NS_SEPARATOR, 5), '/', '_');
376 }
377}