vendor/shopware/core/Content/Category/SalesChannel/CachedCategoryRoute.php line 86

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Category\SalesChannel;
  3. use Shopware\Core\Content\Category\Event\CategoryRouteCacheKeyEvent;
  4. use Shopware\Core\Content\Category\Event\CategoryRouteCacheTagsEvent;
  5. use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
  6. use Shopware\Core\Content\Cms\SalesChannel\Struct\ProductBoxStruct;
  7. use Shopware\Core\Content\Cms\SalesChannel\Struct\ProductSliderStruct;
  8. use Shopware\Core\Framework\Adapter\Cache\AbstractCacheTracer;
  9. use Shopware\Core\Framework\Adapter\Cache\CacheValueCompressor;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Cache\EntityCacheKeyGenerator;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\RuleAreas;
  12. use Shopware\Core\Framework\DataAbstractionLayer\FieldSerializer\JsonFieldSerializer;
  13. use Shopware\Core\Framework\Log\Package;
  14. use Shopware\Core\Framework\Routing\Annotation\RouteScope;
  15. use Shopware\Core\Framework\Routing\Annotation\Since;
  16. use Shopware\Core\Profiling\Profiler;
  17. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  18. use Symfony\Component\HttpFoundation\Request;
  19. use Symfony\Component\Routing\Annotation\Route;
  20. use Symfony\Contracts\Cache\CacheInterface;
  21. use Symfony\Contracts\Cache\ItemInterface;
  22. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  23. /**
  24.  * @Route(defaults={"_routeScope"={"store-api"}})
  25.  */
  26. #[Package('content')]
  27. class CachedCategoryRoute extends AbstractCategoryRoute
  28. {
  29.     private AbstractCategoryRoute $decorated;
  30.     private CacheInterface $cache;
  31.     private EntityCacheKeyGenerator $generator;
  32.     /**
  33.      * @var AbstractCacheTracer<CategoryRouteResponse>
  34.      */
  35.     private AbstractCacheTracer $tracer;
  36.     /**
  37.      * @var array<string>
  38.      */
  39.     private array $states;
  40.     private EventDispatcherInterface $dispatcher;
  41.     /**
  42.      * @internal
  43.      *
  44.      * @param AbstractCacheTracer<CategoryRouteResponse> $tracer
  45.      * @param array<string> $states
  46.      */
  47.     public function __construct(
  48.         AbstractCategoryRoute $decorated,
  49.         CacheInterface $cache,
  50.         EntityCacheKeyGenerator $generator,
  51.         AbstractCacheTracer $tracer,
  52.         EventDispatcherInterface $dispatcher,
  53.         array $states
  54.     ) {
  55.         $this->decorated $decorated;
  56.         $this->cache $cache;
  57.         $this->generator $generator;
  58.         $this->tracer $tracer;
  59.         $this->states $states;
  60.         $this->dispatcher $dispatcher;
  61.     }
  62.     public static function buildName(string $id): string
  63.     {
  64.         return 'category-route-' $id;
  65.     }
  66.     public function getDecorated(): AbstractCategoryRoute
  67.     {
  68.         return $this->decorated;
  69.     }
  70.     /**
  71.      * @Since("6.2.0.0")
  72.      * @Route("/store-api/category/{navigationId}", name="store-api.category.detail", methods={"GET","POST"})
  73.      */
  74.     public function load(string $navigationIdRequest $requestSalesChannelContext $context): CategoryRouteResponse
  75.     {
  76.         return Profiler::trace('category-route', function () use ($navigationId$request$context) {
  77.             if ($context->hasState(...$this->states)) {
  78.                 return $this->getDecorated()->load($navigationId$request$context);
  79.             }
  80.             $key $this->generateKey($navigationId$request$context);
  81.             if ($key === null) {
  82.                 return $this->getDecorated()->load($navigationId$request$context);
  83.             }
  84.             $value $this->cache->get($key, function (ItemInterface $item) use ($navigationId$request$context) {
  85.                 $name self::buildName($navigationId);
  86.                 $response $this->tracer->trace($name, function () use ($navigationId$request$context) {
  87.                     return $this->getDecorated()->load($navigationId$request$context);
  88.                 });
  89.                 $item->tag($this->generateTags($navigationId$response$request$context));
  90.                 return CacheValueCompressor::compress($response);
  91.             });
  92.             return CacheValueCompressor::uncompress($value);
  93.         });
  94.     }
  95.     private function generateKey(string $navigationIdRequest $requestSalesChannelContext $context): ?string
  96.     {
  97.         $parts array_merge(
  98.             $request->query->all(),
  99.             $request->request->all(),
  100.             [$this->generator->getSalesChannelContextHash($context, [RuleAreas::CATEGORY_AREARuleAreas::PRODUCT_AREA])]
  101.         );
  102.         $event = new CategoryRouteCacheKeyEvent($navigationId$parts$request$contextnull);
  103.         $this->dispatcher->dispatch($event);
  104.         if (!$event->shouldCache()) {
  105.             return null;
  106.         }
  107.         return self::buildName($navigationId) . '-' md5(JsonFieldSerializer::encodeJson($event->getParts()));
  108.     }
  109.     /**
  110.      * @return array<string>
  111.      */
  112.     private function generateTags(string $navigationIdCategoryRouteResponse $responseRequest $requestSalesChannelContext $context): array
  113.     {
  114.         $tags array_merge(
  115.             $this->tracer->get(self::buildName($navigationId)),
  116.             $this->extractProductIds($response),
  117.             [self::buildName($navigationId)]
  118.         );
  119.         $event = new CategoryRouteCacheTagsEvent($navigationId$tags$request$response$contextnull);
  120.         $this->dispatcher->dispatch($event);
  121.         return array_unique(array_filter($event->getTags()));
  122.     }
  123.     /**
  124.      * @return array<string>
  125.      */
  126.     private function extractProductIds(CategoryRouteResponse $response): array
  127.     {
  128.         $page $response->getCategory()->getCmsPage();
  129.         if ($page === null) {
  130.             return [];
  131.         }
  132.         $ids = [];
  133.         $streamIds = [];
  134.         $slots $page->getElementsOfType('product-slider');
  135.         /** @var CmsSlotEntity $slot */
  136.         foreach ($slots as $slot) {
  137.             $slider $slot->getData();
  138.             if (!$slider instanceof ProductSliderStruct) {
  139.                 continue;
  140.             }
  141.             if ($slider->getStreamId() !== null) {
  142.                 $streamIds[] = $slider->getStreamId();
  143.             }
  144.             if ($slider->getProducts() === null) {
  145.                 continue;
  146.             }
  147.             foreach ($slider->getProducts() as $product) {
  148.                 $ids[] = $product->getId();
  149.                 $ids[] = $product->getParentId();
  150.             }
  151.         }
  152.         $slots $page->getElementsOfType('product-box');
  153.         /** @var CmsSlotEntity $slot */
  154.         foreach ($slots as $slot) {
  155.             $box $slot->getData();
  156.             if (!$box instanceof ProductBoxStruct) {
  157.                 continue;
  158.             }
  159.             if ($box->getProduct() === null) {
  160.                 continue;
  161.             }
  162.             $ids[] = $box->getProduct()->getId();
  163.             $ids[] = $box->getProduct()->getParentId();
  164.         }
  165.         $ids array_values(array_unique(array_filter($ids)));
  166.         return array_merge(
  167.             array_map([EntityCacheKeyGenerator::class, 'buildProductTag'], $ids),
  168.             array_map([EntityCacheKeyGenerator::class, 'buildStreamTag'], $streamIds),
  169.             [EntityCacheKeyGenerator::buildCmsTag($page->getId())]
  170.         );
  171.     }
  172. }