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

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