vendor/shopware/core/Content/Product/SalesChannel/Listing/CachedProductListingRoute.php line 111

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Product\SalesChannel\Listing;
  3. use OpenApi\Annotations as OA;
  4. use Shopware\Core\Content\Product\Events\ProductListingRouteCacheKeyEvent;
  5. use Shopware\Core\Content\Product\Events\ProductListingRouteCacheTagsEvent;
  6. use Shopware\Core\Framework\Adapter\Cache\AbstractCacheTracer;
  7. use Shopware\Core\Framework\Adapter\Cache\CacheValueCompressor;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Cache\EntityCacheKeyGenerator;
  9. use Shopware\Core\Framework\DataAbstractionLayer\FieldSerializer\JsonFieldSerializer;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  11. use Shopware\Core\Framework\Routing\Annotation\Entity;
  12. use Shopware\Core\Framework\Routing\Annotation\RouteScope;
  13. use Shopware\Core\Framework\Routing\Annotation\Since;
  14. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  15. use Symfony\Component\HttpFoundation\Request;
  16. use Symfony\Component\Routing\Annotation\Route;
  17. use Symfony\Contracts\Cache\CacheInterface;
  18. use Symfony\Contracts\Cache\ItemInterface;
  19. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  20. /**
  21.  * @Route(defaults={"_routeScope"={"store-api"}})
  22.  */
  23. class CachedProductListingRoute extends AbstractProductListingRoute
  24. {
  25.     private AbstractProductListingRoute $decorated;
  26.     private CacheInterface $cache;
  27.     private EntityCacheKeyGenerator $generator;
  28.     /**
  29.      * @var AbstractCacheTracer<ProductListingRouteResponse>
  30.      */
  31.     private AbstractCacheTracer $tracer;
  32.     private array $states;
  33.     private EventDispatcherInterface $dispatcher;
  34.     /**
  35.      * @internal
  36.      *
  37.      * @param AbstractCacheTracer<ProductListingRouteResponse> $tracer
  38.      */
  39.     public function __construct(
  40.         AbstractProductListingRoute $decorated,
  41.         CacheInterface $cache,
  42.         EntityCacheKeyGenerator $generator,
  43.         AbstractCacheTracer $tracer,
  44.         EventDispatcherInterface $dispatcher,
  45.         array $states
  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.     }
  54.     public function getDecorated(): AbstractProductListingRoute
  55.     {
  56.         return $this->decorated;
  57.     }
  58.     /**
  59.      * @Since("6.2.0.0")
  60.      * @Entity("product")
  61.      * @OA\Post(
  62.      *      path="/product-listing/{categoryId}",
  63.      *      summary="Fetch a product listing by category",
  64.      *      description="Fetches a product listing for a specific category. It also provides filters, sortings and property aggregations, analogous to the /search endpoint.",
  65.      *      operationId="readProductListing",
  66.      *      tags={"Store API","Product"},
  67.      *      @OA\Parameter(
  68.      *          name="categoryId",
  69.      *          description="Identifier of a category.",
  70.      *          @OA\Schema(type="string"),
  71.      *          in="path",
  72.      *          required=true
  73.      *      ),
  74.      *      @OA\Response(
  75.      *          response="200",
  76.      *          description="Returns a product listing containing all products and additional fields to display a listing.",
  77.      *          @OA\JsonContent(ref="#/components/schemas/ProductListingResult")
  78.      *     )
  79.      * )
  80.      * @Route("/store-api/product-listing/{categoryId}", name="store-api.product.listing", methods={"POST"})
  81.      */
  82.     public function load(string $categoryIdRequest $requestSalesChannelContext $contextCriteria $criteria): ProductListingRouteResponse
  83.     {
  84.         if ($context->hasState(...$this->states)) {
  85.             return $this->getDecorated()->load($categoryId$request$context$criteria);
  86.         }
  87.         $key $this->generateKey($categoryId$request$context$criteria);
  88.         $value $this->cache->get($key, function (ItemInterface $item) use ($categoryId$request$context$criteria) {
  89.             $name self::buildName($categoryId);
  90.             $response $this->tracer->trace($name, function () use ($categoryId$request$context$criteria) {
  91.                 return $this->getDecorated()->load($categoryId$request$context$criteria);
  92.             });
  93.             $item->tag($this->generateTags($categoryId$request$response$context$criteria));
  94.             return CacheValueCompressor::compress($response);
  95.         });
  96.         return CacheValueCompressor::uncompress($value);
  97.     }
  98.     public static function buildName(string $categoryId): string
  99.     {
  100.         return 'product-listing-route-' $categoryId;
  101.     }
  102.     private function generateKey(string $categoryIdRequest $requestSalesChannelContext $contextCriteria $criteria): string
  103.     {
  104.         $parts = [
  105.             $this->generator->getCriteriaHash($criteria),
  106.             $this->generator->getSalesChannelContextHash($context),
  107.         ];
  108.         $event = new ProductListingRouteCacheKeyEvent($parts$categoryId$request$context$criteria);
  109.         $this->dispatcher->dispatch($event);
  110.         return self::buildName($categoryId) . '-' md5(JsonFieldSerializer::encodeJson($event->getParts()));
  111.     }
  112.     private function generateTags(string $categoryIdRequest $requestProductListingRouteResponse $responseSalesChannelContext $contextCriteria $criteria): array
  113.     {
  114.         $streamId $response->getResult()->getStreamId();
  115.         $tags array_merge(
  116.             $this->tracer->get(self::buildName($categoryId)),
  117.             [$streamId EntityCacheKeyGenerator::buildStreamTag($streamId) : null],
  118.             [self::buildName($categoryId)]
  119.         );
  120.         $event = new ProductListingRouteCacheTagsEvent($tags$categoryId$request$response$context$criteria);
  121.         $this->dispatcher->dispatch($event);
  122.         return array_unique(array_filter($event->getTags()));
  123.     }
  124. }