diff options
Diffstat (limited to 'vendor/symfony/cache/Adapter/PhpArrayAdapter.php')
-rw-r--r-- | vendor/symfony/cache/Adapter/PhpArrayAdapter.php | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/vendor/symfony/cache/Adapter/PhpArrayAdapter.php b/vendor/symfony/cache/Adapter/PhpArrayAdapter.php new file mode 100644 index 0000000..f92a238 --- /dev/null +++ b/vendor/symfony/cache/Adapter/PhpArrayAdapter.php | |||
@@ -0,0 +1,389 @@ | |||
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 | |||
12 | namespace Symfony\Component\Cache\Adapter; | ||
13 | |||
14 | use Psr\Cache\CacheItemInterface; | ||
15 | use Psr\Cache\CacheItemPoolInterface; | ||
16 | use Symfony\Component\Cache\CacheItem; | ||
17 | use Symfony\Component\Cache\Exception\InvalidArgumentException; | ||
18 | use Symfony\Component\Cache\PruneableInterface; | ||
19 | use Symfony\Component\Cache\ResettableInterface; | ||
20 | use Symfony\Component\Cache\Traits\ContractsTrait; | ||
21 | use Symfony\Component\Cache\Traits\ProxyTrait; | ||
22 | use Symfony\Component\VarExporter\VarExporter; | ||
23 | use Symfony\Contracts\Cache\CacheInterface; | ||
24 | |||
25 | /** | ||
26 | * Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0. | ||
27 | * Warmed up items are read-only and run-time discovered items are cached using a fallback adapter. | ||
28 | * | ||
29 | * @author Titouan Galopin <galopintitouan@gmail.com> | ||
30 | * @author Nicolas Grekas <p@tchwork.com> | ||
31 | */ | ||
32 | class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface | ||
33 | { | ||
34 | use ContractsTrait; | ||
35 | use ProxyTrait; | ||
36 | |||
37 | private array $keys; | ||
38 | private array $values; | ||
39 | |||
40 | private static \Closure $createCacheItem; | ||
41 | private static array $valuesCache = []; | ||
42 | |||
43 | /** | ||
44 | * @param string $file The PHP file were values are cached | ||
45 | * @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit | ||
46 | */ | ||
47 | public function __construct( | ||
48 | private string $file, | ||
49 | AdapterInterface $fallbackPool, | ||
50 | ) { | ||
51 | $this->pool = $fallbackPool; | ||
52 | self::$createCacheItem ??= \Closure::bind( | ||
53 | static function ($key, $value, $isHit) { | ||
54 | $item = new CacheItem(); | ||
55 | $item->key = $key; | ||
56 | $item->value = $value; | ||
57 | $item->isHit = $isHit; | ||
58 | |||
59 | return $item; | ||
60 | }, | ||
61 | null, | ||
62 | CacheItem::class | ||
63 | ); | ||
64 | } | ||
65 | |||
66 | /** | ||
67 | * This adapter takes advantage of how PHP stores arrays in its latest versions. | ||
68 | * | ||
69 | * @param string $file The PHP file were values are cached | ||
70 | * @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit | ||
71 | */ | ||
72 | public static function create(string $file, CacheItemPoolInterface $fallbackPool): CacheItemPoolInterface | ||
73 | { | ||
74 | if (!$fallbackPool instanceof AdapterInterface) { | ||
75 | $fallbackPool = new ProxyAdapter($fallbackPool); | ||
76 | } | ||
77 | |||
78 | return new static($file, $fallbackPool); | ||
79 | } | ||
80 | |||
81 | public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed | ||
82 | { | ||
83 | if (!isset($this->values)) { | ||
84 | $this->initialize(); | ||
85 | } | ||
86 | if (!isset($this->keys[$key])) { | ||
87 | get_from_pool: | ||
88 | if ($this->pool instanceof CacheInterface) { | ||
89 | return $this->pool->get($key, $callback, $beta, $metadata); | ||
90 | } | ||
91 | |||
92 | return $this->doGet($this->pool, $key, $callback, $beta, $metadata); | ||
93 | } | ||
94 | $value = $this->values[$this->keys[$key]]; | ||
95 | |||
96 | if ('N;' === $value) { | ||
97 | return null; | ||
98 | } | ||
99 | try { | ||
100 | if ($value instanceof \Closure) { | ||
101 | return $value(); | ||
102 | } | ||
103 | } catch (\Throwable) { | ||
104 | unset($this->keys[$key]); | ||
105 | goto get_from_pool; | ||
106 | } | ||
107 | |||
108 | return $value; | ||
109 | } | ||
110 | |||
111 | public function getItem(mixed $key): CacheItem | ||
112 | { | ||
113 | if (!\is_string($key)) { | ||
114 | throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); | ||
115 | } | ||
116 | if (!isset($this->values)) { | ||
117 | $this->initialize(); | ||
118 | } | ||
119 | if (!isset($this->keys[$key])) { | ||
120 | return $this->pool->getItem($key); | ||
121 | } | ||
122 | |||
123 | $value = $this->values[$this->keys[$key]]; | ||
124 | $isHit = true; | ||
125 | |||
126 | if ('N;' === $value) { | ||
127 | $value = null; | ||
128 | } elseif ($value instanceof \Closure) { | ||
129 | try { | ||
130 | $value = $value(); | ||
131 | } catch (\Throwable) { | ||
132 | $value = null; | ||
133 | $isHit = false; | ||
134 | } | ||
135 | } | ||
136 | |||
137 | return (self::$createCacheItem)($key, $value, $isHit); | ||
138 | } | ||
139 | |||
140 | public function getItems(array $keys = []): iterable | ||
141 | { | ||
142 | foreach ($keys as $key) { | ||
143 | if (!\is_string($key)) { | ||
144 | throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); | ||
145 | } | ||
146 | } | ||
147 | if (!isset($this->values)) { | ||
148 | $this->initialize(); | ||
149 | } | ||
150 | |||
151 | return $this->generateItems($keys); | ||
152 | } | ||
153 | |||
154 | public function hasItem(mixed $key): bool | ||
155 | { | ||
156 | if (!\is_string($key)) { | ||
157 | throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); | ||
158 | } | ||
159 | if (!isset($this->values)) { | ||
160 | $this->initialize(); | ||
161 | } | ||
162 | |||
163 | return isset($this->keys[$key]) || $this->pool->hasItem($key); | ||
164 | } | ||
165 | |||
166 | public function deleteItem(mixed $key): bool | ||
167 | { | ||
168 | if (!\is_string($key)) { | ||
169 | throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); | ||
170 | } | ||
171 | if (!isset($this->values)) { | ||
172 | $this->initialize(); | ||
173 | } | ||
174 | |||
175 | return !isset($this->keys[$key]) && $this->pool->deleteItem($key); | ||
176 | } | ||
177 | |||
178 | public function deleteItems(array $keys): bool | ||
179 | { | ||
180 | $deleted = true; | ||
181 | $fallbackKeys = []; | ||
182 | |||
183 | foreach ($keys as $key) { | ||
184 | if (!\is_string($key)) { | ||
185 | throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key))); | ||
186 | } | ||
187 | |||
188 | if (isset($this->keys[$key])) { | ||
189 | $deleted = false; | ||
190 | } else { | ||
191 | $fallbackKeys[] = $key; | ||
192 | } | ||
193 | } | ||
194 | if (!isset($this->values)) { | ||
195 | $this->initialize(); | ||
196 | } | ||
197 | |||
198 | if ($fallbackKeys) { | ||
199 | $deleted = $this->pool->deleteItems($fallbackKeys) && $deleted; | ||
200 | } | ||
201 | |||
202 | return $deleted; | ||
203 | } | ||
204 | |||
205 | public function save(CacheItemInterface $item): bool | ||
206 | { | ||
207 | if (!isset($this->values)) { | ||
208 | $this->initialize(); | ||
209 | } | ||
210 | |||
211 | return !isset($this->keys[$item->getKey()]) && $this->pool->save($item); | ||
212 | } | ||
213 | |||
214 | public function saveDeferred(CacheItemInterface $item): bool | ||
215 | { | ||
216 | if (!isset($this->values)) { | ||
217 | $this->initialize(); | ||
218 | } | ||
219 | |||
220 | return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item); | ||
221 | } | ||
222 | |||
223 | public function commit(): bool | ||
224 | { | ||
225 | return $this->pool->commit(); | ||
226 | } | ||
227 | |||
228 | public function clear(string $prefix = ''): bool | ||
229 | { | ||
230 | $this->keys = $this->values = []; | ||
231 | |||
232 | $cleared = @unlink($this->file) || !file_exists($this->file); | ||
233 | unset(self::$valuesCache[$this->file]); | ||
234 | |||
235 | if ($this->pool instanceof AdapterInterface) { | ||
236 | return $this->pool->clear($prefix) && $cleared; | ||
237 | } | ||
238 | |||
239 | return $this->pool->clear() && $cleared; | ||
240 | } | ||
241 | |||
242 | /** | ||
243 | * Store an array of cached values. | ||
244 | * | ||
245 | * @param array $values The cached values | ||
246 | * | ||
247 | * @return string[] A list of classes to preload on PHP 7.4+ | ||
248 | */ | ||
249 | public function warmUp(array $values): array | ||
250 | { | ||
251 | if (file_exists($this->file)) { | ||
252 | if (!is_file($this->file)) { | ||
253 | throw new InvalidArgumentException(sprintf('Cache path exists and is not a file: "%s".', $this->file)); | ||
254 | } | ||
255 | |||
256 | if (!is_writable($this->file)) { | ||
257 | throw new InvalidArgumentException(sprintf('Cache file is not writable: "%s".', $this->file)); | ||
258 | } | ||
259 | } else { | ||
260 | $directory = \dirname($this->file); | ||
261 | |||
262 | if (!is_dir($directory) && !@mkdir($directory, 0777, true)) { | ||
263 | throw new InvalidArgumentException(sprintf('Cache directory does not exist and cannot be created: "%s".', $directory)); | ||
264 | } | ||
265 | |||
266 | if (!is_writable($directory)) { | ||
267 | throw new InvalidArgumentException(sprintf('Cache directory is not writable: "%s".', $directory)); | ||
268 | } | ||
269 | } | ||
270 | |||
271 | $preload = []; | ||
272 | $dumpedValues = ''; | ||
273 | $dumpedMap = []; | ||
274 | $dump = <<<'EOF' | ||
275 | <?php | ||
276 | |||
277 | // This file has been auto-generated by the Symfony Cache Component. | ||
278 | |||
279 | return [[ | ||
280 | |||
281 | |||
282 | EOF; | ||
283 | |||
284 | foreach ($values as $key => $value) { | ||
285 | CacheItem::validateKey(\is_int($key) ? (string) $key : $key); | ||
286 | $isStaticValue = true; | ||
287 | |||
288 | if (null === $value) { | ||
289 | $value = "'N;'"; | ||
290 | } elseif (\is_object($value) || \is_array($value)) { | ||
291 | try { | ||
292 | $value = VarExporter::export($value, $isStaticValue, $preload); | ||
293 | } catch (\Exception $e) { | ||
294 | throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)), 0, $e); | ||
295 | } | ||
296 | } elseif (\is_string($value)) { | ||
297 | // Wrap "N;" in a closure to not confuse it with an encoded `null` | ||
298 | if ('N;' === $value) { | ||
299 | $isStaticValue = false; | ||
300 | } | ||
301 | $value = var_export($value, true); | ||
302 | } elseif (!\is_scalar($value)) { | ||
303 | throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value))); | ||
304 | } else { | ||
305 | $value = var_export($value, true); | ||
306 | } | ||
307 | |||
308 | if (!$isStaticValue) { | ||
309 | $value = str_replace("\n", "\n ", $value); | ||
310 | $value = "static function () {\n return {$value};\n}"; | ||
311 | } | ||
312 | $hash = hash('xxh128', $value); | ||
313 | |||
314 | if (null === $id = $dumpedMap[$hash] ?? null) { | ||
315 | $id = $dumpedMap[$hash] = \count($dumpedMap); | ||
316 | $dumpedValues .= "{$id} => {$value},\n"; | ||
317 | } | ||
318 | |||
319 | $dump .= var_export($key, true)." => {$id},\n"; | ||
320 | } | ||
321 | |||
322 | $dump .= "\n], [\n\n{$dumpedValues}\n]];\n"; | ||
323 | |||
324 | $tmpFile = uniqid($this->file, true); | ||
325 | |||
326 | file_put_contents($tmpFile, $dump); | ||
327 | @chmod($tmpFile, 0666 & ~umask()); | ||
328 | unset($serialized, $value, $dump); | ||
329 | |||
330 | @rename($tmpFile, $this->file); | ||
331 | unset(self::$valuesCache[$this->file]); | ||
332 | |||
333 | $this->initialize(); | ||
334 | |||
335 | return $preload; | ||
336 | } | ||
337 | |||
338 | /** | ||
339 | * Load the cache file. | ||
340 | */ | ||
341 | private function initialize(): void | ||
342 | { | ||
343 | if (isset(self::$valuesCache[$this->file])) { | ||
344 | $values = self::$valuesCache[$this->file]; | ||
345 | } elseif (!is_file($this->file)) { | ||
346 | $this->keys = $this->values = []; | ||
347 | |||
348 | return; | ||
349 | } else { | ||
350 | $values = self::$valuesCache[$this->file] = (include $this->file) ?: [[], []]; | ||
351 | } | ||
352 | |||
353 | if (2 !== \count($values) || !isset($values[0], $values[1])) { | ||
354 | $this->keys = $this->values = []; | ||
355 | } else { | ||
356 | [$this->keys, $this->values] = $values; | ||
357 | } | ||
358 | } | ||
359 | |||
360 | private function generateItems(array $keys): \Generator | ||
361 | { | ||
362 | $f = self::$createCacheItem; | ||
363 | $fallbackKeys = []; | ||
364 | |||
365 | foreach ($keys as $key) { | ||
366 | if (isset($this->keys[$key])) { | ||
367 | $value = $this->values[$this->keys[$key]]; | ||
368 | |||
369 | if ('N;' === $value) { | ||
370 | yield $key => $f($key, null, true); | ||
371 | } elseif ($value instanceof \Closure) { | ||
372 | try { | ||
373 | yield $key => $f($key, $value(), true); | ||
374 | } catch (\Throwable) { | ||
375 | yield $key => $f($key, null, false); | ||
376 | } | ||
377 | } else { | ||
378 | yield $key => $f($key, $value, true); | ||
379 | } | ||
380 | } else { | ||
381 | $fallbackKeys[] = $key; | ||
382 | } | ||
383 | } | ||
384 | |||
385 | if ($fallbackKeys) { | ||
386 | yield from $this->pool->getItems($fallbackKeys); | ||
387 | } | ||
388 | } | ||
389 | } | ||