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

  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\Log\Package;
  13. use Shopware\Core\Framework\Util\Json;
  14. use Shopware\Core\Profiling\Profiler;
  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. #[Route(defaults: ['_routeScope' => ['store-api']])]
  22. #[Package('content')]
  23. class CachedCategoryRoute extends AbstractCategoryRoute
  24. {
  25.     /**
  26.      * @internal
  27.      *
  28.      * @param AbstractCacheTracer<CategoryRouteResponse> $tracer
  29.      * @param array<string> $states
  30.      */
  31.     public function __construct(
  32.         private readonly AbstractCategoryRoute $decorated,
  33.         private readonly CacheInterface $cache,
  34.         private readonly EntityCacheKeyGenerator $generator,
  35.         private readonly AbstractCacheTracer $tracer,
  36.         private readonly EventDispatcherInterface $dispatcher,
  37.         private readonly array $states
  38.     ) {
  39.     }
  40.     public static function buildName(string $id): string
  41.     {
  42.         return 'category-route-' $id;
  43.     }
  44.     public function getDecorated(): AbstractCategoryRoute
  45.     {
  46.         return $this->decorated;
  47.     }
  48.     #[Route(path'/store-api/category/{navigationId}'name'store-api.category.detail'methods: ['GET''POST'])]
  49.     public function load(string $navigationIdRequest $requestSalesChannelContext $context): CategoryRouteResponse
  50.     {
  51.         return Profiler::trace('category-route', function () use ($navigationId$request$context) {
  52.             if ($context->hasState(...$this->states)) {
  53.                 return $this->getDecorated()->load($navigationId$request$context);
  54.             }
  55.             $key $this->generateKey($navigationId$request$context);
  56.             if ($key === null) {
  57.                 return $this->getDecorated()->load($navigationId$request$context);
  58.             }
  59.             $value $this->cache->get($key, function (ItemInterface $item) use ($navigationId$request$context) {
  60.                 $name self::buildName($navigationId);
  61.                 $response $this->tracer->trace($name, fn () => $this->getDecorated()->load($navigationId$request$context));
  62.                 $item->tag($this->generateTags($navigationId$response$request$context));
  63.                 return CacheValueCompressor::compress($response);
  64.             });
  65.             return CacheValueCompressor::uncompress($value);
  66.         });
  67.     }
  68.     private function generateKey(string $navigationIdRequest $requestSalesChannelContext $context): ?string
  69.     {
  70.         $parts = [...$request->query->all(), ...$request->request->all(), ...[$this->generator->getSalesChannelContextHash($context, [RuleAreas::CATEGORY_AREARuleAreas::PRODUCT_AREA])]];
  71.         $event = new CategoryRouteCacheKeyEvent($navigationId$parts$request$contextnull);
  72.         $this->dispatcher->dispatch($event);
  73.         if (!$event->shouldCache()) {
  74.             return null;
  75.         }
  76.         return self::buildName($navigationId) . '-' md5(Json::encode($event->getParts()));
  77.     }
  78.     /**
  79.      * @return array<string>
  80.      */
  81.     private function generateTags(string $navigationIdCategoryRouteResponse $responseRequest $requestSalesChannelContext $context): array
  82.     {
  83.         $tags array_merge(
  84.             $this->tracer->get(self::buildName($navigationId)),
  85.             $this->extractProductIds($response),
  86.             [self::buildName($navigationId)]
  87.         );
  88.         $event = new CategoryRouteCacheTagsEvent($navigationId$tags$request$response$contextnull);
  89.         $this->dispatcher->dispatch($event);
  90.         return array_unique(array_filter($event->getTags()));
  91.     }
  92.     /**
  93.      * @return array<string>
  94.      */
  95.     private function extractProductIds(CategoryRouteResponse $response): array
  96.     {
  97.         $page $response->getCategory()->getCmsPage();
  98.         if ($page === null) {
  99.             return [];
  100.         }
  101.         $ids = [];
  102.         $streamIds = [];
  103.         $slots $page->getElementsOfType('product-slider');
  104.         /** @var CmsSlotEntity $slot */
  105.         foreach ($slots as $slot) {
  106.             $slider $slot->getData();
  107.             if (!$slider instanceof ProductSliderStruct) {
  108.                 continue;
  109.             }
  110.             if ($slider->getStreamId() !== null) {
  111.                 $streamIds[] = $slider->getStreamId();
  112.             }
  113.             if ($slider->getProducts() === null) {
  114.                 continue;
  115.             }
  116.             foreach ($slider->getProducts() as $product) {
  117.                 $ids[] = $product->getId();
  118.                 $ids[] = $product->getParentId();
  119.             }
  120.         }
  121.         $slots $page->getElementsOfType('product-box');
  122.         /** @var CmsSlotEntity $slot */
  123.         foreach ($slots as $slot) {
  124.             $box $slot->getData();
  125.             if (!$box instanceof ProductBoxStruct) {
  126.                 continue;
  127.             }
  128.             if ($box->getProduct() === null) {
  129.                 continue;
  130.             }
  131.             $ids[] = $box->getProduct()->getId();
  132.             $ids[] = $box->getProduct()->getParentId();
  133.         }
  134.         $ids array_values(array_unique(array_filter($ids)));
  135.         return [...array_map(EntityCacheKeyGenerator::buildProductTag(...), $ids), ...array_map(EntityCacheKeyGenerator::buildStreamTag(...), $streamIds), ...[EntityCacheKeyGenerator::buildCmsTag($page->getId())]];
  136.     }
  137. }