vendor/shopware/core/Content/Product/SalesChannel/Detail/CachedProductDetailRoute.php line 62

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Product\SalesChannel\Detail;
  3. use Shopware\Core\Content\Product\Events\ProductDetailRouteCacheKeyEvent;
  4. use Shopware\Core\Content\Product\Events\ProductDetailRouteCacheTagsEvent;
  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 CachedProductDetailRoute extends AbstractProductDetailRoute
  21. {
  22.     /**
  23.      * @internal
  24.      *
  25.      * @param AbstractCacheTracer<ProductDetailRouteResponse> $tracer
  26.      * @param array<string, string> $states
  27.      */
  28.     public function __construct(
  29.         private readonly AbstractProductDetailRoute $decorated,
  30.         private readonly CacheInterface $cache,
  31.         private readonly EntityCacheKeyGenerator $generator,
  32.         private readonly AbstractCacheTracer $tracer,
  33.         private readonly EventDispatcherInterface $dispatcher,
  34.         private readonly array $states
  35.     ) {
  36.     }
  37.     public function getDecorated(): AbstractProductDetailRoute
  38.     {
  39.         return $this->decorated;
  40.     }
  41.     #[Route(path'/store-api/product/{productId}'name'store-api.product.detail'methods: ['POST'], defaults: ['_entity' => 'product'])]
  42.     public function load(string $productIdRequest $requestSalesChannelContext $contextCriteria $criteria): ProductDetailRouteResponse
  43.     {
  44.         if ($context->hasState(...$this->states)) {
  45.             return $this->getDecorated()->load($productId$request$context$criteria);
  46.         }
  47.         $key $this->generateKey($productId$request$context$criteria);
  48.         if ($key === null) {
  49.             return $this->getDecorated()->load($productId$request$context$criteria);
  50.         }
  51.         $value $this->cache->get($key, function (ItemInterface $item) use ($productId$request$context$criteria) {
  52.             $name self::buildName($productId);
  53.             $response $this->tracer->trace($name, fn () => $this->getDecorated()->load($productId$request$context$criteria));
  54.             $item->tag($this->generateTags($productId$request$response$context$criteria));
  55.             return CacheValueCompressor::compress($response);
  56.         });
  57.         return CacheValueCompressor::uncompress($value);
  58.     }
  59.     public static function buildName(string $parentId): string
  60.     {
  61.         return 'product-detail-route-' $parentId;
  62.     }
  63.     private function generateKey(string $productIdRequest $requestSalesChannelContext $contextCriteria $criteria): ?string
  64.     {
  65.         $parts = [
  66.             $this->generator->getCriteriaHash($criteria),
  67.             $this->generator->getSalesChannelContextHash($context, [RuleAreas::PRODUCT_AREARuleAreas::CATEGORY_AREA]),
  68.         ];
  69.         $event = new ProductDetailRouteCacheKeyEvent($parts$request$context$criteria);
  70.         $this->dispatcher->dispatch($event);
  71.         if (!$event->shouldCache()) {
  72.             return null;
  73.         }
  74.         return self::buildName($productId) . '-' md5(Json::encode($event->getParts()));
  75.     }
  76.     /**
  77.      * @return array<string>
  78.      */
  79.     private function generateTags(string $productIdRequest $requestProductDetailRouteResponse $responseSalesChannelContext $contextCriteria $criteria): array
  80.     {
  81.         $parentId $response->getProduct()->getParentId() ?? $response->getProduct()->getId();
  82.         $pageId $response->getProduct()->getCmsPageId();
  83.         $tags array_merge(
  84.             $this->tracer->get(self::buildName($productId)),
  85.             [$pageId !== null EntityCacheKeyGenerator::buildCmsTag($pageId) : null],
  86.             [self::buildName($parentId)]
  87.         );
  88.         $event = new ProductDetailRouteCacheTagsEvent($tags$request$response$context$criteria);
  89.         $this->dispatcher->dispatch($event);
  90.         return array_unique(array_filter($event->getTags()));
  91.     }
  92. }