summaryrefslogtreecommitdiff
path: root/vendor/symfony/cache/Adapter/PhpArrayAdapter.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/symfony/cache/Adapter/PhpArrayAdapter.php')
-rw-r--r--vendor/symfony/cache/Adapter/PhpArrayAdapter.php389
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
12namespace Symfony\Component\Cache\Adapter;
13
14use Psr\Cache\CacheItemInterface;
15use Psr\Cache\CacheItemPoolInterface;
16use Symfony\Component\Cache\CacheItem;
17use Symfony\Component\Cache\Exception\InvalidArgumentException;
18use Symfony\Component\Cache\PruneableInterface;
19use Symfony\Component\Cache\ResettableInterface;
20use Symfony\Component\Cache\Traits\ContractsTrait;
21use Symfony\Component\Cache\Traits\ProxyTrait;
22use Symfony\Component\VarExporter\VarExporter;
23use 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 */
32class 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
279return [[
280
281
282EOF;
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}