vendor/symfony/cache/Adapter/PhpArrayAdapter.php line 130

Open in your IDE?
  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\CachedValueInterface;
  18. use Symfony\Component\Cache\Traits\ContractsTrait;
  19. use Symfony\Component\Cache\Traits\ProxyTrait;
  20. use Symfony\Component\VarExporter\VarExporter;
  21. use Symfony\Contracts\Cache\CacheInterface;
  22. /**
  23.  * Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0.
  24.  * Warmed up items are read-only and run-time discovered items are cached using a fallback adapter.
  25.  *
  26.  * @author Titouan Galopin <galopintitouan@gmail.com>
  27.  * @author Nicolas Grekas <p@tchwork.com>
  28.  */
  29. class PhpArrayAdapter implements AdapterInterfaceCacheInterfacePruneableInterfaceResettableInterface
  30. {
  31.     use ContractsTrait;
  32.     use ProxyTrait;
  33.     private string $file;
  34.     private array $keys;
  35.     private array $values;
  36.     private static \Closure $createCacheItem;
  37.     private static array $valuesCache = [];
  38.     /**
  39.      * @param string           $file         The PHP file were values are cached
  40.      * @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit
  41.      */
  42.     public function __construct(string $fileAdapterInterface $fallbackPool)
  43.     {
  44.         $this->file $file;
  45.         $this->pool $fallbackPool;
  46.         self::$createCacheItem ??= \Closure::bind(
  47.             static function ($key$value$isHit) {
  48.                 $item = new CacheItem();
  49.                 $item->key $key;
  50.                 $item->value $value;
  51.                 $item->isHit $isHit;
  52.                 return $item;
  53.             },
  54.             null,
  55.             CacheItem::class
  56.         );
  57.     }
  58.     /**
  59.      * This adapter takes advantage of how PHP stores arrays in its latest versions.
  60.      *
  61.      * @param string                 $file         The PHP file were values are cached
  62.      * @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit
  63.      */
  64.     public static function create(string $fileCacheItemPoolInterface $fallbackPool): CacheItemPoolInterface
  65.     {
  66.         if (!$fallbackPool instanceof AdapterInterface) {
  67.             $fallbackPool = new ProxyAdapter($fallbackPool);
  68.         }
  69.         return new static($file$fallbackPool);
  70.     }
  71.     public function get(string $key, callable $callback, ?float $beta null, ?array &$metadata null): mixed
  72.     {
  73.         if (!isset($this->values)) {
  74.             $this->initialize();
  75.         }
  76.         if (!isset($this->keys[$key])) {
  77.             get_from_pool:
  78.             if ($this->pool instanceof CacheInterface) {
  79.                 return $this->pool->get($key$callback$beta$metadata);
  80.             }
  81.             return $this->doGet($this->pool$key$callback$beta$metadata);
  82.         }
  83.         $value $this->values[$this->keys[$key]];
  84.         if ('N;' === $value) {
  85.             return null;
  86.         }
  87.         if (!$value instanceof CachedValueInterface) {
  88.             return $value;
  89.         }
  90.         try {
  91.             return $value->getValue();
  92.         } catch (\Throwable) {
  93.             unset($this->keys[$key]);
  94.             goto get_from_pool;
  95.         }
  96.     }
  97.     public function getItem(mixed $key): CacheItem
  98.     {
  99.         if (!\is_string($key)) {
  100.             throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.'get_debug_type($key)));
  101.         }
  102.         if (!isset($this->values)) {
  103.             $this->initialize();
  104.         }
  105.         if (!isset($this->keys[$key])) {
  106.             return $this->pool->getItem($key);
  107.         }
  108.         $value $this->values[$this->keys[$key]];
  109.         $isHit true;
  110.         if ('N;' === $value) {
  111.             $value null;
  112.         } elseif ($value instanceof CachedValueInterface) {
  113.             try {
  114.                 $value $value->getValue();
  115.             } catch (\Throwable) {
  116.                 $value null;
  117.                 $isHit false;
  118.             }
  119.         }
  120.         return (self::$createCacheItem)($key$value$isHit);
  121.     }
  122.     public function getItems(array $keys = []): iterable
  123.     {
  124.         foreach ($keys as $key) {
  125.             if (!\is_string($key)) {
  126.                 throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.'get_debug_type($key)));
  127.             }
  128.         }
  129.         if (!isset($this->values)) {
  130.             $this->initialize();
  131.         }
  132.         return $this->generateItems($keys);
  133.     }
  134.     public function hasItem(mixed $key): bool
  135.     {
  136.         if (!\is_string($key)) {
  137.             throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.'get_debug_type($key)));
  138.         }
  139.         if (!isset($this->values)) {
  140.             $this->initialize();
  141.         }
  142.         return isset($this->keys[$key]) || $this->pool->hasItem($key);
  143.     }
  144.     public function deleteItem(mixed $key): bool
  145.     {
  146.         if (!\is_string($key)) {
  147.             throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.'get_debug_type($key)));
  148.         }
  149.         if (!isset($this->values)) {
  150.             $this->initialize();
  151.         }
  152.         return !isset($this->keys[$key]) && $this->pool->deleteItem($key);
  153.     }
  154.     public function deleteItems(array $keys): bool
  155.     {
  156.         $deleted true;
  157.         $fallbackKeys = [];
  158.         foreach ($keys as $key) {
  159.             if (!\is_string($key)) {
  160.                 throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.'get_debug_type($key)));
  161.             }
  162.             if (isset($this->keys[$key])) {
  163.                 $deleted false;
  164.             } else {
  165.                 $fallbackKeys[] = $key;
  166.             }
  167.         }
  168.         if (!isset($this->values)) {
  169.             $this->initialize();
  170.         }
  171.         if ($fallbackKeys) {
  172.             $deleted $this->pool->deleteItems($fallbackKeys) && $deleted;
  173.         }
  174.         return $deleted;
  175.     }
  176.     public function save(CacheItemInterface $item): bool
  177.     {
  178.         if (!isset($this->values)) {
  179.             $this->initialize();
  180.         }
  181.         return !isset($this->keys[$item->getKey()]) && $this->pool->save($item);
  182.     }
  183.     public function saveDeferred(CacheItemInterface $item): bool
  184.     {
  185.         if (!isset($this->values)) {
  186.             $this->initialize();
  187.         }
  188.         return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
  189.     }
  190.     public function commit(): bool
  191.     {
  192.         return $this->pool->commit();
  193.     }
  194.     public function clear(string $prefix ''): bool
  195.     {
  196.         $this->keys $this->values = [];
  197.         $cleared = @unlink($this->file) || !file_exists($this->file);
  198.         unset(self::$valuesCache[$this->file]);
  199.         if ($this->pool instanceof AdapterInterface) {
  200.             return $this->pool->clear($prefix) && $cleared;
  201.         }
  202.         return $this->pool->clear() && $cleared;
  203.     }
  204.     /**
  205.      * Store an array of cached values.
  206.      *
  207.      * @param array $values The cached values
  208.      *
  209.      * @return string[] A list of classes to preload on PHP 7.4+
  210.      */
  211.     public function warmUp(array $values): array
  212.     {
  213.         if (file_exists($this->file)) {
  214.             if (!is_file($this->file)) {
  215.                 throw new InvalidArgumentException(\sprintf('Cache path exists and is not a file: "%s".'$this->file));
  216.             }
  217.             if (!is_writable($this->file)) {
  218.                 throw new InvalidArgumentException(\sprintf('Cache file is not writable: "%s".'$this->file));
  219.             }
  220.         } else {
  221.             $directory \dirname($this->file);
  222.             if (!is_dir($directory) && !@mkdir($directory0777true)) {
  223.                 throw new InvalidArgumentException(\sprintf('Cache directory does not exist and cannot be created: "%s".'$directory));
  224.             }
  225.             if (!is_writable($directory)) {
  226.                 throw new InvalidArgumentException(\sprintf('Cache directory is not writable: "%s".'$directory));
  227.             }
  228.         }
  229.         $preload = [];
  230.         $dumpedValues '';
  231.         $dumpedMap = [];
  232.         $dump = <<<'EOF'
  233. <?php
  234. // This file has been auto-generated by the Symfony Cache Component.
  235. return [[
  236. EOF;
  237.         foreach ($values as $key => $value) {
  238.             CacheItem::validateKey(\is_int($key) ? (string) $key $key);
  239.             $isStaticValue true;
  240.             if (null === $value) {
  241.                 $value "'N;'";
  242.             } elseif (\is_object($value) || \is_array($value)) {
  243.                 try {
  244.                     $value VarExporter::export($value$isStaticValue$preload);
  245.                 } catch (\Exception $e) {
  246.                     throw new InvalidArgumentException(\sprintf('Cache key "%s" has non-serializable "%s" value.'$keyget_debug_type($value)), 0$e);
  247.                 }
  248.             } elseif (\is_string($value)) {
  249.                 // Wrap "N;" in a closure to not confuse it with an encoded `null`
  250.                 if ('N;' === $value) {
  251.                     $isStaticValue false;
  252.                 }
  253.                 $value var_export($valuetrue);
  254.             } elseif (!\is_scalar($value)) {
  255.                 throw new InvalidArgumentException(\sprintf('Cache key "%s" has non-serializable "%s" value.'$keyget_debug_type($value)));
  256.             } else {
  257.                 $value var_export($valuetrue);
  258.             }
  259.             if (!$isStaticValue) {
  260.                 $value 'new class() implements \\'.CachedValueInterface::class." { public function getValue(): mixed { return {$value}; } }";
  261.             }
  262.             $hash hash('xxh128'$value);
  263.             if (null === $id $dumpedMap[$hash] ?? null) {
  264.                 $id $dumpedMap[$hash] = \count($dumpedMap);
  265.                 $dumpedValues .= "{$id} => {$value},\n";
  266.             }
  267.             $dump .= var_export($keytrue)." => {$id},\n";
  268.         }
  269.         $dump .= "\n], [\n\n{$dumpedValues}\n]];\n";
  270.         $tmpFile uniqid($this->filetrue);
  271.         file_put_contents($tmpFile$dump);
  272.         @chmod($tmpFile0666 & ~umask());
  273.         unset($serialized$value$dump);
  274.         @rename($tmpFile$this->file);
  275.         unset(self::$valuesCache[$this->file]);
  276.         $this->initialize();
  277.         return $preload;
  278.     }
  279.     /**
  280.      * Load the cache file.
  281.      */
  282.     private function initialize(): void
  283.     {
  284.         if (isset(self::$valuesCache[$this->file])) {
  285.             $values self::$valuesCache[$this->file];
  286.         } elseif (!is_file($this->file)) {
  287.             $this->keys $this->values = [];
  288.             return;
  289.         } else {
  290.             $values self::$valuesCache[$this->file] = (include $this->file) ?: [[], []];
  291.         }
  292.         if (!== \count($values) || !isset($values[0], $values[1])) {
  293.             $this->keys $this->values = [];
  294.         } else {
  295.             [$this->keys$this->values] = $values;
  296.         }
  297.     }
  298.     private function generateItems(array $keys): \Generator
  299.     {
  300.         $f self::$createCacheItem;
  301.         $fallbackKeys = [];
  302.         foreach ($keys as $key) {
  303.             if (isset($this->keys[$key])) {
  304.                 $value $this->values[$this->keys[$key]];
  305.                 if ('N;' === $value) {
  306.                     yield $key => $f($keynulltrue);
  307.                 } elseif ($value instanceof CachedValueInterface) {
  308.                     try {
  309.                         yield $key => $f($key$value->getValue(), true);
  310.                     } catch (\Throwable) {
  311.                         yield $key => $f($keynullfalse);
  312.                     }
  313.                 } else {
  314.                     yield $key => $f($key$valuetrue);
  315.                 }
  316.             } else {
  317.                 $fallbackKeys[] = $key;
  318.             }
  319.         }
  320.         if ($fallbackKeys) {
  321.             yield from $this->pool->getItems($fallbackKeys);
  322.         }
  323.     }
  324. }