vendor/symfony/config/ResourceCheckerConfigCache.php line 90

  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\Config;
  11. use Symfony\Component\Config\Resource\ResourceInterface;
  12. use Symfony\Component\Filesystem\Exception\IOException;
  13. use Symfony\Component\Filesystem\Filesystem;
  14. /**
  15.  * ResourceCheckerConfigCache uses instances of ResourceCheckerInterface
  16.  * to check whether cached data is still fresh.
  17.  *
  18.  * @author Matthias Pigulla <mp@webfactory.de>
  19.  */
  20. class ResourceCheckerConfigCache implements ConfigCacheInterface
  21. {
  22.     private string $file;
  23.     /**
  24.      * @var iterable<mixed, ResourceCheckerInterface>
  25.      */
  26.     private iterable $resourceCheckers;
  27.     /**
  28.      * @param string                                    $file             The absolute cache path
  29.      * @param iterable<mixed, ResourceCheckerInterface> $resourceCheckers The ResourceCheckers to use for the freshness check
  30.      */
  31.     public function __construct(string $fileiterable $resourceCheckers = [])
  32.     {
  33.         $this->file $file;
  34.         $this->resourceCheckers $resourceCheckers;
  35.     }
  36.     public function getPath(): string
  37.     {
  38.         return $this->file;
  39.     }
  40.     /**
  41.      * Checks if the cache is still fresh.
  42.      *
  43.      * This implementation will make a decision solely based on the ResourceCheckers
  44.      * passed in the constructor.
  45.      *
  46.      * The first ResourceChecker that supports a given resource is considered authoritative.
  47.      * Resources with no matching ResourceChecker will silently be ignored and considered fresh.
  48.      */
  49.     public function isFresh(): bool
  50.     {
  51.         if (!is_file($this->file)) {
  52.             return false;
  53.         }
  54.         if ($this->resourceCheckers instanceof \Traversable && !$this->resourceCheckers instanceof \Countable) {
  55.             $this->resourceCheckers iterator_to_array($this->resourceCheckers);
  56.         }
  57.         if (!\count($this->resourceCheckers)) {
  58.             return true// shortcut - if we don't have any checkers we don't need to bother with the meta file at all
  59.         }
  60.         $metadata $this->getMetaFile();
  61.         if (!is_file($metadata)) {
  62.             return false;
  63.         }
  64.         $meta $this->safelyUnserialize($metadata);
  65.         if (false === $meta) {
  66.             return false;
  67.         }
  68.         $time filemtime($this->file);
  69.         foreach ($meta as $resource) {
  70.             foreach ($this->resourceCheckers as $checker) {
  71.                 if (!$checker->supports($resource)) {
  72.                     continue; // next checker
  73.                 }
  74.                 if ($checker->isFresh($resource$time)) {
  75.                     break; // no need to further check this resource
  76.                 }
  77.                 return false// cache is stale
  78.             }
  79.             // no suitable checker found, ignore this resource
  80.         }
  81.         return true;
  82.     }
  83.     /**
  84.      * Writes cache.
  85.      *
  86.      * @param string              $content  The content to write in the cache
  87.      * @param ResourceInterface[] $metadata An array of metadata
  88.      *
  89.      * @throws \RuntimeException When cache file can't be written
  90.      */
  91.     public function write(string $content, array $metadata null)
  92.     {
  93.         $mode 0666;
  94.         $umask umask();
  95.         $filesystem = new Filesystem();
  96.         $filesystem->dumpFile($this->file$content);
  97.         try {
  98.             $filesystem->chmod($this->file$mode$umask);
  99.         } catch (IOException) {
  100.             // discard chmod failure (some filesystem may not support it)
  101.         }
  102.         if (null !== $metadata) {
  103.             $filesystem->dumpFile($this->getMetaFile(), serialize($metadata));
  104.             try {
  105.                 $filesystem->chmod($this->getMetaFile(), $mode$umask);
  106.             } catch (IOException) {
  107.                 // discard chmod failure (some filesystem may not support it)
  108.             }
  109.         }
  110.         if (\function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL)) {
  111.             @opcache_invalidate($this->filetrue);
  112.         }
  113.     }
  114.     /**
  115.      * Gets the meta file path.
  116.      */
  117.     private function getMetaFile(): string
  118.     {
  119.         return $this->file.'.meta';
  120.     }
  121.     private function safelyUnserialize(string $file)
  122.     {
  123.         $meta false;
  124.         $content file_get_contents($file);
  125.         $signalingException = new \UnexpectedValueException();
  126.         $prevUnserializeHandler ini_set('unserialize_callback_func'self::class.'::handleUnserializeCallback');
  127.         $prevErrorHandler set_error_handler(function ($type$msg$file$line$context = []) use (&$prevErrorHandler$signalingException) {
  128.             if (__FILE__ === $file) {
  129.                 throw $signalingException;
  130.             }
  131.             return $prevErrorHandler $prevErrorHandler($type$msg$file$line$context) : false;
  132.         });
  133.         try {
  134.             $meta unserialize($content);
  135.         } catch (\Throwable $e) {
  136.             if ($e !== $signalingException) {
  137.                 throw $e;
  138.             }
  139.         } finally {
  140.             restore_error_handler();
  141.             ini_set('unserialize_callback_func'$prevUnserializeHandler);
  142.         }
  143.         return $meta;
  144.     }
  145.     /**
  146.      * @internal
  147.      */
  148.     public static function handleUnserializeCallback(string $class)
  149.     {
  150.         trigger_error('Class not found: '.$class);
  151.     }
  152. }