ArrayTrait.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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\Traits;
  11. use Psr\Log\LoggerAwareTrait;
  12. use Symfony\Component\Cache\CacheItem;
  13. /**
  14. * @author Nicolas Grekas <p@tchwork.com>
  15. *
  16. * @internal
  17. */
  18. trait ArrayTrait
  19. {
  20. use LoggerAwareTrait;
  21. private $storeSerialized;
  22. private $values = [];
  23. private $expiries = [];
  24. /**
  25. * Returns all cached values, with cache miss as null.
  26. *
  27. * @return array
  28. */
  29. public function getValues()
  30. {
  31. if (!$this->storeSerialized) {
  32. return $this->values;
  33. }
  34. $values = $this->values;
  35. foreach ($values as $k => $v) {
  36. if (null === $v || 'N;' === $v) {
  37. continue;
  38. }
  39. if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) {
  40. $values[$k] = serialize($v);
  41. }
  42. }
  43. return $values;
  44. }
  45. /**
  46. * {@inheritdoc}
  47. *
  48. * @return bool
  49. */
  50. public function hasItem($key)
  51. {
  52. if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) {
  53. return true;
  54. }
  55. CacheItem::validateKey($key);
  56. return isset($this->expiries[$key]) && !$this->deleteItem($key);
  57. }
  58. /**
  59. * {@inheritdoc}
  60. *
  61. * @param string $prefix
  62. *
  63. * @return bool
  64. */
  65. public function clear(/*string $prefix = ''*/)
  66. {
  67. $prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
  68. if ('' !== $prefix) {
  69. foreach ($this->values as $key => $value) {
  70. if (str_starts_with($key, $prefix)) {
  71. unset($this->values[$key], $this->expiries[$key]);
  72. }
  73. }
  74. } else {
  75. $this->values = $this->expiries = [];
  76. }
  77. return true;
  78. }
  79. /**
  80. * {@inheritdoc}
  81. *
  82. * @return bool
  83. */
  84. public function deleteItem($key)
  85. {
  86. if (!\is_string($key) || !isset($this->expiries[$key])) {
  87. CacheItem::validateKey($key);
  88. }
  89. unset($this->values[$key], $this->expiries[$key]);
  90. return true;
  91. }
  92. /**
  93. * {@inheritdoc}
  94. */
  95. public function reset()
  96. {
  97. $this->clear();
  98. }
  99. private function generateItems(array $keys, float $now, callable $f): iterable
  100. {
  101. foreach ($keys as $i => $key) {
  102. if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) {
  103. $this->values[$key] = $value = null;
  104. } else {
  105. $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
  106. }
  107. unset($keys[$i]);
  108. yield $key => $f($key, $value, $isHit);
  109. }
  110. foreach ($keys as $key) {
  111. yield $key => $f($key, null, false);
  112. }
  113. }
  114. private function freeze($value, $key)
  115. {
  116. if (null === $value) {
  117. return 'N;';
  118. }
  119. if (\is_string($value)) {
  120. // Serialize strings if they could be confused with serialized objects or arrays
  121. if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
  122. return serialize($value);
  123. }
  124. } elseif (!is_scalar($value)) {
  125. try {
  126. $serialized = serialize($value);
  127. } catch (\Exception $e) {
  128. unset($this->values[$key]);
  129. $type = \is_object($value) ? \get_class($value) : \gettype($value);
  130. $message = sprintf('Failed to save key "{key}" of type %s: ', $type).$e->getMessage();
  131. CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
  132. return null;
  133. }
  134. // Keep value serialized if it contains any objects or any internal references
  135. if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) {
  136. return $serialized;
  137. }
  138. }
  139. return $value;
  140. }
  141. private function unfreeze(string $key, bool &$isHit)
  142. {
  143. if ('N;' === $value = $this->values[$key]) {
  144. return null;
  145. }
  146. if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
  147. try {
  148. $value = unserialize($value);
  149. } catch (\Exception $e) {
  150. CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
  151. $value = false;
  152. }
  153. if (false === $value) {
  154. $this->values[$key] = $value = null;
  155. $isHit = false;
  156. }
  157. }
  158. return $value;
  159. }
  160. }