vendor/symfony/asset-mapper/AssetMapperDevServerSubscriber.php line 114

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\AssetMapper;
  11. use Psr\Cache\CacheItemPoolInterface;
  12. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\HttpKernel\Event\RequestEvent;
  15. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  16. use Symfony\Component\HttpKernel\KernelEvents;
  17. /**
  18.  * Functions like a controller that returns assets from the asset mapper.
  19.  *
  20.  * @experimental
  21.  *
  22.  * @author Ryan Weaver <ryan@symfonycasts.com>
  23.  */
  24. final class AssetMapperDevServerSubscriber implements EventSubscriberInterface
  25. {
  26.     // source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
  27.     private const EXTENSIONS_MAP = [
  28.         'aac' => 'audio/aac',
  29.         'abw' => 'application/x-abiword',
  30.         'arc' => 'application/x-freearc',
  31.         'avif' => 'image/avif',
  32.         'avi' => 'video/x-msvideo',
  33.         'azw' => 'application/vnd.amazon.ebook',
  34.         'bin' => 'application/octet-stream',
  35.         'bmp' => 'image/bmp',
  36.         'bz' => 'application/x-bzip',
  37.         'bz2' => 'application/x-bzip2',
  38.         'cda' => 'application/x-cdf',
  39.         'csh' => 'application/x-csh',
  40.         'css' => 'text/css',
  41.         'csv' => 'text/csv',
  42.         'doc' => 'application/msword',
  43.         'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  44.         'eot' => 'application/vnd.ms-fontobject',
  45.         'epub' => 'application/epub+zip',
  46.         'gz' => 'application/gzip',
  47.         'gif' => 'image/gif',
  48.         'htm' => 'text/html',
  49.         'html' => 'text/html',
  50.         'ico' => 'image/vnd.microsoft.icon',
  51.         'ics' => 'text/calendar',
  52.         'jar' => 'application/java-archive',
  53.         'jpeg' => 'image/jpeg',
  54.         'jpg' => 'image/jpeg',
  55.         'js' => 'text/javascript',
  56.         'json' => 'application/json',
  57.         'jsonld' => 'application/ld+json',
  58.         'mid' => 'audio/midi',
  59.         'midi' => 'audio/midi',
  60.         'mjs' => 'text/javascript',
  61.         'mp3' => 'audio/mpeg',
  62.         'mp4' => 'video/mp4',
  63.         'mpeg' => 'video/mpeg',
  64.         'mpkg' => 'application/vnd.apple.installer+xml',
  65.         'odp' => 'application/vnd.oasis.opendocument.presentation',
  66.         'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
  67.         'odt' => 'application/vnd.oasis.opendocument.text',
  68.         'oga' => 'audio/ogg',
  69.         'ogv' => 'video/ogg',
  70.         'ogx' => 'application/ogg',
  71.         'opus' => 'audio/opus',
  72.         'otf' => 'font/otf',
  73.         'png' => 'image/png',
  74.         'pdf' => 'application/pdf',
  75.         'php' => 'application/x-httpd-php',
  76.         'ppt' => 'application/vnd.ms-powerpoint',
  77.         'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  78.         'rar' => 'application/vnd.rar',
  79.         'rtf' => 'application/rtf',
  80.         'sh' => 'application/x-sh',
  81.         'svg' => 'image/svg+xml',
  82.         'tar' => 'application/x-tar',
  83.         'tif' => 'image/tiff',
  84.         'tiff' => 'image/tiff',
  85.         'ts' => 'video/mp2t',
  86.         'ttf' => 'font/ttf',
  87.         'txt' => 'text/plain',
  88.         'vsd' => 'application/vnd.visio',
  89.         'wav' => 'audio/wav',
  90.         'weba' => 'audio/webm',
  91.         'webm' => 'video/webm',
  92.         'webp' => 'image/webp',
  93.         'woff' => 'font/woff',
  94.         'woff2' => 'font/woff2',
  95.     ];
  96.     private readonly string $publicPrefix;
  97.     private array $extensionsMap;
  98.     public function __construct(
  99.         private readonly AssetMapperInterface $assetMapper,
  100.         string $publicPrefix '/assets/',
  101.         array $extensionsMap = [],
  102.         private readonly ?CacheItemPoolInterface $cacheMapCache null,
  103.     ) {
  104.         $this->publicPrefix rtrim($publicPrefix'/').'/';
  105.         $this->extensionsMap array_merge(self::EXTENSIONS_MAP$extensionsMap);
  106.     }
  107.     public function onKernelRequest(RequestEvent $event): void
  108.     {
  109.         if (!$event->isMainRequest()) {
  110.             return;
  111.         }
  112.         $pathInfo $event->getRequest()->getPathInfo();
  113.         if (!str_starts_with($pathInfo$this->publicPrefix)) {
  114.             return;
  115.         }
  116.         $asset $this->findAssetFromCache($pathInfo);
  117.         if (!$asset) {
  118.             throw new NotFoundHttpException(sprintf('Asset with public path "%s" not found.'$pathInfo));
  119.         }
  120.         $mediaType $this->getMediaType($asset->publicPath);
  121.         $response = (new Response(
  122.             $asset->content,
  123.             headers$mediaType ? ['Content-Type' => $mediaType] : [],
  124.         ))
  125.             ->setPublic()
  126.             ->setMaxAge(604800)
  127.             ->setImmutable()
  128.             ->setEtag($asset->digest)
  129.         ;
  130.         $event->setResponse($response);
  131.     }
  132.     public static function getSubscribedEvents(): array
  133.     {
  134.         return [
  135.             // priority higher than RouterListener
  136.             KernelEvents::REQUEST => [['onKernelRequest'35]],
  137.         ];
  138.     }
  139.     private function getMediaType(string $path): ?string
  140.     {
  141.         $extension pathinfo($path\PATHINFO_EXTENSION);
  142.         return $this->extensionsMap[$extension] ?? null;
  143.     }
  144.     private function findAssetFromCache(string $pathInfo): ?MappedAsset
  145.     {
  146.         $cachedAsset null;
  147.         if (null !== $this->cacheMapCache) {
  148.             $cachedAsset $this->cacheMapCache->getItem(hash('xxh128'$pathInfo));
  149.             $asset $cachedAsset->isHit() ? $this->assetMapper->getAsset($cachedAsset->get()) : null;
  150.             if (null !== $asset && $asset->publicPath === $pathInfo) {
  151.                 return $asset;
  152.             }
  153.         }
  154.         // we did not find a match
  155.         $asset null;
  156.         foreach ($this->assetMapper->allAssets() as $assetCandidate) {
  157.             if ($pathInfo === $assetCandidate->publicPath) {
  158.                 $asset $assetCandidate;
  159.                 break;
  160.             }
  161.         }
  162.         if (null === $asset) {
  163.             return null;
  164.         }
  165.         if (null !== $cachedAsset) {
  166.             $cachedAsset->set($asset->logicalPath);
  167.             $this->cacheMapCache->save($cachedAsset);
  168.         }
  169.         return $asset;
  170.     }
  171. }