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

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Product\SalesChannel\Detail;
  3. use OpenApi\Annotations as OA;
  4. use Psr\Log\LoggerInterface;
  5. use Shopware\Core\Content\Product\Events\ProductDetailRouteCacheKeyEvent;
  6. use Shopware\Core\Content\Product\Events\ProductDetailRouteCacheTagsEvent;
  7. use Shopware\Core\Framework\Adapter\Cache\AbstractCacheTracer;
  8. use Shopware\Core\Framework\Adapter\Cache\CacheCompressor;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Cache\EntityCacheKeyGenerator;
  10. use Shopware\Core\Framework\DataAbstractionLayer\FieldSerializer\JsonFieldSerializer;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  12. use Shopware\Core\Framework\Routing\Annotation\Entity;
  13. use Shopware\Core\Framework\Routing\Annotation\RouteScope;
  14. use Shopware\Core\Framework\Routing\Annotation\Since;
  15. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  16. use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
  17. use Symfony\Component\HttpFoundation\Request;
  18. use Symfony\Component\Routing\Annotation\Route;
  19. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  20. /**
  21.  * @RouteScope(scopes={"store-api"})
  22.  */
  23. class CachedProductDetailRoute extends AbstractProductDetailRoute
  24. {
  25.     private AbstractProductDetailRoute $decorated;
  26.     private TagAwareAdapterInterface $cache;
  27.     private EntityCacheKeyGenerator $generator;
  28.     /**
  29.      * @var AbstractCacheTracer<ProductDetailRouteResponse>
  30.      */
  31.     private AbstractCacheTracer $tracer;
  32.     private array $states;
  33.     private EventDispatcherInterface $dispatcher;
  34.     private LoggerInterface $logger;
  35.     /**
  36.      * @param AbstractCacheTracer<ProductDetailRouteResponse> $tracer
  37.      */
  38.     public function __construct(
  39.         AbstractProductDetailRoute $decorated,
  40.         TagAwareAdapterInterface $cache,
  41.         EntityCacheKeyGenerator $generator,
  42.         AbstractCacheTracer $tracer,
  43.         EventDispatcherInterface $dispatcher,
  44.         array $states,
  45.         LoggerInterface $logger
  46.     ) {
  47.         $this->decorated $decorated;
  48.         $this->cache $cache;
  49.         $this->generator $generator;
  50.         $this->tracer $tracer;
  51.         $this->states $states;
  52.         $this->dispatcher $dispatcher;
  53.         $this->logger $logger;
  54.     }
  55.     public function getDecorated(): AbstractProductDetailRoute
  56.     {
  57.         return $this->decorated;
  58.     }
  59.     /**
  60.      * @Since("6.3.2.0")
  61.      * @Entity("product")
  62.      * @OA\Post(
  63.      *      path="/product/{productId}",
  64.      *      summary="Fetch a single product",
  65.      *      description="This route is used to load a single product with the corresponding details. In addition to loading the data, the best variant of the product is determined when a parent id is passed.",
  66.      *      operationId="readProductDetail",
  67.      *      tags={"Store API","Product"},
  68.      *      @OA\Parameter(
  69.      *          name="productId",
  70.      *          description="Product ID",
  71.      *          @OA\Schema(type="string"),
  72.      *          in="path",
  73.      *          required=true
  74.      *      ),
  75.      *      @OA\Response(
  76.      *          response="200",
  77.      *          description="Product information along with variant groups and options",
  78.      *          @OA\JsonContent(ref="#/components/schemas/ProductDetailResponse")
  79.      *     )
  80.      * )
  81.      * @Route("/store-api/product/{productId}", name="store-api.product.detail", methods={"POST"})
  82.      */
  83.     public function load(string $productIdRequest $requestSalesChannelContext $contextCriteria $criteria): ProductDetailRouteResponse
  84.     {
  85.         if ($context->hasState(...$this->states)) {
  86.             $this->logger->info('cache-miss: ' self::buildName($productId));
  87.             return $this->getDecorated()->load($productId$request$context$criteria);
  88.         }
  89.         $item $this->cache->getItem(
  90.             $this->generateKey($productId$request$context$criteria)
  91.         );
  92.         try {
  93.             if ($item->isHit() && $item->get()) {
  94.                 $this->logger->info('cache-hit: ' self::buildName($productId));
  95.                 return CacheCompressor::uncompress($item);
  96.             }
  97.         } catch (\Throwable $e) {
  98.             $this->logger->error($e->getMessage());
  99.         }
  100.         $this->logger->info('cache-miss: ' self::buildName($productId));
  101.         $name self::buildName($productId);
  102.         $response $this->tracer->trace($name, function () use ($productId$request$context$criteria) {
  103.             return $this->getDecorated()->load($productId$request$context$criteria);
  104.         });
  105.         $item CacheCompressor::compress($item$response);
  106.         $item->tag($this->generateTags($productId$request$response$context$criteria));
  107.         $this->cache->save($item);
  108.         return $response;
  109.     }
  110.     public static function buildName(string $parentId): string
  111.     {
  112.         return 'product-detail-route-' $parentId;
  113.     }
  114.     private function generateKey(string $productIdRequest $requestSalesChannelContext $contextCriteria $criteria): string
  115.     {
  116.         $parts = [
  117.             self::buildName($productId),
  118.             $this->generator->getCriteriaHash($criteria),
  119.             $this->generator->getSalesChannelContextHash($context),
  120.         ];
  121.         $event = new ProductDetailRouteCacheKeyEvent($parts$request$context$criteria);
  122.         $this->dispatcher->dispatch($event);
  123.         return md5(JsonFieldSerializer::encodeJson($event->getParts()));
  124.     }
  125.     private function generateTags(string $productIdRequest $requestProductDetailRouteResponse $responseSalesChannelContext $contextCriteria $criteria): array
  126.     {
  127.         $parentId $response->getProduct()->getParentId() ?? $response->getProduct()->getId();
  128.         $pageId $response->getProduct()->getCmsPageId();
  129.         $tags array_merge(
  130.             $this->tracer->get(self::buildName($productId)),
  131.             [$pageId !== null EntityCacheKeyGenerator::buildCmsTag($pageId) : null],
  132.             [self::buildName($parentId)]
  133.         );
  134.         $event = new ProductDetailRouteCacheTagsEvent($tags$request$response$context$criteria);
  135.         $this->dispatcher->dispatch($event);
  136.         return array_unique(array_filter($event->getTags()));
  137.     }
  138. }