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 <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\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 <galopintitouan@gmail.com>
  26.  * @author Nicolas Grekas <p@tchwork.com>
  27.  */
  28. class PhpArrayAdapter implements AdapterInterfaceCacheInterfacePruneableInterfaceResettableInterface
  29. {
  30.     use ContractsTrait;
  31.     use ProxyTrait;
  32.     private $file;
  33.     private $keys;
  34.     private $values;
  35.     private $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.         $this->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.         $f $this->createCacheItem;
  128.         return $f($key$value$isHit);
  129.     }
  130.     /**
  131.      * {@inheritdoc}
  132.      */
  133.     public function getItems(array $keys = [])
  134.     {
  135.         foreach ($keys as $key) {
  136.             if (!\is_string($key)) {
  137.                 throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.'get_debug_type($key)));
  138.             }
  139.         }
  140.         if (null === $this->values) {
  141.             $this->initialize();
  142.         }
  143.         return $this->generateItems($keys);
  144.     }
  145.     /**
  146.      * {@inheritdoc}
  147.      *
  148.      * @return bool
  149.      */
  150.     public function hasItem($key)
  151.     {
  152.         if (!\is_string($key)) {
  153.             throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.'get_debug_type($key)));
  154.         }
  155.         if (null === $this->values) {
  156.             $this->initialize();
  157.         }
  158.         return isset($this->keys[$key]) || $this->pool->hasItem($key);
  159.     }
  160.     /**
  161.      * {@inheritdoc}
  162.      *
  163.      * @return bool
  164.      */
  165.     public function deleteItem($key)
  166.     {
  167.         if (!\is_string($key)) {
  168.             throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.'get_debug_type($key)));
  169.         }
  170.         if (null === $this->values) {
  171.             $this->initialize();
  172.         }
  173.         return !isset($this->keys[$key]) && $this->pool->deleteItem($key);
  174.     }
  175.     /**
  176.      * {@inheritdoc}
  177.      *
  178.      * @return bool
  179.      */
  180.     public function deleteItems(array $keys)
  181.     {
  182.         $deleted true;
  183.         $fallbackKeys = [];
  184.         foreach ($keys as $key) {
  185.             if (!\is_string($key)) {
  186.                 throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.'get_debug_type($key)));
  187.             }
  188.             if (isset($this->keys[$key])) {
  189.                 $deleted false;
  190.             } else {
  191.                 $fallbackKeys[] = $key;
  192.             }
  193.         }
  194.         if (null === $this->values) {
  195.             $this->initialize();
  196.         }
  197.         if ($fallbackKeys) {
  198.             $deleted $this->pool->deleteItems($fallbackKeys) && $deleted;
  199.         }
  200.         return $deleted;
  201.     }
  202.     /**
  203.      * {@inheritdoc}
  204.      *
  205.      * @return bool
  206.      */
  207.     public function save(CacheItemInterface $item)
  208.     {
  209.         if (null === $this->values) {
  210.             $this->initialize();
  211.         }
  212.         return !isset($this->keys[$item->getKey()]) && $this->pool->save($item);
  213.     }
  214.     /**
  215.      * {@inheritdoc}
  216.      *
  217.      * @return bool
  218.      */
  219.     public function saveDeferred(CacheItemInterface $item)
  220.     {
  221.         if (null === $this->values) {
  222.             $this->initialize();
  223.         }
  224.         return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
  225.     }
  226.     /**
  227.      * {@inheritdoc}
  228.      *
  229.      * @return bool
  230.      */
  231.     public function commit()
  232.     {
  233.         return $this->pool->commit();
  234.     }
  235.     /**
  236.      * {@inheritdoc}
  237.      *
  238.      * @return bool
  239.      */
  240.     public function clear(string $prefix '')
  241.     {
  242.         $this->keys $this->values = [];
  243.         $cleared = @unlink($this->file) || !file_exists($this->file);
  244.         unset(self::$valuesCache[$this->file]);
  245.         if ($this->pool instanceof AdapterInterface) {
  246.             return $this->pool->clear($prefix) && $cleared;
  247.         }
  248.         return $this->pool->clear() && $cleared;
  249.     }
  250.     /**
  251.      * Store an array of cached values.
  252.      *
  253.      * @param array $values The cached values
  254.      *
  255.      * @return string[] A list of classes to preload on PHP 7.4+
  256.      */
  257.     public function warmUp(array $values)
  258.     {
  259.         if (file_exists($this->file)) {
  260.             if (!is_file($this->file)) {
  261.                 throw new InvalidArgumentException(sprintf('Cache path exists and is not a file: "%s".'$this->file));
  262.             }
  263.             if (!is_writable($this->file)) {
  264.                 throw new InvalidArgumentException(sprintf('Cache file is not writable: "%s".'$this->file));
  265.             }
  266.         } else {
  267.             $directory \dirname($this->file);
  268.             if (!is_dir($directory) && !@mkdir($directory0777true)) {
  269.                 throw new InvalidArgumentException(sprintf('Cache directory does not exist and cannot be created: "%s".'$directory));
  270.             }
  271.             if (!is_writable($directory)) {
  272.                 throw new InvalidArgumentException(sprintf('Cache directory is not writable: "%s".'$directory));
  273.             }
  274.         }
  275.         $preload = [];
  276.         $dumpedValues '';
  277.         $dumpedMap = [];
  278.         $dump = <<<'EOF'
  279. <?php
  280. // This file has been auto-generated by the Symfony Cache Component.
  281. return [[
  282. EOF;
  283.         foreach ($values as $key => $value) {
  284.             CacheItem::validateKey(\is_int($key) ? (string) $key $key);
  285.             $isStaticValue true;
  286.             if (null === $value) {
  287.                 $value "'N;'";
  288.             } elseif (\is_object($value) || \is_array($value)) {
  289.                 try {
  290.                     $value VarExporter::export($value$isStaticValue$preload);
  291.                 } catch (\Exception $e) {
  292.                     throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.'$keyget_debug_type($value)), 0$e);
  293.                 }
  294.             } elseif (\is_string($value)) {
  295.                 // Wrap "N;" in a closure to not confuse it with an encoded `null`
  296.                 if ('N;' === $value) {
  297.                     $isStaticValue false;
  298.                 }
  299.                 $value var_export($valuetrue);
  300.             } elseif (!is_scalar($value)) {
  301.                 throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.'$keyget_debug_type($value)));
  302.             } else {
  303.                 $value var_export($valuetrue);
  304.             }
  305.             if (!$isStaticValue) {
  306.                 $value str_replace("\n""\n    "$value);
  307.                 $value "static function () {\n    return {$value};\n}";
  308.             }
  309.             $hash hash('md5'$value);
  310.             if (null === $id $dumpedMap[$hash] ?? null) {
  311.                 $id $dumpedMap[$hash] = \count($dumpedMap);
  312.                 $dumpedValues .= "{$id} => {$value},\n";
  313.             }
  314.             $dump .= var_export($keytrue)." => {$id},\n";
  315.         }
  316.         $dump .= "\n], [\n\n{$dumpedValues}\n]];\n";
  317.         $tmpFile uniqid($this->filetrue);
  318.         file_put_contents($tmpFile$dump);
  319.         @chmod($tmpFile0666 & ~umask());
  320.         unset($serialized$value$dump);
  321.         @rename($tmpFile$this->file);
  322.         unset(self::$valuesCache[$this->file]);
  323.         $this->initialize();
  324.         return $preload;
  325.     }
  326.     /**
  327.      * Load the cache file.
  328.      */
  329.     private function initialize()
  330.     {
  331.         if (isset(self::$valuesCache[$this->file])) {
  332.             $values self::$valuesCache[$this->file];
  333.         } elseif (!is_file($this->file)) {
  334.             $this->keys $this->values = [];
  335.             return;
  336.         } else {
  337.             $values self::$valuesCache[$this->file] = (include $this->file) ?: [[], []];
  338.         }
  339.         if (!== \count($values) || !isset($values[0], $values[1])) {
  340.             $this->keys $this->values = [];
  341.         } else {
  342.             [$this->keys$this->values] = $values;
  343.         }
  344.     }
  345.     private function generateItems(array $keys): \Generator
  346.     {
  347.         $f $this->createCacheItem;
  348.         $fallbackKeys = [];
  349.         foreach ($keys as $key) {
  350.             if (isset($this->keys[$key])) {
  351.                 $value $this->values[$this->keys[$key]];
  352.                 if ('N;' === $value) {
  353.                     yield $key => $f($keynulltrue);
  354.                 } elseif ($value instanceof \Closure) {
  355.                     try {
  356.                         yield $key => $f($key$value(), true);
  357.                     } catch (\Throwable $e) {
  358.                         yield $key => $f($keynullfalse);
  359.                     }
  360.                 } else {
  361.                     yield $key => $f($key$valuetrue);
  362.                 }
  363.             } else {
  364.                 $fallbackKeys[] = $key;
  365.             }
  366.         }
  367.         if ($fallbackKeys) {
  368.             yield from $this->pool->getItems($fallbackKeys);
  369.         }
  370.     }
  371. }