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

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 $productId, Request $request, SalesChannelContext $context, Criteria $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 $productId, Request $request, SalesChannelContext $context, Criteria $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 $productId, Request $request, ProductCrossSellingRouteResponse $response, SalesChannelContext $context, Criteria $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. }