vendor/shopware/core/System/SalesChannel/Entity/SalesChannelRepository.php line 208

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\System\SalesChannel\Entity;
  3. use Shopware\Core\Framework\DataAbstractionLayer\Entity;
  4. use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection;
  5. use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
  6. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityAggregationResultLoadedEvent;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityLoadedEventFactory;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntitySearchResultLoadedEvent;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Field\AssociationField;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Read\EntityReaderInterface;
  12. use Shopware\Core\Framework\DataAbstractionLayer\RepositorySearchDetector;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\AggregationResultCollection;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Search\EntityAggregatorInterface;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearcherInterface;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
  18. use Shopware\Core\Framework\DataAbstractionLayer\Search\IdSearchResult;
  19. use Shopware\Core\Framework\Feature;
  20. use Shopware\Core\Framework\Log\Package;
  21. use Shopware\Core\Framework\Struct\ArrayEntity;
  22. use Shopware\Core\System\SalesChannel\Event\SalesChannelProcessCriteriaEvent;
  23. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  24. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  25. /**
  26. * @final tag:v6.5.0
  27. */
  28. #[Package('sales-channel')]
  29. class SalesChannelRepository implements SalesChannelRepositoryInterface
  30. {
  31. /**
  32. * @var EntityDefinition
  33. */
  34. protected $definition;
  35. /**
  36. * @var EntityReaderInterface
  37. */
  38. protected $reader;
  39. /**
  40. * @var EntitySearcherInterface
  41. */
  42. protected $searcher;
  43. /**
  44. * @var EntityAggregatorInterface
  45. */
  46. protected $aggregator;
  47. /**
  48. * @var EventDispatcherInterface
  49. */
  50. protected $eventDispatcher;
  51. private ?EntityLoadedEventFactory $eventFactory;
  52. /**
  53. * @internal
  54. *
  55. * @deprecated tag:v6.5.0 - $eventFactory parameter will be required
  56. */
  57. public function __construct(
  58. EntityDefinition $definition,
  59. EntityReaderInterface $reader,
  60. EntitySearcherInterface $searcher,
  61. EntityAggregatorInterface $aggregator,
  62. EventDispatcherInterface $eventDispatcher,
  63. ?EntityLoadedEventFactory $eventFactory
  64. ) {
  65. $this->definition = $definition;
  66. $this->reader = $reader;
  67. $this->searcher = $searcher;
  68. $this->aggregator = $aggregator;
  69. $this->eventDispatcher = $eventDispatcher;
  70. if ($eventFactory !== null) {
  71. $this->eventFactory = $eventFactory;
  72. } else {
  73. Feature::triggerDeprecationOrThrow(
  74. 'v6.5.0.0',
  75. sprintf('SalesChannelRepository constructor for definition %s requires the event factory as required 6th parameter in v6.5.0.0', $definition->getEntityName())
  76. );
  77. }
  78. }
  79. /**
  80. * @deprecated tag:v6.5.0 - Will be removed
  81. */
  82. public function setEntityLoadedEventFactory(EntityLoadedEventFactory $eventFactory): void
  83. {
  84. Feature::triggerDeprecationOrThrow(
  85. 'v6.5.0.0',
  86. Feature::deprecatedMethodMessage(__CLASS__, __METHOD__, 'v6.5.0.0')
  87. );
  88. $this->eventFactory = $eventFactory;
  89. }
  90. /**
  91. * @throws InconsistentCriteriaIdsException
  92. */
  93. public function search(Criteria $criteria, SalesChannelContext $salesChannelContext): EntitySearchResult
  94. {
  95. $criteria = clone $criteria;
  96. $this->processCriteria($criteria, $salesChannelContext);
  97. $aggregations = null;
  98. if ($criteria->getAggregations()) {
  99. $aggregations = $this->aggregate($criteria, $salesChannelContext);
  100. }
  101. if (!RepositorySearchDetector::isSearchRequired($this->definition, $criteria)) {
  102. $entities = $this->read($criteria, $salesChannelContext);
  103. return new EntitySearchResult($this->definition->getEntityName(), $entities->count(), $entities, $aggregations, $criteria, $salesChannelContext->getContext());
  104. }
  105. $ids = $this->doSearch($criteria, $salesChannelContext);
  106. if (empty($ids->getIds())) {
  107. /** @var EntityCollection<Entity> $collection */
  108. $collection = $this->definition->getCollectionClass();
  109. return new EntitySearchResult($this->definition->getEntityName(), $ids->getTotal(), new $collection(), $aggregations, $criteria, $salesChannelContext->getContext());
  110. }
  111. $readCriteria = $criteria->cloneForRead($ids->getIds());
  112. $entities = $this->read($readCriteria, $salesChannelContext);
  113. $search = $ids->getData();
  114. /** @var Entity $element */
  115. foreach ($entities as $element) {
  116. if (!\array_key_exists($element->getUniqueIdentifier(), $search)) {
  117. continue;
  118. }
  119. $data = $search[$element->getUniqueIdentifier()];
  120. unset($data['id']);
  121. if (empty($data)) {
  122. continue;
  123. }
  124. $element->addExtension('search', new ArrayEntity($data));
  125. }
  126. $result = new EntitySearchResult($this->definition->getEntityName(), $ids->getTotal(), $entities, $aggregations, $criteria, $salesChannelContext->getContext());
  127. $result->addState(...$ids->getStates());
  128. $event = new EntitySearchResultLoadedEvent($this->definition, $result);
  129. $this->eventDispatcher->dispatch($event, $event->getName());
  130. $event = new SalesChannelEntitySearchResultLoadedEvent($this->definition, $result, $salesChannelContext);
  131. $this->eventDispatcher->dispatch($event, $event->getName());
  132. return $result;
  133. }
  134. public function aggregate(Criteria $criteria, SalesChannelContext $salesChannelContext): AggregationResultCollection
  135. {
  136. $criteria = clone $criteria;
  137. $this->processCriteria($criteria, $salesChannelContext);
  138. $result = $this->aggregator->aggregate($this->definition, $criteria, $salesChannelContext->getContext());
  139. $event = new EntityAggregationResultLoadedEvent($this->definition, $result, $salesChannelContext->getContext());
  140. $this->eventDispatcher->dispatch($event, $event->getName());
  141. return $result;
  142. }
  143. public function searchIds(Criteria $criteria, SalesChannelContext $salesChannelContext): IdSearchResult
  144. {
  145. $criteria = clone $criteria;
  146. $this->processCriteria($criteria, $salesChannelContext);
  147. return $this->doSearch($criteria, $salesChannelContext);
  148. }
  149. /**
  150. * @return EntityCollection<Entity>
  151. */
  152. private function read(Criteria $criteria, SalesChannelContext $salesChannelContext): EntityCollection
  153. {
  154. $criteria = clone $criteria;
  155. $entities = $this->reader->read($this->definition, $criteria, $salesChannelContext->getContext());
  156. if ($this->eventFactory === null) {
  157. throw new \RuntimeException('Event loaded factory was not injected');
  158. }
  159. if ($criteria->getFields() === []) {
  160. $events = $this->eventFactory->createForSalesChannel($entities->getElements(), $salesChannelContext);
  161. } else {
  162. $events = $this->eventFactory->createPartialForSalesChannel($entities->getElements(), $salesChannelContext);
  163. }
  164. foreach ($events as $event) {
  165. $this->eventDispatcher->dispatch($event);
  166. }
  167. return $entities;
  168. }
  169. private function doSearch(Criteria $criteria, SalesChannelContext $salesChannelContext): IdSearchResult
  170. {
  171. $result = $this->searcher->search($this->definition, $criteria, $salesChannelContext->getContext());
  172. $event = new SalesChannelEntityIdSearchResultLoadedEvent($this->definition, $result, $salesChannelContext);
  173. $this->eventDispatcher->dispatch($event, $event->getName());
  174. return $result;
  175. }
  176. private function processCriteria(Criteria $topCriteria, SalesChannelContext $salesChannelContext): void
  177. {
  178. if (!$this->definition instanceof SalesChannelDefinitionInterface) {
  179. return;
  180. }
  181. $queue = [
  182. ['definition' => $this->definition, 'criteria' => $topCriteria],
  183. ];
  184. $maxCount = 100;
  185. $processed = [];
  186. // process all associations breadth-first
  187. while (!empty($queue) && --$maxCount > 0) {
  188. $cur = array_shift($queue);
  189. /** @var EntityDefinition $definition */
  190. $definition = $cur['definition'];
  191. $criteria = $cur['criteria'];
  192. if (isset($processed[\get_class($definition)])) {
  193. continue;
  194. }
  195. if ($definition instanceof SalesChannelDefinitionInterface) {
  196. $definition->processCriteria($criteria, $salesChannelContext);
  197. $eventName = \sprintf('sales_channel.%s.process.criteria', $definition->getEntityName());
  198. $event = new SalesChannelProcessCriteriaEvent($criteria, $salesChannelContext);
  199. $this->eventDispatcher->dispatch($event, $eventName);
  200. }
  201. $processed[\get_class($definition)] = true;
  202. foreach ($criteria->getAssociations() as $associationName => $associationCriteria) {
  203. // find definition
  204. $field = $definition->getField($associationName);
  205. if (!$field instanceof AssociationField) {
  206. continue;
  207. }
  208. $referenceDefinition = $field->getReferenceDefinition();
  209. $queue[] = ['definition' => $referenceDefinition, 'criteria' => $associationCriteria];
  210. }
  211. }
  212. }
  213. }