vendor/symfony/cache/Adapter/PhpFilesAdapter.php line 98

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 Symfony\Component\Cache\Exception\CacheException;
  12. use Symfony\Component\Cache\Exception\InvalidArgumentException;
  13. use Symfony\Component\Cache\PruneableInterface;
  14. use Symfony\Component\Cache\Traits\FilesystemCommonTrait;
  15. use Symfony\Component\VarExporter\VarExporter;
  16. /**
  17.  * @author Piotr Stankowski <git@trakos.pl>
  18.  * @author Nicolas Grekas <p@tchwork.com>
  19.  * @author Rob Frawley 2nd <rmf@src.run>
  20.  */
  21. class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
  22. {
  23.     use FilesystemCommonTrait {
  24.         doClear as private doCommonClear;
  25.         doDelete as private doCommonDelete;
  26.     }
  27.     private $includeHandler;
  28.     private $appendOnly;
  29.     private $values = [];
  30.     private $files = [];
  31.     private static $startTime;
  32.     private static $valuesCache = [];
  33.     /**
  34.      * @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
  35.      *                    Doing so is encouraged because it fits perfectly OPcache's memory model.
  36.      *
  37.      * @throws CacheException if OPcache is not enabled
  38.      */
  39.     public function __construct(string $namespace ''int $defaultLifetime 0string $directory nullbool $appendOnly false)
  40.     {
  41.         $this->appendOnly $appendOnly;
  42.         self::$startTime self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
  43.         parent::__construct(''$defaultLifetime);
  44.         $this->init($namespace$directory);
  45.         $this->includeHandler = static function ($type$msg$file$line) {
  46.             throw new \ErrorException($msg0$type$file$line);
  47.         };
  48.     }
  49.     public static function isSupported()
  50.     {
  51.         self::$startTime self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
  52.         return \function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli''phpdbg'], true) || filter_var(ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN));
  53.     }
  54.     /**
  55.      * @return bool
  56.      */
  57.     public function prune()
  58.     {
  59.         $time time();
  60.         $pruned true;
  61.         $getExpiry true;
  62.         set_error_handler($this->includeHandler);
  63.         try {
  64.             foreach ($this->scanHashDir($this->directory) as $file) {
  65.                 try {
  66.                     if (\is_array($expiresAt = include $file)) {
  67.                         $expiresAt $expiresAt[0];
  68.                     }
  69.                 } catch (\ErrorException $e) {
  70.                     $expiresAt $time;
  71.                 }
  72.                 if ($time >= $expiresAt) {
  73.                     $pruned $this->doUnlink($file) && !file_exists($file) && $pruned;
  74.                 }
  75.             }
  76.         } finally {
  77.             restore_error_handler();
  78.         }
  79.         return $pruned;
  80.     }
  81.     /**
  82.      * {@inheritdoc}
  83.      */
  84.     protected function doFetch(array $ids)
  85.     {
  86.         if ($this->appendOnly) {
  87.             $now 0;
  88.             $missingIds = [];
  89.         } else {
  90.             $now time();
  91.             $missingIds $ids;
  92.             $ids = [];
  93.         }
  94.         $values = [];
  95.         begin:
  96.         $getExpiry false;
  97.         foreach ($ids as $id) {
  98.             if (null === $value $this->values[$id] ?? null) {
  99.                 $missingIds[] = $id;
  100.             } elseif ('N;' === $value) {
  101.                 $values[$id] = null;
  102.             } elseif (!\is_object($value)) {
  103.                 $values[$id] = $value;
  104.             } elseif (!$value instanceof LazyValue) {
  105.                 $values[$id] = $value();
  106.             } elseif (false === $values[$id] = include $value->file) {
  107.                 unset($values[$id], $this->values[$id]);
  108.                 $missingIds[] = $id;
  109.             }
  110.             if (!$this->appendOnly) {
  111.                 unset($this->values[$id]);
  112.             }
  113.         }
  114.         if (!$missingIds) {
  115.             return $values;
  116.         }
  117.         set_error_handler($this->includeHandler);
  118.         try {
  119.             $getExpiry true;
  120.             foreach ($missingIds as $k => $id) {
  121.                 try {
  122.                     $file $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
  123.                     if (isset(self::$valuesCache[$file])) {
  124.                         [$expiresAt$this->values[$id]] = self::$valuesCache[$file];
  125.                     } elseif (\is_array($expiresAt = include $file)) {
  126.                         if ($this->appendOnly) {
  127.                             self::$valuesCache[$file] = $expiresAt;
  128.                         }
  129.                         [$expiresAt$this->values[$id]] = $expiresAt;
  130.                     } elseif ($now $expiresAt) {
  131.                         $this->values[$id] = new LazyValue($file);
  132.                     }
  133.                     if ($now >= $expiresAt) {
  134.                         unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]);
  135.                     }
  136.                 } catch (\ErrorException $e) {
  137.                     unset($missingIds[$k]);
  138.                 }
  139.             }
  140.         } finally {
  141.             restore_error_handler();
  142.         }
  143.         $ids $missingIds;
  144.         $missingIds = [];
  145.         goto begin;
  146.     }
  147.     /**
  148.      * {@inheritdoc}
  149.      */
  150.     protected function doHave(string $id)
  151.     {
  152.         if ($this->appendOnly && isset($this->values[$id])) {
  153.             return true;
  154.         }
  155.         set_error_handler($this->includeHandler);
  156.         try {
  157.             $file $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
  158.             $getExpiry true;
  159.             if (isset(self::$valuesCache[$file])) {
  160.                 [$expiresAt$value] = self::$valuesCache[$file];
  161.             } elseif (\is_array($expiresAt = include $file)) {
  162.                 if ($this->appendOnly) {
  163.                     self::$valuesCache[$file] = $expiresAt;
  164.                 }
  165.                 [$expiresAt$value] = $expiresAt;
  166.             } elseif ($this->appendOnly) {
  167.                 $value = new LazyValue($file);
  168.             }
  169.         } catch (\ErrorException $e) {
  170.             return false;
  171.         } finally {
  172.             restore_error_handler();
  173.         }
  174.         if ($this->appendOnly) {
  175.             $now 0;
  176.             $this->values[$id] = $value;
  177.         } else {
  178.             $now time();
  179.         }
  180.         return $now $expiresAt;
  181.     }
  182.     /**
  183.      * {@inheritdoc}
  184.      */
  185.     protected function doSave(array $valuesint $lifetime)
  186.     {
  187.         $ok true;
  188.         $expiry $lifetime time() + $lifetime 'PHP_INT_MAX';
  189.         $allowCompile self::isSupported();
  190.         foreach ($values as $key => $value) {
  191.             unset($this->values[$key]);
  192.             $isStaticValue true;
  193.             if (null === $value) {
  194.                 $value "'N;'";
  195.             } elseif (\is_object($value) || \is_array($value)) {
  196.                 try {
  197.                     $value VarExporter::export($value$isStaticValue);
  198.                 } catch (\Exception $e) {
  199.                     throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.'$keyget_debug_type($value)), 0$e);
  200.                 }
  201.             } elseif (\is_string($value)) {
  202.                 // Wrap "N;" in a closure to not confuse it with an encoded `null`
  203.                 if ('N;' === $value) {
  204.                     $isStaticValue false;
  205.                 }
  206.                 $value var_export($valuetrue);
  207.             } elseif (!is_scalar($value)) {
  208.                 throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.'$keyget_debug_type($value)));
  209.             } else {
  210.                 $value var_export($valuetrue);
  211.             }
  212.             $encodedKey rawurlencode($key);
  213.             if ($isStaticValue) {
  214.                 $value "return [{$expiry}{$value}];";
  215.             } elseif ($this->appendOnly) {
  216.                 $value "return [{$expiry}, static function () { return {$value}; }];";
  217.             } else {
  218.                 // We cannot use a closure here because of https://bugs.php.net/76982
  219.                 $value str_replace('\Symfony\Component\VarExporter\Internal\\'''$value);
  220.                 $value "namespace Symfony\Component\VarExporter\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};";
  221.             }
  222.             $file $this->files[$key] = $this->getFile($keytrue);
  223.             // Since OPcache only compiles files older than the script execution start, set the file's mtime in the past
  224.             $ok $this->write($file"<?php //{$encodedKey}\n\n{$value}\n"self::$startTime 10) && $ok;
  225.             if ($allowCompile) {
  226.                 @opcache_invalidate($filetrue);
  227.                 @opcache_compile_file($file);
  228.             }
  229.             unset(self::$valuesCache[$file]);
  230.         }
  231.         if (!$ok && !is_writable($this->directory)) {
  232.             throw new CacheException(sprintf('Cache directory is not writable (%s).'$this->directory));
  233.         }
  234.         return $ok;
  235.     }
  236.     /**
  237.      * {@inheritdoc}
  238.      */
  239.     protected function doClear(string $namespace)
  240.     {
  241.         $this->values = [];
  242.         return $this->doCommonClear($namespace);
  243.     }
  244.     /**
  245.      * {@inheritdoc}
  246.      */
  247.     protected function doDelete(array $ids)
  248.     {
  249.         foreach ($ids as $id) {
  250.             unset($this->values[$id]);
  251.         }
  252.         return $this->doCommonDelete($ids);
  253.     }
  254.     protected function doUnlink(string $file)
  255.     {
  256.         unset(self::$valuesCache[$file]);
  257.         if (self::isSupported()) {
  258.             @opcache_invalidate($filetrue);
  259.         }
  260.         return @unlink($file);
  261.     }
  262.     private function getFileKey(string $file): string
  263.     {
  264.         if (!$h = @fopen($file'r')) {
  265.             return '';
  266.         }
  267.         $encodedKey substr(fgets($h), 8);
  268.         fclose($h);
  269.         return rawurldecode(rtrim($encodedKey));
  270.     }
  271. }
  272. /**
  273.  * @internal
  274.  */
  275. class LazyValue
  276. {
  277.     public $file;
  278.     public function __construct(string $file)
  279.     {
  280.         $this->file $file;
  281.     }
  282. }