summaryrefslogtreecommitdiff
path: root/vendor/symfony/cache/Adapter/ChainAdapter.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/symfony/cache/Adapter/ChainAdapter.php')
-rw-r--r--vendor/symfony/cache/Adapter/ChainAdapter.php291
1 files changed, 291 insertions, 0 deletions
diff --git a/vendor/symfony/cache/Adapter/ChainAdapter.php b/vendor/symfony/cache/Adapter/ChainAdapter.php
new file mode 100644
index 0000000..1418cff
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/ChainAdapter.php
@@ -0,0 +1,291 @@
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\Contracts\Cache\CacheInterface;
22use Symfony\Contracts\Service\ResetInterface;
23
24/**
25 * Chains several adapters together.
26 *
27 * Cached items are fetched from the first adapter having them in its data store.
28 * They are saved and deleted in all adapters at once.
29 *
30 * @author Kévin Dunglas <dunglas@gmail.com>
31 */
32class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
33{
34 use ContractsTrait;
35
36 private array $adapters = [];
37 private int $adapterCount;
38
39 private static \Closure $syncItem;
40
41 /**
42 * @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items
43 * @param int $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones
44 */
45 public function __construct(
46 array $adapters,
47 private int $defaultLifetime = 0,
48 ) {
49 if (!$adapters) {
50 throw new InvalidArgumentException('At least one adapter must be specified.');
51 }
52
53 foreach ($adapters as $adapter) {
54 if (!$adapter instanceof CacheItemPoolInterface) {
55 throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', get_debug_type($adapter), CacheItemPoolInterface::class));
56 }
57 if ('cli' === \PHP_SAPI && $adapter instanceof ApcuAdapter && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOL)) {
58 continue; // skip putting APCu in the chain when the backend is disabled
59 }
60
61 if ($adapter instanceof AdapterInterface) {
62 $this->adapters[] = $adapter;
63 } else {
64 $this->adapters[] = new ProxyAdapter($adapter);
65 }
66 }
67 $this->adapterCount = \count($this->adapters);
68
69 self::$syncItem ??= \Closure::bind(
70 static function ($sourceItem, $item, $defaultLifetime, $sourceMetadata = null) {
71 $sourceItem->isTaggable = false;
72 $sourceMetadata ??= $sourceItem->metadata;
73
74 $item->value = $sourceItem->value;
75 $item->isHit = $sourceItem->isHit;
76 $item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata;
77
78 if (isset($item->metadata[CacheItem::METADATA_EXPIRY])) {
79 $item->expiresAt(\DateTimeImmutable::createFromFormat('U.u', sprintf('%.6F', $item->metadata[CacheItem::METADATA_EXPIRY])));
80 } elseif (0 < $defaultLifetime) {
81 $item->expiresAfter($defaultLifetime);
82 }
83
84 return $item;
85 },
86 null,
87 CacheItem::class
88 );
89 }
90
91 public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed
92 {
93 $doSave = true;
94 $callback = static function (CacheItem $item, bool &$save) use ($callback, &$doSave) {
95 $value = $callback($item, $save);
96 $doSave = $save;
97
98 return $value;
99 };
100
101 $wrap = function (?CacheItem $item = null, bool &$save = true) use ($key, $callback, $beta, &$wrap, &$doSave, &$metadata) {
102 static $lastItem;
103 static $i = 0;
104 $adapter = $this->adapters[$i];
105 if (isset($this->adapters[++$i])) {
106 $callback = $wrap;
107 $beta = \INF === $beta ? \INF : 0;
108 }
109 if ($adapter instanceof CacheInterface) {
110 $value = $adapter->get($key, $callback, $beta, $metadata);
111 } else {
112 $value = $this->doGet($adapter, $key, $callback, $beta, $metadata);
113 }
114 if (null !== $item) {
115 (self::$syncItem)($lastItem ??= $item, $item, $this->defaultLifetime, $metadata);
116 }
117 $save = $doSave;
118
119 return $value;
120 };
121
122 return $wrap();
123 }
124
125 public function getItem(mixed $key): CacheItem
126 {
127 $syncItem = self::$syncItem;
128 $misses = [];
129
130 foreach ($this->adapters as $i => $adapter) {
131 $item = $adapter->getItem($key);
132
133 if ($item->isHit()) {
134 while (0 <= --$i) {
135 $this->adapters[$i]->save($syncItem($item, $misses[$i], $this->defaultLifetime));
136 }
137
138 return $item;
139 }
140
141 $misses[$i] = $item;
142 }
143
144 return $item;
145 }
146
147 public function getItems(array $keys = []): iterable
148 {
149 return $this->generateItems($this->adapters[0]->getItems($keys), 0);
150 }
151
152 private function generateItems(iterable $items, int $adapterIndex): \Generator
153 {
154 $missing = [];
155 $misses = [];
156 $nextAdapterIndex = $adapterIndex + 1;
157 $nextAdapter = $this->adapters[$nextAdapterIndex] ?? null;
158
159 foreach ($items as $k => $item) {
160 if (!$nextAdapter || $item->isHit()) {
161 yield $k => $item;
162 } else {
163 $missing[] = $k;
164 $misses[$k] = $item;
165 }
166 }
167
168 if ($missing) {
169 $syncItem = self::$syncItem;
170 $adapter = $this->adapters[$adapterIndex];
171 $items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex);
172
173 foreach ($items as $k => $item) {
174 if ($item->isHit()) {
175 $adapter->save($syncItem($item, $misses[$k], $this->defaultLifetime));
176 }
177
178 yield $k => $item;
179 }
180 }
181 }
182
183 public function hasItem(mixed $key): bool
184 {
185 foreach ($this->adapters as $adapter) {
186 if ($adapter->hasItem($key)) {
187 return true;
188 }
189 }
190
191 return false;
192 }
193
194 public function clear(string $prefix = ''): bool
195 {
196 $cleared = true;
197 $i = $this->adapterCount;
198
199 while ($i--) {
200 if ($this->adapters[$i] instanceof AdapterInterface) {
201 $cleared = $this->adapters[$i]->clear($prefix) && $cleared;
202 } else {
203 $cleared = $this->adapters[$i]->clear() && $cleared;
204 }
205 }
206
207 return $cleared;
208 }
209
210 public function deleteItem(mixed $key): bool
211 {
212 $deleted = true;
213 $i = $this->adapterCount;
214
215 while ($i--) {
216 $deleted = $this->adapters[$i]->deleteItem($key) && $deleted;
217 }
218
219 return $deleted;
220 }
221
222 public function deleteItems(array $keys): bool
223 {
224 $deleted = true;
225 $i = $this->adapterCount;
226
227 while ($i--) {
228 $deleted = $this->adapters[$i]->deleteItems($keys) && $deleted;
229 }
230
231 return $deleted;
232 }
233
234 public function save(CacheItemInterface $item): bool
235 {
236 $saved = true;
237 $i = $this->adapterCount;
238
239 while ($i--) {
240 $saved = $this->adapters[$i]->save($item) && $saved;
241 }
242
243 return $saved;
244 }
245
246 public function saveDeferred(CacheItemInterface $item): bool
247 {
248 $saved = true;
249 $i = $this->adapterCount;
250
251 while ($i--) {
252 $saved = $this->adapters[$i]->saveDeferred($item) && $saved;
253 }
254
255 return $saved;
256 }
257
258 public function commit(): bool
259 {
260 $committed = true;
261 $i = $this->adapterCount;
262
263 while ($i--) {
264 $committed = $this->adapters[$i]->commit() && $committed;
265 }
266
267 return $committed;
268 }
269
270 public function prune(): bool
271 {
272 $pruned = true;
273
274 foreach ($this->adapters as $adapter) {
275 if ($adapter instanceof PruneableInterface) {
276 $pruned = $adapter->prune() && $pruned;
277 }
278 }
279
280 return $pruned;
281 }
282
283 public function reset(): void
284 {
285 foreach ($this->adapters as $adapter) {
286 if ($adapter instanceof ResetInterface) {
287 $adapter->reset();
288 }
289 }
290 }
291}