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

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