vendor/shopware/elasticsearch/Framework/DataAbstractionLayer/ElasticsearchEntitySearcher.php line 63

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Elasticsearch\Framework\DataAbstractionLayer;
  3. use Elasticsearch\Client;
  4. use ONGR\ElasticsearchDSL\Aggregation\AbstractAggregation;
  5. use ONGR\ElasticsearchDSL\Aggregation\Bucketing\FilterAggregation;
  6. use ONGR\ElasticsearchDSL\Aggregation\Metric\CardinalityAggregation;
  7. use ONGR\ElasticsearchDSL\Search;
  8. use Shopware\Core\Framework\Context;
  9. use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearcherInterface;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Search\Grouping\FieldGrouping;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\IdSearchResult;
  15. use Shopware\Elasticsearch\Framework\DataAbstractionLayer\Event\ElasticsearchEntitySearcherSearchEvent;
  16. use Shopware\Elasticsearch\Framework\ElasticsearchHelper;
  17. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  18. /**
  19.  * @package core
  20.  */
  21. class ElasticsearchEntitySearcher implements EntitySearcherInterface
  22. {
  23.     public const MAX_LIMIT 10000;
  24.     public const RESULT_STATE 'loaded-by-elastic';
  25.     private Client $client;
  26.     private EntitySearcherInterface $decorated;
  27.     private ElasticsearchHelper $helper;
  28.     private CriteriaParser $criteriaParser;
  29.     private AbstractElasticsearchSearchHydrator $hydrator;
  30.     private EventDispatcherInterface $eventDispatcher;
  31.     /**
  32.      * @internal
  33.      */
  34.     public function __construct(
  35.         Client $client,
  36.         EntitySearcherInterface $searcher,
  37.         ElasticsearchHelper $helper,
  38.         CriteriaParser $criteriaParser,
  39.         AbstractElasticsearchSearchHydrator $hydrator,
  40.         EventDispatcherInterface $eventDispatcher
  41.     ) {
  42.         $this->client $client;
  43.         $this->decorated $searcher;
  44.         $this->helper $helper;
  45.         $this->criteriaParser $criteriaParser;
  46.         $this->hydrator $hydrator;
  47.         $this->eventDispatcher $eventDispatcher;
  48.     }
  49.     public function search(EntityDefinition $definitionCriteria $criteriaContext $context): IdSearchResult
  50.     {
  51.         if (!$this->helper->allowSearch($definition$context$criteria)) {
  52.             return $this->decorated->search($definition$criteria$context);
  53.         }
  54.         if ($criteria->getLimit() === 0) {
  55.             return new IdSearchResult(0, [], $criteria$context);
  56.         }
  57.         $search $this->createSearch($criteria$definition$context);
  58.         $this->eventDispatcher->dispatch(
  59.             new ElasticsearchEntitySearcherSearchEvent(
  60.                 $search,
  61.                 $definition,
  62.                 $criteria,
  63.                 $context
  64.             )
  65.         );
  66.         $search $this->convertSearch($criteria$definition$context$search);
  67.         try {
  68.             $result $this->client->search([
  69.                 'index' => $this->helper->getIndexName($definition$context->getLanguageId()),
  70.                 'track_total_hits' => true,
  71.                 'body' => $search,
  72.             ]);
  73.         } catch (\Throwable $e) {
  74.             $this->helper->logAndThrowException($e);
  75.             return $this->decorated->search($definition$criteria$context);
  76.         }
  77.         $result $this->hydrator->hydrate($definition$criteria$context$result);
  78.         $result->addState(self::RESULT_STATE);
  79.         return $result;
  80.     }
  81.     private function createSearch(Criteria $criteriaEntityDefinition $definitionContext $context): Search
  82.     {
  83.         $search = new Search();
  84.         $this->helper->handleIds($definition$criteria$search$context);
  85.         $this->helper->addFilters($definition$criteria$search$context);
  86.         $this->helper->addPostFilters($definition$criteria$search$context);
  87.         $this->helper->addQueries($definition$criteria$search$context);
  88.         $this->helper->addSortings($definition$criteria$search$context);
  89.         $this->helper->addTerm($criteria$search$context$definition);
  90.         $search->setSize(self::MAX_LIMIT);
  91.         $limit $criteria->getLimit();
  92.         if ($limit !== null) {
  93.             $search->setSize($limit);
  94.         }
  95.         $search->setFrom((int) $criteria->getOffset());
  96.         return $search;
  97.     }
  98.     /**
  99.      * @return array<string, mixed>
  100.      */
  101.     private function convertSearch(Criteria $criteriaEntityDefinition $definitionContext $contextSearch $search): array
  102.     {
  103.         if (!$criteria->getGroupFields()) {
  104.             return $search->toArray();
  105.         }
  106.         $aggregation $this->buildTotalCountAggregation($criteria$definition$context);
  107.         $search->addAggregation($aggregation);
  108.         $array $search->toArray();
  109.         $array['collapse'] = $this->parseGrouping($criteria->getGroupFields(), $definition$context);
  110.         return $array;
  111.     }
  112.     /**
  113.      * @param FieldGrouping[] $groupings
  114.      *
  115.      * @return array{field: string, inner_hits?: array{name: string}}
  116.      */
  117.     private function parseGrouping(array $groupingsEntityDefinition $definitionContext $context): array
  118.     {
  119.         /** @var FieldGrouping $grouping */
  120.         $grouping array_shift($groupings);
  121.         $accessor $this->criteriaParser->buildAccessor($definition$grouping->getField(), $context);
  122.         if (empty($groupings)) {
  123.             return ['field' => $accessor];
  124.         }
  125.         return [
  126.             'field' => $accessor,
  127.             'inner_hits' => [
  128.                 'name' => 'inner',
  129.                 'collapse' => $this->parseGrouping($groupings$definition$context),
  130.             ],
  131.         ];
  132.     }
  133.     private function buildTotalCountAggregation(Criteria $criteriaEntityDefinition $definitionContext $context): AbstractAggregation
  134.     {
  135.         $groupings $criteria->getGroupFields();
  136.         if (\count($groupings) === 1) {
  137.             $first array_shift($groupings);
  138.             $accessor $this->criteriaParser->buildAccessor($definition$first->getField(), $context);
  139.             $aggregation = new CardinalityAggregation('total-count');
  140.             $aggregation->setField($accessor);
  141.             return $this->addPostFilterAggregation($criteria$definition$context$aggregation);
  142.         }
  143.         $fields = [];
  144.         foreach ($groupings as $grouping) {
  145.             $accessor $this->criteriaParser->buildAccessor($definition$grouping->getField(), $context);
  146.             $fields[] = sprintf(
  147.                 '
  148.                 if (doc[\'%s\'].size()==0) {
  149.                     value = value + \'empty\';
  150.                 } else {
  151.                     value = value + doc[\'%s\'].value;
  152.                 }',
  153.                 $accessor,
  154.                 $accessor
  155.             );
  156.         }
  157.         $script '
  158.             def value = \'\';
  159.             ' implode(' '$fields) . '
  160.             return value;
  161.         ';
  162.         $aggregation = new CardinalityAggregation('total-count');
  163.         $aggregation->setScript($script);
  164.         return $this->addPostFilterAggregation($criteria$definition$context$aggregation);
  165.     }
  166.     private function addPostFilterAggregation(Criteria $criteriaEntityDefinition $definitionContext $contextCardinalityAggregation $aggregation): AbstractAggregation
  167.     {
  168.         if (!$criteria->getPostFilters()) {
  169.             return $aggregation;
  170.         }
  171.         $query $this->criteriaParser->parseFilter(
  172.             new MultiFilter(MultiFilter::CONNECTION_AND$criteria->getPostFilters()),
  173.             $definition,
  174.             $definition->getEntityName(),
  175.             $context
  176.         );
  177.         $filterAgg = new FilterAggregation('total-filtered-count'$query);
  178.         $filterAgg->addAggregation($aggregation);
  179.         return $filterAgg;
  180.     }
  181. }