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

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