vendor/shopware/storefront/Framework/Cache/CacheStore.php line 73

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Storefront\Framework\Cache;
  3. use Shopware\Core\Framework\Adapter\Cache\AbstractCacheTracer;
  4. use Shopware\Core\Framework\Adapter\Cache\CacheCompressor;
  5. use Shopware\Core\Framework\Log\Package;
  6. use Shopware\Core\System\SalesChannel\StoreApiResponse;
  7. use Shopware\Storefront\Framework\Cache\Event\HttpCacheHitEvent;
  8. use Shopware\Storefront\Framework\Cache\Event\HttpCacheItemWrittenEvent;
  9. use Shopware\Storefront\Framework\Routing\MaintenanceModeResolver;
  10. use Shopware\Storefront\Framework\Routing\StorefrontResponse;
  11. use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\HttpKernel\HttpCache\StoreInterface;
  15. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  16. #[Package('storefront')]
  17. class CacheStore implements StoreInterface
  18. {
  19.     public const TAG_HEADER 'sw-cache-tags';
  20.     private TagAwareAdapterInterface $cache;
  21.     /**
  22.      * @var array<string, bool>
  23.      */
  24.     private array $locks = [];
  25.     private CacheStateValidator $stateValidator;
  26.     private EventDispatcherInterface $eventDispatcher;
  27.     /**
  28.      * @var AbstractCacheTracer<StoreApiResponse>
  29.      */
  30.     private AbstractCacheTracer $tracer;
  31.     private AbstractHttpCacheKeyGenerator $cacheKeyGenerator;
  32.     private MaintenanceModeResolver $maintenanceResolver;
  33.     private string $sessionName;
  34.     /**
  35.      * @internal
  36.      *
  37.      * @param AbstractCacheTracer<StoreApiResponse> $tracer
  38.      * @param array<string, mixed> $sessionOptions
  39.      */
  40.     public function __construct(
  41.         TagAwareAdapterInterface $cache,
  42.         CacheStateValidator $stateValidator,
  43.         EventDispatcherInterface $eventDispatcher,
  44.         AbstractCacheTracer $tracer,
  45.         AbstractHttpCacheKeyGenerator $cacheKeyGenerator,
  46.         MaintenanceModeResolver $maintenanceModeResolver,
  47.         array $sessionOptions
  48.     ) {
  49.         $this->cache $cache;
  50.         $this->stateValidator $stateValidator;
  51.         $this->eventDispatcher $eventDispatcher;
  52.         $this->tracer $tracer;
  53.         $this->cacheKeyGenerator $cacheKeyGenerator;
  54.         $this->maintenanceResolver $maintenanceModeResolver;
  55.         $this->sessionName $sessionOptions['name'] ?? 'session-';
  56.     }
  57.     /**
  58.      * @return Response|null
  59.      */
  60.     public function lookup(Request $request)
  61.     {
  62.         // maintenance mode active and current ip is whitelisted > disable caching
  63.         if (!$this->maintenanceResolver->shouldBeCached($request)) {
  64.             return null;
  65.         }
  66.         $key $this->cacheKeyGenerator->generate($request);
  67.         $item $this->cache->getItem($key);
  68.         if (!$item->isHit() || !$item->get()) {
  69.             return null;
  70.         }
  71.         /** @var Response $response */
  72.         $response CacheCompressor::uncompress($item);
  73.         if (!$this->stateValidator->isValid($request$response)) {
  74.             return null;
  75.         }
  76.         $this->eventDispatcher->dispatch(
  77.             new HttpCacheHitEvent($item$request$response)
  78.         );
  79.         return $response;
  80.     }
  81.     /**
  82.      * @return string
  83.      */
  84.     public function write(Request $requestResponse $response)
  85.     {
  86.         $key $this->cacheKeyGenerator->generate($request);
  87.         // maintenance mode active and current ip is whitelisted > disable caching
  88.         if ($this->maintenanceResolver->isMaintenanceRequest($request)) {
  89.             return $key;
  90.         }
  91.         if ($response instanceof StorefrontResponse) {
  92.             $response->setData(null);
  93.             $response->setContext(null);
  94.         }
  95.         $tags $this->tracer->get('all');
  96.         $tags array_filter($tags, static function (string $tag): bool {
  97.             // remove tag for global theme cache, http cache will be invalidate for each key which gets accessed in the request
  98.             if (strpos($tag'theme-config') !== false) {
  99.                 return false;
  100.             }
  101.             // remove tag for global config cache, http cache will be invalidate for each key which gets accessed in the request
  102.             if (strpos($tag'system-config') !== false) {
  103.                 return false;
  104.             }
  105.             return true;
  106.         });
  107.         if ($response->headers->has(self::TAG_HEADER)) {
  108.             /** @var string $tagHeader */
  109.             $tagHeader $response->headers->get(self::TAG_HEADER);
  110.             $responseTags = \json_decode($tagHeadertrue512, \JSON_THROW_ON_ERROR);
  111.             $tags array_merge($responseTags$tags);
  112.             $response->headers->remove(self::TAG_HEADER);
  113.         }
  114.         $item $this->cache->getItem($key);
  115.         /**
  116.          * Symfony pops out in AbstractSessionListener(https://github.com/symfony/symfony/blob/v5.4.5/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php#L139-L186) the session and assigns it to the Response
  117.          * We should never cache the cookie of the actual browser session, this part removes it again from the cloned response object. As they popped it out of the PHP stack, we need to from it only from the cached response
  118.          */
  119.         $cacheResponse = clone $response;
  120.         $cacheResponse->headers = clone $response->headers;
  121.         foreach ($cacheResponse->headers->getCookies() as $cookie) {
  122.             if ($cookie->getName() === $this->sessionName) {
  123.                 $cacheResponse->headers->removeCookie($cookie->getName(), $cookie->getPath(), $cookie->getDomain());
  124.             }
  125.         }
  126.         $item CacheCompressor::compress($item$cacheResponse);
  127.         $item->expiresAt($cacheResponse->getExpires());
  128.         $item->tag($tags);
  129.         $this->cache->save($item);
  130.         $this->eventDispatcher->dispatch(
  131.             new HttpCacheItemWrittenEvent($item$tags$request$response)
  132.         );
  133.         return $key;
  134.     }
  135.     public function invalidate(Request $request): void
  136.     {
  137.         $this->cache->deleteItem(
  138.             $this->cacheKeyGenerator->generate($request)
  139.         );
  140.     }
  141.     /**
  142.      * Cleanups storage.
  143.      */
  144.     public function cleanup(): void
  145.     {
  146.         $keys array_keys($this->locks);
  147.         $this->cache->deleteItems($keys);
  148.         $this->locks = [];
  149.     }
  150.     /**
  151.      * Tries to lock the cache for a given Request, without blocking.
  152.      *
  153.      * @return bool|string true if the lock is acquired, the path to the current lock otherwise
  154.      */
  155.     public function lock(Request $request)
  156.     {
  157.         $key $this->getLockKey($request);
  158.         if ($this->cache->hasItem($key)) {
  159.             return $key;
  160.         }
  161.         $item $this->cache->getItem($key);
  162.         $item->set(true);
  163.         $item->expiresAfter(3);
  164.         $this->cache->save($item);
  165.         $this->locks[$key] = true;
  166.         return true;
  167.     }
  168.     /**
  169.      * Releases the lock for the given Request.
  170.      *
  171.      * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise
  172.      */
  173.     public function unlock(Request $request)
  174.     {
  175.         $key $this->getLockKey($request);
  176.         $this->cache->deleteItem($key);
  177.         unset($this->locks[$key]);
  178.         return true;
  179.     }
  180.     /**
  181.      * Returns whether or not a lock exists.
  182.      *
  183.      * @return bool true if lock exists, false otherwise
  184.      */
  185.     public function isLocked(Request $request)
  186.     {
  187.         return $this->cache->hasItem(
  188.             $this->getLockKey($request)
  189.         );
  190.     }
  191.     /**
  192.      * @return bool
  193.      */
  194.     public function purge(string $url)
  195.     {
  196.         $http preg_replace('#^https:#''http:'$url);
  197.         if ($http === null) {
  198.             return false;
  199.         }
  200.         $https preg_replace('#^http:#''https:'$url);
  201.         if ($https === null) {
  202.             return false;
  203.         }
  204.         $httpPurged $this->unlock(Request::create($http));
  205.         $httpsPurged $this->unlock(Request::create($https));
  206.         return $httpPurged || $httpsPurged;
  207.     }
  208.     private function getLockKey(Request $request): string
  209.     {
  210.         return 'http_lock_' $this->cacheKeyGenerator->generate($request);
  211.     }
  212. }