vendor/shopware/core/Content/Product/SalesChannel/CrossSelling/CachedProductCrossSellingRoute.php line 101

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Product\SalesChannel\CrossSelling;
  3. use Shopware\Core\Content\Product\Events\CrossSellingRouteCacheKeyEvent;
  4. use Shopware\Core\Content\Product\Events\CrossSellingRouteCacheTagsEvent;
  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\FieldSerializer\JsonFieldSerializer;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  11. use Shopware\Core\Framework\Log\Package;
  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\HttpFoundation\Request;
  17. use Symfony\Component\Routing\Annotation\Route;
  18. use Symfony\Contracts\Cache\CacheInterface;
  19. use Symfony\Contracts\Cache\ItemInterface;
  20. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  21. /**
  22.  * @Route(defaults={"_routeScope"={"store-api"}})
  23.  */
  24. #[Package('inventory')]
  25. class CachedProductCrossSellingRoute extends AbstractProductCrossSellingRoute
  26. {
  27.     private AbstractProductCrossSellingRoute $decorated;
  28.     private CacheInterface $cache;
  29.     private EntityCacheKeyGenerator $generator;
  30.     /**
  31.      * @var AbstractCacheTracer<ProductCrossSellingRouteResponse>
  32.      */
  33.     private AbstractCacheTracer $tracer;
  34.     /**
  35.      * @var array<string>
  36.      */
  37.     private array $states;
  38.     private EventDispatcherInterface $dispatcher;
  39.     /**
  40.      * @internal
  41.      *
  42.      * @param AbstractCacheTracer<ProductCrossSellingRouteResponse> $tracer
  43.      * @param array<string> $states
  44.      */
  45.     public function __construct(
  46.         AbstractProductCrossSellingRoute $decorated,
  47.         CacheInterface $cache,
  48.         EntityCacheKeyGenerator $generator,
  49.         AbstractCacheTracer $tracer,
  50.         EventDispatcherInterface $dispatcher,
  51.         array $states
  52.     ) {
  53.         $this->decorated $decorated;
  54.         $this->cache $cache;
  55.         $this->generator $generator;
  56.         $this->tracer $tracer;
  57.         $this->states $states;
  58.         $this->dispatcher $dispatcher;
  59.     }
  60.     public function getDecorated(): AbstractProductCrossSellingRoute
  61.     {
  62.         return $this->decorated;
  63.     }
  64.     public static function buildName(string $id): string
  65.     {
  66.         return 'cross-selling-route-' $id;
  67.     }
  68.     /**
  69.      * @Since("6.3.2.0")
  70.      * @Entity("product")
  71.      * @Route("/store-api/product/{productId}/cross-selling", name="store-api.product.cross-selling", methods={"POST"})
  72.      */
  73.     public function load(string $productIdRequest $requestSalesChannelContext $contextCriteria $criteria): ProductCrossSellingRouteResponse
  74.     {
  75.         if ($context->hasState(...$this->states)) {
  76.             return $this->getDecorated()->load($productId$request$context$criteria);
  77.         }
  78.         $key $this->generateKey($productId$request$context$criteria);
  79.         if ($key === null) {
  80.             return $this->getDecorated()->load($productId$request$context$criteria);
  81.         }
  82.         $value $this->cache->get($key, function (ItemInterface $item) use ($productId$request$context$criteria) {
  83.             $name self::buildName($productId);
  84.             $response $this->tracer->trace($name, function () use ($productId$request$context$criteria) {
  85.                 return $this->getDecorated()->load($productId$request$context$criteria);
  86.             });
  87.             $item->tag($this->generateTags($productId$request$response$context$criteria));
  88.             return CacheValueCompressor::compress($response);
  89.         });
  90.         return CacheValueCompressor::uncompress($value);
  91.     }
  92.     private function generateKey(string $productIdRequest $requestSalesChannelContext $contextCriteria $criteria): ?string
  93.     {
  94.         $parts = [
  95.             $this->generator->getCriteriaHash($criteria),
  96.             $this->generator->getSalesChannelContextHash($context, [RuleAreas::PRODUCT_AREA]),
  97.         ];
  98.         $event = new CrossSellingRouteCacheKeyEvent($productId$parts$request$context$criteria);
  99.         $this->dispatcher->dispatch($event);
  100.         if (!$event->shouldCache()) {
  101.             return null;
  102.         }
  103.         return self::buildName($productId) . '-' md5(JsonFieldSerializer::encodeJson($event->getParts()));
  104.     }
  105.     /**
  106.      * @return array<string>
  107.      */
  108.     private function generateTags(string $productIdRequest $requestProductCrossSellingRouteResponse $responseSalesChannelContext $contextCriteria $criteria): array
  109.     {
  110.         $tags array_merge(
  111.             $this->tracer->get(self::buildName($productId)),
  112.             $this->extractStreamTags($response),
  113.             $this->extractProductIds($response),
  114.             [self::buildName($productId)]
  115.         );
  116.         $event = new CrossSellingRouteCacheTagsEvent($productId$tags$request$response$context$criteria);
  117.         $this->dispatcher->dispatch($event);
  118.         return array_unique(array_filter($event->getTags()));
  119.     }
  120.     /**
  121.      * @return array<string>
  122.      */
  123.     private function extractStreamTags(ProductCrossSellingRouteResponse $response): array
  124.     {
  125.         $ids = [];
  126.         foreach ($response->getResult() as $element) {
  127.             $ids[] = $element->getStreamId();
  128.         }
  129.         $ids array_unique(array_filter($ids));
  130.         return array_map([EntityCacheKeyGenerator::class, 'buildStreamTag'], $ids);
  131.     }
  132.     /**
  133.      * @return array<string>
  134.      */
  135.     private function extractProductIds(ProductCrossSellingRouteResponse $response): array
  136.     {
  137.         $ids = [];
  138.         foreach ($response->getResult() as $element) {
  139.             $ids array_merge($ids$element->getProducts()->getIds());
  140.         }
  141.         $ids array_unique(array_filter($ids));
  142.         return array_map([EntityCacheKeyGenerator::class, 'buildProductTag'], $ids);
  143.     }
  144. }