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

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