vendor/shopware/storefront/Framework/Cache/CacheResponseSubscriber.php line 78

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Storefront\Framework\Cache;
  3. use Shopware\Core\Checkout\Cart\Cart;
  4. use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
  5. use Shopware\Core\Framework\Adapter\Cache\CacheStateSubscriber;
  6. use Shopware\Core\PlatformRequest;
  7. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextService;
  8. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  9. use Shopware\Storefront\Framework\Cache\Annotation\HttpCache;
  10. use Shopware\Storefront\Framework\Routing\MaintenanceModeResolver;
  11. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  12. use Symfony\Component\HttpFoundation\Cookie;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Symfony\Component\HttpFoundation\Response;
  15. use Symfony\Component\HttpKernel\Event\RequestEvent;
  16. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  17. use Symfony\Component\HttpKernel\KernelEvents;
  18. class CacheResponseSubscriber implements EventSubscriberInterface
  19. {
  20.     public const STATE_LOGGED_IN CacheStateSubscriber::STATE_LOGGED_IN;
  21.     public const STATE_CART_FILLED CacheStateSubscriber::STATE_CART_FILLED;
  22.     public const CURRENCY_COOKIE 'sw-currency';
  23.     public const CONTEXT_CACHE_COOKIE 'sw-cache-hash';
  24.     public const SYSTEM_STATE_COOKIE 'sw-states';
  25.     public const INVALIDATION_STATES_HEADER 'sw-invalidation-states';
  26.     private const CORE_HTTP_CACHED_ROUTES = [
  27.         'api.acl.privileges.get',
  28.     ];
  29.     private CartService $cartService;
  30.     private int $defaultTtl;
  31.     private bool $httpCacheEnabled;
  32.     private MaintenanceModeResolver $maintenanceResolver;
  33.     public function __construct(
  34.         CartService $cartService,
  35.         int $defaultTtl,
  36.         bool $httpCacheEnabled,
  37.         MaintenanceModeResolver $maintenanceModeResolver
  38.     ) {
  39.         $this->cartService $cartService;
  40.         $this->defaultTtl $defaultTtl;
  41.         $this->httpCacheEnabled $httpCacheEnabled;
  42.         $this->maintenanceResolver $maintenanceModeResolver;
  43.     }
  44.     /**
  45.      * @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
  46.      */
  47.     public static function getSubscribedEvents()
  48.     {
  49.         return [
  50.             KernelEvents::REQUEST => 'addHttpCacheToCoreRoutes',
  51.             KernelEvents::RESPONSE => [
  52.                 ['setResponseCache', -1500],
  53.             ],
  54.         ];
  55.     }
  56.     public function addHttpCacheToCoreRoutes(RequestEvent $event): void
  57.     {
  58.         $request $event->getRequest();
  59.         $route $request->attributes->get('_route');
  60.         if (\in_array($routeself::CORE_HTTP_CACHED_ROUTEStrue)) {
  61.             $request->attributes->set('_' HttpCache::ALIAS, [new HttpCache([])]);
  62.         }
  63.     }
  64.     public function setResponseCache(ResponseEvent $event): void
  65.     {
  66.         if (!$this->httpCacheEnabled) {
  67.             return;
  68.         }
  69.         $response $event->getResponse();
  70.         $request $event->getRequest();
  71.         if ($this->maintenanceResolver->isMaintenanceRequest($request)) {
  72.             return;
  73.         }
  74.         $context $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
  75.         if (!$context instanceof SalesChannelContext) {
  76.             return;
  77.         }
  78.         $route $request->attributes->get('_route');
  79.         if ($route === 'frontend.checkout.configure') {
  80.             $this->setCurrencyCookie($request$response);
  81.         }
  82.         $cart $this->cartService->getCart($context->getToken(), $context);
  83.         $states $this->updateSystemState($cart$context$request$response);
  84.         if ($request->getMethod() !== Request::METHOD_GET) {
  85.             return;
  86.         }
  87.         if ($context->getCustomer() || $cart->getLineItems()->count() > 0) {
  88.             $cookie Cookie::create(self::CONTEXT_CACHE_COOKIE$this->buildCacheHash($context));
  89.             $cookie->setSecureDefault($request->isSecure());
  90.             $response->headers->setCookie($cookie);
  91.         } else {
  92.             $response->headers->removeCookie(self::CONTEXT_CACHE_COOKIE);
  93.             $response->headers->clearCookie(self::CONTEXT_CACHE_COOKIE);
  94.         }
  95.         $config $request->attributes->get('_' HttpCache::ALIAS);
  96.         if (empty($config)) {
  97.             return;
  98.         }
  99.         /** @var HttpCache $cache */
  100.         $cache array_shift($config);
  101.         if ($this->hasInvalidationState($cache$states)) {
  102.             return;
  103.         }
  104.         $maxAge $cache->getMaxAge() ?? $this->defaultTtl;
  105.         $response->setSharedMaxAge($maxAge);
  106.         $response->headers->addCacheControlDirective('must-revalidate');
  107.         $response->headers->set(
  108.             self::INVALIDATION_STATES_HEADER,
  109.             implode(','$cache->getStates())
  110.         );
  111.     }
  112.     private function hasInvalidationState(HttpCache $cache, array $states): bool
  113.     {
  114.         foreach ($states as $state) {
  115.             if (\in_array($state$cache->getStates(), true)) {
  116.                 return true;
  117.             }
  118.         }
  119.         return false;
  120.     }
  121.     private function buildCacheHash(SalesChannelContext $context): string
  122.     {
  123.         return md5(json_encode([
  124.             $context->getRuleIds(),
  125.             $context->getContext()->getVersionId(),
  126.             $context->getCurrency()->getId(),
  127.         ]));
  128.     }
  129.     /**
  130.      * System states can be used to stop caching routes at certain states. For example,
  131.      * the checkout routes are no longer cached if the customer has products in the cart or is logged in.
  132.      */
  133.     private function updateSystemState(Cart $cartSalesChannelContext $contextRequest $requestResponse $response): array
  134.     {
  135.         $states $this->getSystemStates($request$context$cart);
  136.         if (empty($states)) {
  137.             $response->headers->removeCookie(self::SYSTEM_STATE_COOKIE);
  138.             $response->headers->clearCookie(self::SYSTEM_STATE_COOKIE);
  139.             return [];
  140.         }
  141.         $cookie Cookie::create(self::SYSTEM_STATE_COOKIEimplode(','$states));
  142.         $cookie->setSecureDefault($request->isSecure());
  143.         $response->headers->setCookie($cookie);
  144.         return $states;
  145.     }
  146.     private function getSystemStates(Request $requestSalesChannelContext $contextCart $cart): array
  147.     {
  148.         $states = [];
  149.         $swStates = (string) $request->cookies->get(self::SYSTEM_STATE_COOKIE);
  150.         if ($swStates !== null) {
  151.             $states explode(','$swStates);
  152.             $states array_flip($states);
  153.         }
  154.         $states $this->switchState($statesself::STATE_LOGGED_IN$context->getCustomer() !== null);
  155.         $states $this->switchState($statesself::STATE_CART_FILLED$cart->getLineItems()->count() > 0);
  156.         return array_keys($states);
  157.     }
  158.     private function switchState(array $statesstring $keybool $match): array
  159.     {
  160.         if ($match) {
  161.             $states[$key] = true;
  162.             return $states;
  163.         }
  164.         unset($states[$key]);
  165.         return $states;
  166.     }
  167.     private function setCurrencyCookie(Request $requestResponse $response): void
  168.     {
  169.         $currencyId $request->get(SalesChannelContextService::CURRENCY_ID);
  170.         if (!$currencyId) {
  171.             return;
  172.         }
  173.         $cookie Cookie::create(self::CURRENCY_COOKIE$currencyId);
  174.         $cookie->setSecureDefault($request->isSecure());
  175.         $response->headers->setCookie($cookie);
  176.     }
  177. }