FestinHegre/vendor/symfony/asset-mapper/Factory/MappedAssetFactory.php
2024-09-26 17:26:04 +02:00

135 lines
4.6 KiB
PHP

<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\AssetMapper\Factory;
use Symfony\Component\AssetMapper\AssetMapperCompiler;
use Symfony\Component\AssetMapper\Exception\CircularAssetsException;
use Symfony\Component\AssetMapper\Exception\RuntimeException;
use Symfony\Component\AssetMapper\MappedAsset;
use Symfony\Component\AssetMapper\Path\PublicAssetsPathResolverInterface;
use Symfony\Component\Filesystem\Filesystem;
/**
* Creates MappedAsset objects by reading their contents & passing it through compilers.
*/
class MappedAssetFactory implements MappedAssetFactoryInterface
{
private const PREDIGESTED_REGEX = '/-([0-9a-zA-Z]{7,128}\.digested)/';
private array $assetsCache = [];
private array $assetsBeingCreated = [];
public function __construct(
private readonly PublicAssetsPathResolverInterface $assetsPathResolver,
private readonly AssetMapperCompiler $compiler,
private readonly string $vendorDir,
) {
}
public function createMappedAsset(string $logicalPath, string $sourcePath): ?MappedAsset
{
if (isset($this->assetsBeingCreated[$logicalPath])) {
throw new CircularAssetsException($this->assetsCache[$logicalPath], sprintf('Circular reference detected while creating asset for "%s": "%s".', $logicalPath, implode(' -> ', $this->assetsBeingCreated).' -> '.$logicalPath));
}
$this->assetsBeingCreated[$logicalPath] = $logicalPath;
if (!isset($this->assetsCache[$logicalPath])) {
$isVendor = $this->isVendor($sourcePath);
$asset = new MappedAsset($logicalPath, $sourcePath, $this->assetsPathResolver->resolvePublicPath($logicalPath), isVendor: $isVendor);
$this->assetsCache[$logicalPath] = $asset;
$content = $this->compileContent($asset);
[$digest, $isPredigested] = $this->getDigest($asset, $content);
$asset = new MappedAsset(
$asset->logicalPath,
$asset->sourcePath,
$asset->publicPathWithoutDigest,
$this->getPublicPath($asset, $content),
$content,
$digest,
$isPredigested,
$isVendor,
$asset->getDependencies(),
$asset->getFileDependencies(),
$asset->getJavaScriptImports(),
);
$this->assetsCache[$logicalPath] = $asset;
}
unset($this->assetsBeingCreated[$logicalPath]);
return $this->assetsCache[$logicalPath];
}
/**
* Returns an array of "string digest" and "bool predigested".
*
* @return array{0: string, 1: bool}
*/
private function getDigest(MappedAsset $asset, ?string $content): array
{
// check for a pre-digested file
if (preg_match(self::PREDIGESTED_REGEX, $asset->logicalPath, $matches)) {
return [$matches[1], true];
}
// Use the compiled content if any
if (null !== $content) {
return [hash('xxh128', $content), false];
}
return [
hash_file('xxh128', $asset->sourcePath),
false,
];
}
private function compileContent(MappedAsset $asset): ?string
{
if (!is_file($asset->sourcePath)) {
throw new RuntimeException(sprintf('Asset source path "%s" could not be found.', $asset->sourcePath));
}
if (!$this->compiler->supports($asset)) {
return null;
}
$content = (new Filesystem())->readFile($asset->sourcePath);
$compiled = $this->compiler->compile($content, $asset);
return $compiled !== $content ? $compiled : null;
}
private function getPublicPath(MappedAsset $asset, ?string $content): ?string
{
[$digest, $isPredigested] = $this->getDigest($asset, $content);
if ($isPredigested) {
return $this->assetsPathResolver->resolvePublicPath($asset->logicalPath);
}
$digestedPath = preg_replace_callback('/\.(\w+)$/', fn ($matches) => "-{$digest}{$matches[0]}", $asset->logicalPath);
return $this->assetsPathResolver->resolvePublicPath($digestedPath);
}
private function isVendor(string $sourcePath): bool
{
$sourcePath = realpath($sourcePath);
$vendorDir = realpath($this->vendorDir);
return $sourcePath && str_starts_with($sourcePath, $vendorDir);
}
}