PhpArrayAdapter.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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. use Symfony\Component\Cache\Traits\ContractsTrait;
  18. use Symfony\Component\Cache\Traits\PhpArrayTrait;
  19. use Symfony\Contracts\Cache\CacheInterface;
  20. /**
  21. * Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0.
  22. * Warmed up items are read-only and run-time discovered items are cached using a fallback adapter.
  23. *
  24. * @author Titouan Galopin <galopintitouan@gmail.com>
  25. * @author Nicolas Grekas <p@tchwork.com>
  26. */
  27. class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
  28. {
  29. use ContractsTrait;
  30. use PhpArrayTrait;
  31. private $createCacheItem;
  32. /**
  33. * @param string $file The PHP file were values are cached
  34. * @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit
  35. */
  36. public function __construct(string $file, AdapterInterface $fallbackPool)
  37. {
  38. $this->file = $file;
  39. $this->pool = $fallbackPool;
  40. $this->createCacheItem = \Closure::bind(
  41. static function ($key, $value, $isHit) {
  42. $item = new CacheItem();
  43. $item->key = $key;
  44. $item->value = $value;
  45. $item->isHit = $isHit;
  46. return $item;
  47. },
  48. null,
  49. CacheItem::class
  50. );
  51. }
  52. /**
  53. * This adapter takes advantage of how PHP stores arrays in its latest versions.
  54. *
  55. * @param string $file The PHP file were values are cached
  56. * @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit
  57. *
  58. * @return CacheItemPoolInterface
  59. */
  60. public static function create($file, CacheItemPoolInterface $fallbackPool)
  61. {
  62. if (!$fallbackPool instanceof AdapterInterface) {
  63. $fallbackPool = new ProxyAdapter($fallbackPool);
  64. }
  65. return new static($file, $fallbackPool);
  66. }
  67. /**
  68. * {@inheritdoc}
  69. */
  70. public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
  71. {
  72. if (null === $this->values) {
  73. $this->initialize();
  74. }
  75. if (!isset($this->keys[$key])) {
  76. get_from_pool:
  77. if ($this->pool instanceof CacheInterface) {
  78. return $this->pool->get($key, $callback, $beta, $metadata);
  79. }
  80. return $this->doGet($this->pool, $key, $callback, $beta, $metadata);
  81. }
  82. $value = $this->values[$this->keys[$key]];
  83. if ('N;' === $value) {
  84. return null;
  85. }
  86. try {
  87. if ($value instanceof \Closure) {
  88. return $value();
  89. }
  90. } catch (\Throwable $e) {
  91. unset($this->keys[$key]);
  92. goto get_from_pool;
  93. }
  94. return $value;
  95. }
  96. /**
  97. * {@inheritdoc}
  98. */
  99. public function getItem($key)
  100. {
  101. if (!\is_string($key)) {
  102. throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
  103. }
  104. if (null === $this->values) {
  105. $this->initialize();
  106. }
  107. if (!isset($this->keys[$key])) {
  108. return $this->pool->getItem($key);
  109. }
  110. $value = $this->values[$this->keys[$key]];
  111. $isHit = true;
  112. if ('N;' === $value) {
  113. $value = null;
  114. } elseif ($value instanceof \Closure) {
  115. try {
  116. $value = $value();
  117. } catch (\Throwable $e) {
  118. $value = null;
  119. $isHit = false;
  120. }
  121. }
  122. $f = $this->createCacheItem;
  123. return $f($key, $value, $isHit);
  124. }
  125. /**
  126. * {@inheritdoc}
  127. */
  128. public function getItems(array $keys = [])
  129. {
  130. foreach ($keys as $key) {
  131. if (!\is_string($key)) {
  132. throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
  133. }
  134. }
  135. if (null === $this->values) {
  136. $this->initialize();
  137. }
  138. return $this->generateItems($keys);
  139. }
  140. /**
  141. * {@inheritdoc}
  142. *
  143. * @return bool
  144. */
  145. public function hasItem($key)
  146. {
  147. if (!\is_string($key)) {
  148. throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
  149. }
  150. if (null === $this->values) {
  151. $this->initialize();
  152. }
  153. return isset($this->keys[$key]) || $this->pool->hasItem($key);
  154. }
  155. /**
  156. * {@inheritdoc}
  157. *
  158. * @return bool
  159. */
  160. public function deleteItem($key)
  161. {
  162. if (!\is_string($key)) {
  163. throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
  164. }
  165. if (null === $this->values) {
  166. $this->initialize();
  167. }
  168. return !isset($this->keys[$key]) && $this->pool->deleteItem($key);
  169. }
  170. /**
  171. * {@inheritdoc}
  172. *
  173. * @return bool
  174. */
  175. public function deleteItems(array $keys)
  176. {
  177. $deleted = true;
  178. $fallbackKeys = [];
  179. foreach ($keys as $key) {
  180. if (!\is_string($key)) {
  181. throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
  182. }
  183. if (isset($this->keys[$key])) {
  184. $deleted = false;
  185. } else {
  186. $fallbackKeys[] = $key;
  187. }
  188. }
  189. if (null === $this->values) {
  190. $this->initialize();
  191. }
  192. if ($fallbackKeys) {
  193. $deleted = $this->pool->deleteItems($fallbackKeys) && $deleted;
  194. }
  195. return $deleted;
  196. }
  197. /**
  198. * {@inheritdoc}
  199. *
  200. * @return bool
  201. */
  202. public function save(CacheItemInterface $item)
  203. {
  204. if (null === $this->values) {
  205. $this->initialize();
  206. }
  207. return !isset($this->keys[$item->getKey()]) && $this->pool->save($item);
  208. }
  209. /**
  210. * {@inheritdoc}
  211. *
  212. * @return bool
  213. */
  214. public function saveDeferred(CacheItemInterface $item)
  215. {
  216. if (null === $this->values) {
  217. $this->initialize();
  218. }
  219. return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
  220. }
  221. /**
  222. * {@inheritdoc}
  223. *
  224. * @return bool
  225. */
  226. public function commit()
  227. {
  228. return $this->pool->commit();
  229. }
  230. private function generateItems(array $keys): \Generator
  231. {
  232. $f = $this->createCacheItem;
  233. $fallbackKeys = [];
  234. foreach ($keys as $key) {
  235. if (isset($this->keys[$key])) {
  236. $value = $this->values[$this->keys[$key]];
  237. if ('N;' === $value) {
  238. yield $key => $f($key, null, true);
  239. } elseif ($value instanceof \Closure) {
  240. try {
  241. yield $key => $f($key, $value(), true);
  242. } catch (\Throwable $e) {
  243. yield $key => $f($key, null, false);
  244. }
  245. } else {
  246. yield $key => $f($key, $value, true);
  247. }
  248. } else {
  249. $fallbackKeys[] = $key;
  250. }
  251. }
  252. if ($fallbackKeys) {
  253. yield from $this->pool->getItems($fallbackKeys);
  254. }
  255. }
  256. /**
  257. * @throws \ReflectionException When $class is not found and is required
  258. *
  259. * @internal to be removed in Symfony 5.0
  260. */
  261. public static function throwOnRequiredClass($class)
  262. {
  263. $e = new \ReflectionException("Class $class does not exist");
  264. $trace = debug_backtrace();
  265. $autoloadFrame = [
  266. 'function' => 'spl_autoload_call',
  267. 'args' => [$class],
  268. ];
  269. if (\PHP_VERSION_ID >= 80000 && isset($trace[1])) {
  270. $callerFrame = $trace[1];
  271. } elseif (false !== $i = array_search($autoloadFrame, $trace, true)) {
  272. $callerFrame = $trace[++$i];
  273. } else {
  274. throw $e;
  275. }
  276. if (isset($callerFrame['function']) && !isset($callerFrame['class'])) {
  277. switch ($callerFrame['function']) {
  278. case 'get_class_methods':
  279. case 'get_class_vars':
  280. case 'get_parent_class':
  281. case 'is_a':
  282. case 'is_subclass_of':
  283. case 'class_exists':
  284. case 'class_implements':
  285. case 'class_parents':
  286. case 'trait_exists':
  287. case 'defined':
  288. case 'interface_exists':
  289. case 'method_exists':
  290. case 'property_exists':
  291. case 'is_callable':
  292. return;
  293. }
  294. }
  295. throw $e;
  296. }
  297. }