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

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