PageRenderTime 41ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php

http://github.com/fabpot/symfony
PHP | 393 lines | 256 code | 56 blank | 81 comment | 59 complexity | 942c900946e1e77523045ad6577e389b MD5 | raw file
  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\Log\LoggerAwareInterface;
  13. use Psr\Log\LoggerAwareTrait;
  14. use Symfony\Component\Cache\CacheItem;
  15. use Symfony\Component\Cache\Exception\InvalidArgumentException;
  16. use Symfony\Component\Cache\ResettableInterface;
  17. use Symfony\Contracts\Cache\CacheInterface;
  18. /**
  19. * An in-memory cache storage.
  20. *
  21. * Acts as a least-recently-used (LRU) storage when configured with a maximum number of items.
  22. *
  23. * @author Nicolas Grekas <p@tchwork.com>
  24. */
  25. class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
  26. {
  27. use LoggerAwareTrait;
  28. private $storeSerialized;
  29. private $values = [];
  30. private $expiries = [];
  31. private $createCacheItem;
  32. private $maxLifetime;
  33. private $maxItems;
  34. /**
  35. * @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
  36. */
  37. public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true, int $maxLifetime = 0, int $maxItems = 0)
  38. {
  39. if (0 > $maxLifetime) {
  40. throw new InvalidArgumentException(sprintf('Argument $maxLifetime must be a positive integer, %d passed.', $maxLifetime));
  41. }
  42. if (0 > $maxItems) {
  43. throw new InvalidArgumentException(sprintf('Argument $maxItems must be a positive integer, %d passed.', $maxItems));
  44. }
  45. $this->storeSerialized = $storeSerialized;
  46. $this->maxLifetime = $maxLifetime;
  47. $this->maxItems = $maxItems;
  48. $this->createCacheItem = \Closure::bind(
  49. static function ($key, $value, $isHit) use ($defaultLifetime) {
  50. $item = new CacheItem();
  51. $item->key = $key;
  52. $item->value = $value;
  53. $item->isHit = $isHit;
  54. $item->defaultLifetime = $defaultLifetime;
  55. return $item;
  56. },
  57. null,
  58. CacheItem::class
  59. );
  60. }
  61. /**
  62. * {@inheritdoc}
  63. */
  64. public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
  65. {
  66. $item = $this->getItem($key);
  67. $metadata = $item->getMetadata();
  68. // ArrayAdapter works in memory, we don't care about stampede protection
  69. if (INF === $beta || !$item->isHit()) {
  70. $save = true;
  71. $this->save($item->set($callback($item, $save)));
  72. }
  73. return $item->get();
  74. }
  75. /**
  76. * {@inheritdoc}
  77. */
  78. public function delete(string $key): bool
  79. {
  80. return $this->deleteItem($key);
  81. }
  82. /**
  83. * {@inheritdoc}
  84. *
  85. * @return bool
  86. */
  87. public function hasItem($key)
  88. {
  89. if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) {
  90. if ($this->maxItems) {
  91. // Move the item last in the storage
  92. $value = $this->values[$key];
  93. unset($this->values[$key]);
  94. $this->values[$key] = $value;
  95. }
  96. return true;
  97. }
  98. CacheItem::validateKey($key);
  99. return isset($this->expiries[$key]) && !$this->deleteItem($key);
  100. }
  101. /**
  102. * {@inheritdoc}
  103. */
  104. public function getItem($key)
  105. {
  106. if (!$isHit = $this->hasItem($key)) {
  107. $value = null;
  108. if (!$this->maxItems) {
  109. // Track misses in non-LRU mode only
  110. $this->values[$key] = null;
  111. }
  112. } else {
  113. $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
  114. }
  115. $f = $this->createCacheItem;
  116. return $f($key, $value, $isHit);
  117. }
  118. /**
  119. * {@inheritdoc}
  120. */
  121. public function getItems(array $keys = [])
  122. {
  123. foreach ($keys as $key) {
  124. if (!\is_string($key) || !isset($this->expiries[$key])) {
  125. CacheItem::validateKey($key);
  126. }
  127. }
  128. return $this->generateItems($keys, microtime(true), $this->createCacheItem);
  129. }
  130. /**
  131. * {@inheritdoc}
  132. *
  133. * @return bool
  134. */
  135. public function deleteItem($key)
  136. {
  137. if (!\is_string($key) || !isset($this->expiries[$key])) {
  138. CacheItem::validateKey($key);
  139. }
  140. unset($this->values[$key], $this->expiries[$key]);
  141. return true;
  142. }
  143. /**
  144. * {@inheritdoc}
  145. *
  146. * @return bool
  147. */
  148. public function deleteItems(array $keys)
  149. {
  150. foreach ($keys as $key) {
  151. $this->deleteItem($key);
  152. }
  153. return true;
  154. }
  155. /**
  156. * {@inheritdoc}
  157. *
  158. * @return bool
  159. */
  160. public function save(CacheItemInterface $item)
  161. {
  162. if (!$item instanceof CacheItem) {
  163. return false;
  164. }
  165. $item = (array) $item;
  166. $key = $item["\0*\0key"];
  167. $value = $item["\0*\0value"];
  168. $expiry = $item["\0*\0expiry"];
  169. $now = microtime(true);
  170. if (null !== $expiry && $expiry <= $now) {
  171. $this->deleteItem($key);
  172. return true;
  173. }
  174. if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
  175. return false;
  176. }
  177. if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) {
  178. $expiry = $item["\0*\0defaultLifetime"];
  179. $expiry = $now + ($expiry > ($this->maxLifetime ?: $expiry) ? $this->maxLifetime : $expiry);
  180. } elseif ($this->maxLifetime && (null === $expiry || $expiry > $now + $this->maxLifetime)) {
  181. $expiry = $now + $this->maxLifetime;
  182. }
  183. if ($this->maxItems) {
  184. unset($this->values[$key]);
  185. // Iterate items and vacuum expired ones while we are at it
  186. foreach ($this->values as $k => $v) {
  187. if ($this->expiries[$k] > $now && \count($this->values) < $this->maxItems) {
  188. break;
  189. }
  190. unset($this->values[$k], $this->expiries[$k]);
  191. }
  192. }
  193. $this->values[$key] = $value;
  194. $this->expiries[$key] = null !== $expiry ? $expiry : PHP_INT_MAX;
  195. return true;
  196. }
  197. /**
  198. * {@inheritdoc}
  199. *
  200. * @return bool
  201. */
  202. public function saveDeferred(CacheItemInterface $item)
  203. {
  204. return $this->save($item);
  205. }
  206. /**
  207. * {@inheritdoc}
  208. *
  209. * @return bool
  210. */
  211. public function commit()
  212. {
  213. return true;
  214. }
  215. /**
  216. * {@inheritdoc}
  217. *
  218. * @return bool
  219. */
  220. public function clear(string $prefix = '')
  221. {
  222. if ('' !== $prefix) {
  223. $now = microtime(true);
  224. foreach ($this->values as $key => $value) {
  225. if (!isset($this->expiries[$key]) || $this->expiries[$key] <= $now || 0 === strpos($key, $prefix)) {
  226. unset($this->values[$key], $this->expiries[$key]);
  227. }
  228. }
  229. if ($this->values) {
  230. return true;
  231. }
  232. }
  233. $this->values = $this->expiries = [];
  234. return true;
  235. }
  236. /**
  237. * Returns all cached values, with cache miss as null.
  238. *
  239. * @return array
  240. */
  241. public function getValues()
  242. {
  243. if (!$this->storeSerialized) {
  244. return $this->values;
  245. }
  246. $values = $this->values;
  247. foreach ($values as $k => $v) {
  248. if (null === $v || 'N;' === $v) {
  249. continue;
  250. }
  251. if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) {
  252. $values[$k] = serialize($v);
  253. }
  254. }
  255. return $values;
  256. }
  257. /**
  258. * {@inheritdoc}
  259. */
  260. public function reset()
  261. {
  262. $this->clear();
  263. }
  264. private function generateItems(array $keys, $now, $f)
  265. {
  266. foreach ($keys as $i => $key) {
  267. if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) {
  268. $value = null;
  269. if (!$this->maxItems) {
  270. // Track misses in non-LRU mode only
  271. $this->values[$key] = null;
  272. }
  273. } else {
  274. if ($this->maxItems) {
  275. // Move the item last in the storage
  276. $value = $this->values[$key];
  277. unset($this->values[$key]);
  278. $this->values[$key] = $value;
  279. }
  280. $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
  281. }
  282. unset($keys[$i]);
  283. yield $key => $f($key, $value, $isHit);
  284. }
  285. foreach ($keys as $key) {
  286. yield $key => $f($key, null, false);
  287. }
  288. }
  289. private function freeze($value, $key)
  290. {
  291. if (null === $value) {
  292. return 'N;';
  293. }
  294. if (\is_string($value)) {
  295. // Serialize strings if they could be confused with serialized objects or arrays
  296. if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
  297. return serialize($value);
  298. }
  299. } elseif (!is_scalar($value)) {
  300. try {
  301. $serialized = serialize($value);
  302. } catch (\Exception $e) {
  303. $type = get_debug_type($value);
  304. $message = sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage());
  305. CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
  306. return;
  307. }
  308. // Keep value serialized if it contains any objects or any internal references
  309. if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) {
  310. return $serialized;
  311. }
  312. }
  313. return $value;
  314. }
  315. private function unfreeze(string $key, bool &$isHit)
  316. {
  317. if ('N;' === $value = $this->values[$key]) {
  318. return null;
  319. }
  320. if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
  321. try {
  322. $value = unserialize($value);
  323. } catch (\Exception $e) {
  324. CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
  325. $value = false;
  326. }
  327. if (false === $value) {
  328. $value = null;
  329. $isHit = false;
  330. if (!$this->maxItems) {
  331. $this->values[$key] = null;
  332. }
  333. }
  334. }
  335. return $value;
  336. }
  337. }