vendor/symfony/asset/VersionStrategy/JsonManifestVersionStrategy.php line 43

  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\Asset\VersionStrategy;
  11. use Symfony\Component\Asset\Exception\AssetNotFoundException;
  12. use Symfony\Component\Asset\Exception\LogicException;
  13. use Symfony\Component\Asset\Exception\RuntimeException;
  14. use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
  15. use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
  16. use Symfony\Contracts\HttpClient\HttpClientInterface;
  17. /**
  18.  * Reads the versioned path of an asset from a JSON manifest file.
  19.  *
  20.  * For example, the manifest file might look like this:
  21.  *     {
  22.  *         "main.js": "main.abc123.js",
  23.  *         "css/styles.css": "css/styles.555abc.css"
  24.  *     }
  25.  *
  26.  * You could then ask for the version of "main.js" or "css/styles.css".
  27.  */
  28. class JsonManifestVersionStrategy implements VersionStrategyInterface
  29. {
  30.     private string $manifestPath;
  31.     private array $manifestData;
  32.     private ?HttpClientInterface $httpClient;
  33.     private bool $strictMode;
  34.     /**
  35.      * @param string $manifestPath Absolute path to the manifest file
  36.      * @param bool   $strictMode   Throws an exception for unknown paths
  37.      */
  38.     public function __construct(string $manifestPathHttpClientInterface $httpClient nullbool $strictMode false)
  39.     {
  40.         $this->manifestPath $manifestPath;
  41.         $this->httpClient $httpClient;
  42.         $this->strictMode $strictMode;
  43.         if (null === $this->httpClient && ($scheme parse_url($this->manifestPath\PHP_URL_SCHEME)) && str_starts_with($scheme'http')) {
  44.             throw new LogicException(sprintf('The "%s" class needs an HTTP client to use a remote manifest. Try running "composer require symfony/http-client".'self::class));
  45.         }
  46.     }
  47.     /**
  48.      * With a manifest, we don't really know or care about what
  49.      * the version is. Instead, this returns the path to the
  50.      * versioned file.
  51.      */
  52.     public function getVersion(string $path): string
  53.     {
  54.         return $this->applyVersion($path);
  55.     }
  56.     public function applyVersion(string $path): string
  57.     {
  58.         return $this->getManifestPath($path) ?: $path;
  59.     }
  60.     private function getManifestPath(string $path): ?string
  61.     {
  62.         if (!isset($this->manifestData)) {
  63.             if (null !== $this->httpClient && ($scheme parse_url($this->manifestPath\PHP_URL_SCHEME)) && str_starts_with($scheme'http')) {
  64.                 try {
  65.                     $this->manifestData $this->httpClient->request('GET'$this->manifestPath, [
  66.                         'headers' => ['accept' => 'application/json'],
  67.                     ])->toArray();
  68.                 } catch (DecodingExceptionInterface $e) {
  69.                     throw new RuntimeException(sprintf('Error parsing JSON from asset manifest URL "%s".'$this->manifestPath), 0$e);
  70.                 } catch (ClientExceptionInterface $e) {
  71.                     throw new RuntimeException(sprintf('Error loading JSON from asset manifest URL "%s".'$this->manifestPath), 0$e);
  72.                 }
  73.             } else {
  74.                 if (!is_file($this->manifestPath)) {
  75.                     throw new RuntimeException(sprintf('Asset manifest file "%s" does not exist. Did you forget to build the assets with npm or yarn?'$this->manifestPath));
  76.                 }
  77.                 try {
  78.                     $this->manifestData json_decode(file_get_contents($this->manifestPath), trueflags\JSON_THROW_ON_ERROR);
  79.                 } catch (\JsonException $e) {
  80.                     throw new RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s": '$this->manifestPath).$e->getMessage(), previous$e);
  81.                 }
  82.             }
  83.         }
  84.         if (isset($this->manifestData[$path])) {
  85.             return $this->manifestData[$path];
  86.         }
  87.         if ($this->strictMode) {
  88.             $message sprintf('Asset "%s" not found in manifest "%s".'$path$this->manifestPath);
  89.             $alternatives $this->findAlternatives($path$this->manifestData);
  90.             if (\count($alternatives) > 0) {
  91.                 $message .= sprintf(' Did you mean one of these? "%s".'implode('", "'$alternatives));
  92.             }
  93.             throw new AssetNotFoundException($message$alternatives);
  94.         }
  95.         return null;
  96.     }
  97.     private function findAlternatives(string $path, array $manifestData): array
  98.     {
  99.         $path strtolower($path);
  100.         $alternatives = [];
  101.         foreach ($manifestData as $key => $value) {
  102.             $lev levenshtein($pathstrtolower($key));
  103.             if ($lev <= \strlen($path) / || false !== stripos($key$path)) {
  104.                 $alternatives[$key] = isset($alternatives[$key]) ? min($lev$alternatives[$key]) : $lev;
  105.             }
  106.             $lev levenshtein($pathstrtolower($value));
  107.             if ($lev <= \strlen($path) / || false !== stripos($key$path)) {
  108.                 $alternatives[$key] = isset($alternatives[$key]) ? min($lev$alternatives[$key]) : $lev;
  109.             }
  110.         }
  111.         asort($alternatives);
  112.         return array_keys($alternatives);
  113.     }
  114. }