src/Core/Content/Product/SalesChannel/Listing/CachedProductListingRoute.php line 53

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Product\SalesChannel\Listing;
  3. use Shopware\Core\Content\Product\Events\ProductListingRouteCacheKeyEvent;
  4. use Shopware\Core\Content\Product\Events\ProductListingRouteCacheTagsEvent;
  5. use Shopware\Core\Framework\Adapter\Cache\AbstractCacheTracer;
  6. use Shopware\Core\Framework\Adapter\Cache\CacheValueCompressor;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Cache\EntityCacheKeyGenerator;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\RuleAreas;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  10. use Shopware\Core\Framework\Log\Package;
  11. use Shopware\Core\Framework\Util\Json;
  12. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Symfony\Component\Routing\Annotation\Route;
  15. use Symfony\Contracts\Cache\CacheInterface;
  16. use Symfony\Contracts\Cache\ItemInterface;
  17. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  18. #[Route(defaults: ['_routeScope' => ['store-api']])]
  19. #[Package('inventory')]
  20. class CachedProductListingRoute extends AbstractProductListingRoute
  21. {
  22.     /**
  23.      * @internal
  24.      *
  25.      * @param AbstractCacheTracer<ProductListingRouteResponse> $tracer
  26.      * @param array<string> $states
  27.      */
  28.     public function __construct(private readonly AbstractProductListingRoute $decorated, private readonly CacheInterface $cache, private readonly EntityCacheKeyGenerator $generator, private readonly AbstractCacheTracer $tracer, private readonly EventDispatcherInterface $dispatcher, private readonly array $states)
  29.     {
  30.     }
  31.     public function getDecorated(): AbstractProductListingRoute
  32.     {
  33.         return $this->decorated;
  34.     }
  35.     #[Route(path'/store-api/product-listing/{categoryId}'name'store-api.product.listing'methods: ['POST'], defaults: ['_entity' => 'product'])]
  36.     public function load(string $categoryIdRequest $requestSalesChannelContext $contextCriteria $criteria): ProductListingRouteResponse
  37.     {
  38.         if ($context->hasState(...$this->states)) {
  39.             return $this->getDecorated()->load($categoryId$request$context$criteria);
  40.         }
  41.         $key $this->generateKey($categoryId$request$context$criteria);
  42.         if ($key === null) {
  43.             return $this->getDecorated()->load($categoryId$request$context$criteria);
  44.         }
  45.         $value $this->cache->get($key, function (ItemInterface $item) use ($categoryId$request$context$criteria) {
  46.             $name self::buildName($categoryId);
  47.             $response $this->tracer->trace($name, fn () => $this->getDecorated()->load($categoryId$request$context$criteria));
  48.             $item->tag($this->generateTags($categoryId$request$response$context$criteria));
  49.             return CacheValueCompressor::compress($response);
  50.         });
  51.         return CacheValueCompressor::uncompress($value);
  52.     }
  53.     public static function buildName(string $categoryId): string
  54.     {
  55.         return 'product-listing-route-' $categoryId;
  56.     }
  57.     private function generateKey(string $categoryIdRequest $requestSalesChannelContext $contextCriteria $criteria): ?string
  58.     {
  59.         $parts = [
  60.             $this->generator->getCriteriaHash($criteria),
  61.             $this->generator->getSalesChannelContextHash($context, [RuleAreas::PRODUCT_AREARuleAreas::CATEGORY_AREA]),
  62.         ];
  63.         $event = new ProductListingRouteCacheKeyEvent($parts$categoryId$request$context$criteria);
  64.         $this->dispatcher->dispatch($event);
  65.         if (!$event->shouldCache()) {
  66.             return null;
  67.         }
  68.         return self::buildName($categoryId) . '-' md5(Json::encode($event->getParts()));
  69.     }
  70.     /**
  71.      * @return array<string>
  72.      */
  73.     private function generateTags(string $categoryIdRequest $requestProductListingRouteResponse $responseSalesChannelContext $contextCriteria $criteria): array
  74.     {
  75.         $streamId $response->getResult()->getStreamId();
  76.         $tags array_merge(
  77.             $this->tracer->get(self::buildName($categoryId)),
  78.             [$streamId EntityCacheKeyGenerator::buildStreamTag($streamId) : null],
  79.             [self::buildName($categoryId)]
  80.         );
  81.         $event = new ProductListingRouteCacheTagsEvent($tags$categoryId$request$response$context$criteria);
  82.         $this->dispatcher->dispatch($event);
  83.         return array_unique(array_filter($event->getTags()));
  84.     }
  85. }