src/Core/Content/Cms/DataResolver/CmsSlotsDataResolver.php line 118

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