vendor/shopware/storefront/Framework/Routing/NotFound/NotFoundSubscriber.php line 66

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Storefront\Framework\Routing\NotFound;
  3. use Shopware\Core\Framework\Adapter\Cache\AbstractCacheTracer;
  4. use Shopware\Core\Framework\Adapter\Cache\CacheInvalidator;
  5. use Shopware\Core\Framework\DataAbstractionLayer\Cache\EntityCacheKeyGenerator;
  6. use Shopware\Core\Framework\Log\Package;
  7. use Shopware\Core\Framework\Uuid\Uuid;
  8. use Shopware\Core\PlatformRequest;
  9. use Shopware\Core\SalesChannelRequest;
  10. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceInterface;
  11. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceParameters;
  12. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  13. use Shopware\Core\System\SystemConfig\Event\SystemConfigChangedEvent;
  14. use Shopware\Storefront\Controller\ErrorController;
  15. use Shopware\Storefront\Framework\Routing\StorefrontResponse;
  16. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  17. use Symfony\Component\HttpFoundation\Request;
  18. use Symfony\Component\HttpFoundation\RequestStack;
  19. use Symfony\Component\HttpFoundation\Response;
  20. use Symfony\Component\HttpKernel\Event\ExceptionEvent;
  21. use Symfony\Component\HttpKernel\Exception\HttpException;
  22. use Symfony\Component\HttpKernel\KernelEvents;
  23. use Symfony\Contracts\Cache\CacheInterface;
  24. use Symfony\Contracts\Cache\ItemInterface;
  25. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  26. /**
  27.  * @internal
  28.  */
  29. #[Package('storefront')]
  30. class NotFoundSubscriber implements EventSubscriberInterface
  31. {
  32.     private const ALL_TAG 'error-page';
  33.     private const SYSTEM_CONFIG_KEY 'core.basicInformation.http404Page';
  34.     /**
  35.      * @internal
  36.      *
  37.      * @param AbstractCacheTracer<Response> $cacheTracer
  38.      */
  39.     public function __construct(
  40.         private readonly ErrorController $controller,
  41.         private readonly RequestStack $requestStack,
  42.         private readonly SalesChannelContextServiceInterface $contextService,
  43.         private bool $kernelDebug,
  44.         private readonly CacheInterface $cache,
  45.         private readonly AbstractCacheTracer $cacheTracer,
  46.         private readonly EntityCacheKeyGenerator $generator,
  47.         private readonly CacheInvalidator $cacheInvalidator,
  48.         private readonly EventDispatcherInterface $eventDispatcher
  49.     ) {
  50.     }
  51.     public static function getSubscribedEvents(): array
  52.     {
  53.         return [
  54.             KernelEvents::EXCEPTION => [
  55.                 ['onError', -100],
  56.             ],
  57.             SystemConfigChangedEvent::class => 'onSystemConfigChanged',
  58.         ];
  59.     }
  60.     public function onError(ExceptionEvent $event): void
  61.     {
  62.         $request $event->getRequest();
  63.         if ($this->kernelDebug) {
  64.             return;
  65.         }
  66.         $event->stopPropagation();
  67.         $salesChannelId $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID'');
  68.         $domainId $request->attributes->get(SalesChannelRequest::ATTRIBUTE_DOMAIN_ID'');
  69.         $languageId $request->attributes->get(PlatformRequest::HEADER_LANGUAGE_ID'');
  70.         if (!$request->attributes->has(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT)) {
  71.             // When no sales-channel context is resolved, we need to resolve it now.
  72.             $this->setSalesChannelContext($request);
  73.         }
  74.         $is404StatusCode $event->getThrowable() instanceof HttpException && $event->getThrowable()->getStatusCode() === Response::HTTP_NOT_FOUND;
  75.         // If the exception is not a 404 status code, we don't need to cache it.
  76.         if (!$is404StatusCode) {
  77.             $event->setResponse($this->controller->error(
  78.                 $event->getThrowable(),
  79.                 $request,
  80.                 $event->getRequest()->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT)
  81.             ));
  82.             return;
  83.         }
  84.         /** @var SalesChannelContext $context */
  85.         $context $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
  86.         $name self::buildName($salesChannelId$domainId$languageId);
  87.         $key $this->generateKey($salesChannelId$domainId$languageId$request$context);
  88.         $response $this->cache->get($key, function (ItemInterface $item) use ($event$name$context) {
  89.             /** @var StorefrontResponse $response */
  90.             $response $this->cacheTracer->trace($name, function () use ($event) {
  91.                 /** @var Request $request */
  92.                 $request $this->requestStack->getMainRequest();
  93.                 return $this->controller->error(
  94.                     $event->getThrowable(),
  95.                     $request,
  96.                     $event->getRequest()->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT)
  97.                 );
  98.             });
  99.             $item->tag($this->generateTags($name$event->getRequest(), $context));
  100.             $response->setData([]);
  101.             $response->setContext(null);
  102.             return $response;
  103.         });
  104.         $event->setResponse($response);
  105.     }
  106.     public function onSystemConfigChanged(SystemConfigChangedEvent $event): void
  107.     {
  108.         if ($event->getKey() !== self::SYSTEM_CONFIG_KEY) {
  109.             return;
  110.         }
  111.         $this->cacheInvalidator->invalidate([self::ALL_TAG]);
  112.     }
  113.     private static function buildName(string $salesChannelIdstring $domainIdstring $languageId): string
  114.     {
  115.         return 'error-page-' $salesChannelId $domainId $languageId;
  116.     }
  117.     private function generateKey(string $salesChannelIdstring $domainIdstring $languageIdRequest $requestSalesChannelContext $context): string
  118.     {
  119.         $key self::buildName($salesChannelId$domainId$languageId) . md5($this->generator->getSalesChannelContextHash($context));
  120.         $event = new NotFoundPageCacheKeyEvent($key$request$context);
  121.         $this->eventDispatcher->dispatch($event);
  122.         return $event->getKey();
  123.     }
  124.     /**
  125.      * @return array<string>
  126.      */
  127.     private function generateTags(string $nameRequest $requestSalesChannelContext $context): array
  128.     {
  129.         $tags array_merge(
  130.             $this->cacheTracer->get($name),
  131.             [$nameself::ALL_TAG]
  132.         );
  133.         $event = new NotFoundPageTagsEvent($tags$request$context);
  134.         $this->eventDispatcher->dispatch($event);
  135.         return array_unique(array_filter($event->getTags()));
  136.     }
  137.     private function setSalesChannelContext(Request $request): void
  138.     {
  139.         $salesChannelId = (string) $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID);
  140.         $context $this->contextService->get(
  141.             new SalesChannelContextServiceParameters(
  142.                 $salesChannelId,
  143.                 Uuid::randomHex(),
  144.                 $request->headers->get(PlatformRequest::HEADER_LANGUAGE_ID),
  145.                 $request->attributes->get(SalesChannelRequest::ATTRIBUTE_DOMAIN_CURRENCY_ID),
  146.                 $request->attributes->get(SalesChannelRequest::ATTRIBUTE_DOMAIN_ID)
  147.             )
  148.         );
  149.         $request->attributes->set(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT$context);
  150.     }
  151. }