vendor/shopware/core/Content/Cms/DataResolver/CmsSlotsDataResolver.php line 77

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Cms\DataResolver;
  3. use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotCollection;
  4. use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
  5. use Shopware\Core\Content\Cms\DataResolver\Element\CmsElementResolverInterface;
  6. use Shopware\Core\Content\Cms\DataResolver\Element\ElementDataCollection;
  7. use Shopware\Core\Content\Cms\DataResolver\ResolverContext\ResolverContext;
  8. use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Entity;
  10. use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
  11. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
  15. use Shopware\Core\Framework\Log\Package;
  16. use Shopware\Core\Framework\Struct\ArrayEntity;
  17. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  18. #[Package('content')]
  19. class CmsSlotsDataResolver
  20. {
  21.     /**
  22.      * @var CmsElementResolverInterface[]
  23.      */
  24.     private ?array $resolvers null;
  25.     private ?array $repositories null;
  26.     /**
  27.      * @internal
  28.      *
  29.      * @param CmsElementResolverInterface[] $resolvers
  30.      */
  31.     public function __construct(
  32.         iterable $resolvers,
  33.         array $repositories,
  34.         private readonly DefinitionInstanceRegistry $definitionRegistry
  35.     ) {
  36.         foreach ($repositories as $entityName => $repository) {
  37.             $this->repositories[$entityName] = $repository;
  38.         }
  39.         foreach ($resolvers as $resolver) {
  40.             $this->resolvers[$resolver->getType()] = $resolver;
  41.         }
  42.     }
  43.     public function resolve(CmsSlotCollection $slotsResolverContext $resolverContext): CmsSlotCollection
  44.     {
  45.         $slotCriteriaList = [];
  46.         /*
  47.          * Collect criteria objects for each slot from resolver
  48.          *
  49.          * @var CmsSlotEntity
  50.          */
  51.         foreach ($slots as $slot) {
  52.             $resolver $this->resolvers[$slot->getType()] ?? null;
  53.             if (!$resolver) {
  54.                 continue;
  55.             }
  56.             $collection $resolver->collect($slot$resolverContext);
  57.             if ($collection === null) {
  58.                 continue;
  59.             }
  60.             $slotCriteriaList[$slot->getUniqueIdentifier()] = $collection;
  61.         }
  62.         // reduce search requests by combining mergeable criteria objects
  63.         [$directReads$searches] = $this->optimizeCriteriaObjects($slotCriteriaList);
  64.         // fetch data from storage
  65.         $entities $this->fetchByIdentifier($directReads$resolverContext->getSalesChannelContext());
  66.         $searchResults $this->fetchByCriteria($searches$resolverContext->getSalesChannelContext());
  67.         // create result for each slot with the requested data
  68.         foreach ($slots as $slotId => $slot) {
  69.             $resolver $this->resolvers[$slot->getType()] ?? null;
  70.             if (!$resolver) {
  71.                 continue;
  72.             }
  73.             $result = new ElementDataCollection();
  74.             $this->mapSearchResults($result$slot$slotCriteriaList$searchResults);
  75.             $this->mapEntities($result$slot$slotCriteriaList$entities);
  76.             $resolver->enrich($slot$resolverContext$result);
  77.             // replace with return value from enrich(), because it's allowed to change the entity type
  78.             $slots->set($slotId$slot);
  79.         }
  80.         return $slots;
  81.     }
  82.     /**
  83.      * @param string[][] $directReads
  84.      *
  85.      * @throws InconsistentCriteriaIdsException
  86.      *
  87.      * @return EntitySearchResult[]
  88.      */
  89.     private function fetchByIdentifier(array $directReadsSalesChannelContext $context): array
  90.     {
  91.         $entities = [];
  92.         foreach ($directReads as $definitionClass => $ids) {
  93.             $definition $this->definitionRegistry->get($definitionClass);
  94.             $repository $this->getSalesChannelApiRepository($definition);
  95.             if ($repository) {
  96.                 $entities[$definitionClass] = $repository->search(new Criteria($ids), $context);
  97.             } else {
  98.                 $repository $this->getApiRepository($definition);
  99.                 $entities[$definitionClass] = $repository->search(new Criteria($ids), $context->getContext());
  100.             }
  101.         }
  102.         return $entities;
  103.     }
  104.     private function fetchByCriteria(array $searchesSalesChannelContext $context): array
  105.     {
  106.         $searchResults = [];
  107.         /** @var Criteria[] $criteriaObjects */
  108.         foreach ($searches as $definitionClass => $criteriaObjects) {
  109.             foreach ($criteriaObjects as $criteriaHash => $criteria) {
  110.                 $definition $this->definitionRegistry->get($definitionClass);
  111.                 $repository $this->getSalesChannelApiRepository($definition);
  112.                 if ($repository) {
  113.                     $result $repository->search($criteria$context);
  114.                 } else {
  115.                     $repository $this->getApiRepository($definition);
  116.                     $result $repository->search($criteria$context->getContext());
  117.                 }
  118.                 $searchResults[$criteriaHash] = $result;
  119.             }
  120.         }
  121.         return $searchResults;
  122.     }
  123.     /**
  124.      * @param CriteriaCollection[] $criteriaCollections
  125.      */
  126.     private function optimizeCriteriaObjects(array $criteriaCollections): array
  127.     {
  128.         $directReads = [];
  129.         $searches = [];
  130.         $criteriaCollection $this->flattenCriteriaCollections($criteriaCollections);
  131.         foreach ($criteriaCollection as $definition => $criteriaObjects) {
  132.             $directReads[$definition] = [[]];
  133.             $searches[$definition] = [];
  134.             /** @var Criteria $criteria */
  135.             foreach ($criteriaObjects as $criteria) {
  136.                 if ($this->canBeMerged($criteria)) {
  137.                     $directReads[$definition][] = $criteria->getIds();
  138.                 } else {
  139.                     $criteriaHash $this->hash($criteria);
  140.                     $criteria->addExtension('criteriaHash', new ArrayEntity(['hash' => $criteriaHash]));
  141.                     $searches[$definition][$criteriaHash] = $criteria;
  142.                 }
  143.             }
  144.         }
  145.         foreach ($directReads as $definition => $idLists) {
  146.             $directReads[$definition] = array_merge(...$idLists);
  147.         }
  148.         return [
  149.             array_filter($directReads),
  150.             array_filter($searches),
  151.         ];
  152.     }
  153.     private function canBeMerged(Criteria $criteria): bool
  154.     {
  155.         //paginated lists must be an own search
  156.         if ($criteria->getOffset() !== null || $criteria->getLimit() !== null) {
  157.             return false;
  158.         }
  159.         //sortings must be an own search
  160.         if (\count($criteria->getSorting())) {
  161.             return false;
  162.         }
  163.         //queries must be an own search
  164.         if (\count($criteria->getQueries())) {
  165.             return false;
  166.         }
  167.         if ($criteria->getAssociations()) {
  168.             return false;
  169.         }
  170.         if ($criteria->getAggregations()) {
  171.             return false;
  172.         }
  173.         $filters array_merge(
  174.             $criteria->getFilters(),
  175.             $criteria->getPostFilters()
  176.         );
  177.         // any kind of filters must be an own search
  178.         if (!empty($filters)) {
  179.             return false;
  180.         }
  181.         if (empty($criteria->getIds())) {
  182.             return false;
  183.         }
  184.         return true;
  185.     }
  186.     private function getApiRepository(EntityDefinition $definition): EntityRepository
  187.     {
  188.         return $this->definitionRegistry->getRepository($definition->getEntityName());
  189.     }
  190.     /**
  191.      * @return mixed|null
  192.      */
  193.     private function getSalesChannelApiRepository(EntityDefinition $definition)
  194.     {
  195.         return $this->repositories[$definition->getEntityName()] ?? null;
  196.     }
  197.     private function flattenCriteriaCollections(array $criteriaCollections): array
  198.     {
  199.         $flattened = [];
  200.         $criteriaCollections array_values($criteriaCollections);
  201.         foreach ($criteriaCollections as $collections) {
  202.             foreach ($collections as $definition => $criteriaObjects) {
  203.                 $flattened[$definition] = array_merge($flattened[$definition] ?? [], array_values($criteriaObjects));
  204.             }
  205.         }
  206.         return $flattened;
  207.     }
  208.     /**
  209.      * @param CriteriaCollection[] $criteriaObjects
  210.      * @param EntitySearchResult[] $searchResults
  211.      */
  212.     private function mapSearchResults(ElementDataCollection $resultCmsSlotEntity $slot, array $criteriaObjects, array $searchResults): void
  213.     {
  214.         if (!isset($criteriaObjects[$slot->getUniqueIdentifier()])) {
  215.             return;
  216.         }
  217.         foreach ($criteriaObjects[$slot->getUniqueIdentifier()] as $criterias) {
  218.             foreach ($criterias as $key => $criteria) {
  219.                 if (!$criteria->hasExtension('criteriaHash')) {
  220.                     continue;
  221.                 }
  222.                 /** @var ArrayEntity $hashArrayEntity */
  223.                 $hashArrayEntity $criteria->getExtension('criteriaHash');
  224.                 $hash $hashArrayEntity->get('hash');
  225.                 if (!isset($searchResults[$hash])) {
  226.                     continue;
  227.                 }
  228.                 $result->add($key$searchResults[$hash]);
  229.             }
  230.         }
  231.     }
  232.     /**
  233.      * @param CriteriaCollection[] $criteriaObjects
  234.      * @param EntitySearchResult[] $entities
  235.      */
  236.     private function mapEntities(ElementDataCollection $resultCmsSlotEntity $slot, array $criteriaObjects, array $entities): void
  237.     {
  238.         if (!isset($criteriaObjects[$slot->getUniqueIdentifier()])) {
  239.             return;
  240.         }
  241.         foreach ($criteriaObjects[$slot->getUniqueIdentifier()] as $definition => $criterias) {
  242.             foreach ($criterias as $key => $criteria) {
  243.                 if (!$this->canBeMerged($criteria)) {
  244.                     continue;
  245.                 }
  246.                 if (!isset($entities[$definition])) {
  247.                     continue;
  248.                 }
  249.                 $ids $criteria->getIds();
  250.                 $filtered $entities[$definition]->filter(fn (Entity $entity) => \in_array($entity->getUniqueIdentifier(), $idstrue));
  251.                 $result->add($key$filtered);
  252.             }
  253.         }
  254.     }
  255.     private function hash(Criteria $criteria): string
  256.     {
  257.         return md5(serialize($criteria));
  258.     }
  259. }