ChainAdapter.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Cache\Adapter;
  11. use Psr\Cache\CacheItemInterface;
  12. use Psr\Cache\CacheItemPoolInterface;
  13. use Symfony\Component\Cache\CacheItem;
  14. use Symfony\Component\Cache\Exception\InvalidArgumentException;
  15. use Symfony\Component\Cache\PruneableInterface;
  16. use Symfony\Component\Cache\ResettableInterface;
  17. /**
  18. * Chains several adapters together.
  19. *
  20. * Cached items are fetched from the first adapter having them in its data store.
  21. * They are saved and deleted in all adapters at once.
  22. *
  23. * @author Kévin Dunglas <dunglas@gmail.com>
  24. */
  25. class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
  26. {
  27. private $adapters = [];
  28. private $adapterCount;
  29. private $syncItem;
  30. /**
  31. * @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items
  32. * @param int $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones
  33. */
  34. public function __construct(array $adapters, $defaultLifetime = 0)
  35. {
  36. if (!$adapters) {
  37. throw new InvalidArgumentException('At least one adapter must be specified.');
  38. }
  39. foreach ($adapters as $adapter) {
  40. if (!$adapter instanceof CacheItemPoolInterface) {
  41. throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($adapter), CacheItemPoolInterface::class));
  42. }
  43. if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && $adapter instanceof ApcuAdapter && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
  44. continue; // skip putting APCu in the chain when the backend is disabled
  45. }
  46. if ($adapter instanceof AdapterInterface) {
  47. $this->adapters[] = $adapter;
  48. } else {
  49. $this->adapters[] = new ProxyAdapter($adapter);
  50. }
  51. }
  52. $this->adapterCount = \count($this->adapters);
  53. $this->syncItem = \Closure::bind(
  54. static function ($sourceItem, $item) use ($defaultLifetime) {
  55. $item->value = $sourceItem->value;
  56. $item->isHit = $sourceItem->isHit;
  57. if (0 < $defaultLifetime) {
  58. $item->expiresAfter($defaultLifetime);
  59. }
  60. return $item;
  61. },
  62. null,
  63. CacheItem::class
  64. );
  65. }
  66. /**
  67. * {@inheritdoc}
  68. */
  69. public function getItem($key)
  70. {
  71. $syncItem = $this->syncItem;
  72. $misses = [];
  73. foreach ($this->adapters as $i => $adapter) {
  74. $item = $adapter->getItem($key);
  75. if ($item->isHit()) {
  76. while (0 <= --$i) {
  77. $this->adapters[$i]->save($syncItem($item, $misses[$i]));
  78. }
  79. return $item;
  80. }
  81. $misses[$i] = $item;
  82. }
  83. return $item;
  84. }
  85. /**
  86. * {@inheritdoc}
  87. */
  88. public function getItems(array $keys = [])
  89. {
  90. return $this->generateItems($this->adapters[0]->getItems($keys), 0);
  91. }
  92. private function generateItems($items, $adapterIndex)
  93. {
  94. $missing = [];
  95. $misses = [];
  96. $nextAdapterIndex = $adapterIndex + 1;
  97. $nextAdapter = isset($this->adapters[$nextAdapterIndex]) ? $this->adapters[$nextAdapterIndex] : null;
  98. foreach ($items as $k => $item) {
  99. if (!$nextAdapter || $item->isHit()) {
  100. yield $k => $item;
  101. } else {
  102. $missing[] = $k;
  103. $misses[$k] = $item;
  104. }
  105. }
  106. if ($missing) {
  107. $syncItem = $this->syncItem;
  108. $adapter = $this->adapters[$adapterIndex];
  109. $items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex);
  110. foreach ($items as $k => $item) {
  111. if ($item->isHit()) {
  112. $adapter->save($syncItem($item, $misses[$k]));
  113. }
  114. yield $k => $item;
  115. }
  116. }
  117. }
  118. /**
  119. * {@inheritdoc}
  120. */
  121. public function hasItem($key)
  122. {
  123. foreach ($this->adapters as $adapter) {
  124. if ($adapter->hasItem($key)) {
  125. return true;
  126. }
  127. }
  128. return false;
  129. }
  130. /**
  131. * {@inheritdoc}
  132. */
  133. public function clear()
  134. {
  135. $cleared = true;
  136. $i = $this->adapterCount;
  137. while ($i--) {
  138. $cleared = $this->adapters[$i]->clear() && $cleared;
  139. }
  140. return $cleared;
  141. }
  142. /**
  143. * {@inheritdoc}
  144. */
  145. public function deleteItem($key)
  146. {
  147. $deleted = true;
  148. $i = $this->adapterCount;
  149. while ($i--) {
  150. $deleted = $this->adapters[$i]->deleteItem($key) && $deleted;
  151. }
  152. return $deleted;
  153. }
  154. /**
  155. * {@inheritdoc}
  156. */
  157. public function deleteItems(array $keys)
  158. {
  159. $deleted = true;
  160. $i = $this->adapterCount;
  161. while ($i--) {
  162. $deleted = $this->adapters[$i]->deleteItems($keys) && $deleted;
  163. }
  164. return $deleted;
  165. }
  166. /**
  167. * {@inheritdoc}
  168. */
  169. public function save(CacheItemInterface $item)
  170. {
  171. $saved = true;
  172. $i = $this->adapterCount;
  173. while ($i--) {
  174. $saved = $this->adapters[$i]->save($item) && $saved;
  175. }
  176. return $saved;
  177. }
  178. /**
  179. * {@inheritdoc}
  180. */
  181. public function saveDeferred(CacheItemInterface $item)
  182. {
  183. $saved = true;
  184. $i = $this->adapterCount;
  185. while ($i--) {
  186. $saved = $this->adapters[$i]->saveDeferred($item) && $saved;
  187. }
  188. return $saved;
  189. }
  190. /**
  191. * {@inheritdoc}
  192. */
  193. public function commit()
  194. {
  195. $committed = true;
  196. $i = $this->adapterCount;
  197. while ($i--) {
  198. $committed = $this->adapters[$i]->commit() && $committed;
  199. }
  200. return $committed;
  201. }
  202. /**
  203. * {@inheritdoc}
  204. */
  205. public function prune()
  206. {
  207. $pruned = true;
  208. foreach ($this->adapters as $adapter) {
  209. if ($adapter instanceof PruneableInterface) {
  210. $pruned = $adapter->prune() && $pruned;
  211. }
  212. }
  213. return $pruned;
  214. }
  215. /**
  216. * {@inheritdoc}
  217. */
  218. public function reset()
  219. {
  220. foreach ($this->adapters as $adapter) {
  221. if ($adapter instanceof ResettableInterface) {
  222. $adapter->reset();
  223. }
  224. }
  225. }
  226. }