Newer
Older
use IQDEV\ElasticSearch\Config\MappingValidator;
use IQDEV\ElasticSearch\Configuration;
use IQDEV\ElasticSearch\Criteria;
use IQDEV\ElasticSearch\Document\Property\PropertyType;
use IQDEV\ElasticSearch\Request\Filter\Collection\FilterCollection;
use IQDEV\ElasticSearch\Request\Filter\Collection\FilterGroupCollection;
use IQDEV\ElasticSearch\Request\Filter\Collection\PostFilterCollection;
use IQDEV\ElasticSearch\Request\Filter\Collection\QueryFilterCollection;
use IQDEV\ElasticSearch\Request\Filter\Filter;
use IQDEV\ElasticSearch\Request\Filter\FilterOperator;
use IQDEV\ElasticSearch\Request\Filter\FilterType;
use IQDEV\ElasticSearch\Request\Filter\LogicOperator;
use IQDEV\ElasticSearch\Request\Filter\Value\FilterKeyword;
use IQDEV\ElasticSearch\Request\Filter\Value\FilterNumber;
use IQDEV\ElasticSearch\Request\Order\Order;
use IQDEV\ElasticSearch\Request\Search\Search;
use IQDEV\ElasticSearch\Request\Search\SearchQuery;
use IQDEV\ElasticSearch\Search\Aggs\Aggs;
use IQDEV\ElasticSearch\Search\Aggs\AggsFacetStats;
use IQDEV\ElasticSearch\Search\Aggs\AggsFacetTerms;
use IQDEV\ElasticSearch\Search\BoolQuery\FilterKeywordFacet;
use IQDEV\ElasticSearch\Search\BoolQuery\FilterNumberFacet;
use IQDEV\ElasticSearch\Search\BoolQuery\Query;
use IQDEV\ElasticSearch\Search\BoolQuery\Stats;
use IQDEV\ElasticSearch\Search\BoolQuery\Terms;
use IQDEV\ElasticSearch\Search\Nested;
use IQDEV\ElasticSearch\Search\Pagination;
use IQDEV\ElasticSearch\Search\Request;
final class CriteriaToEsRequest
{
public function __construct(
private readonly Configuration $configuration,
) {
}
public function fromCriteria(Criteria $criteria): Request
{
$request = new Request();
$request = $this->pagination($request, $criteria);
$request = $this->order($request, $criteria);
$request = $this->search($request, $criteria);
$request = $this->filter($request, $criteria);
$request = $this->aggs($request, $criteria);
return $request;
}
private function pagination(Request $request, Criteria $criteria): Request
{
$request = clone $request;
$request->setPagination(new Pagination($criteria->getPagination()->limit, $criteria->getPagination()->offset));
return $request;
}
private function order(Request $request, Criteria $criteria): Request
{
$request = clone $request;
if (true === $criteria->getSorting()->isEmpty()) {
foreach ($criteria->getSorting() as $order) {
private function search(Request $request, Criteria $criteria): Request
{
$request = clone $request;
foreach ($criteria->getSearch() as $search) {
/** @var Search $search */
$searchQuery = new SearchQuery($search);
if ($search->getProperty()->getType() === PropertyType::TEXT) {
$request->getQueryMatch()->add($searchQuery->toQueryMatch());
} else {
$request->getQuery()->getMust()->add($searchQuery->toMust($this->configuration));
}
}
return $request;
}
private function filter(Request $request, Criteria $criteria): Request
{
$request = clone $request;
if ($criteria->getFilters()->isEmpty()) {
$queryFilters = $criteria->getFilters()->getFilterCollectionByType(FilterType::QUERY);
$postFilters = $criteria->getFilters()->getFilterCollectionByType(FilterType::POST);
$this->addFilterToRequest($request, $queryFilters);
$this->addPostFilterToRequest($request, $postFilters);
return $request;
}
private function separatePropertyTypes(FilterCollection $filterCollection): array
{
$propertyFilter = new FilterCollection();
$nestedFilter = new FilterCollection();
foreach ($filterCollection as $groupFilter) {
/** @var FilterGroupCollection $groupFilter */
$propertyGroupCollection = new FilterGroupCollection();
$nestedGroupCollection = new FilterGroupCollection();
$propertyGroupCollection->setLogicOperator($groupFilter->getLogicOperator());
$nestedGroupCollection->setLogicOperator($groupFilter->getLogicOperator());
foreach ($groupFilter as $filter) {
/** @var Filter $filter */
if (true === MappingValidator::isPropertyExists($this->configuration, $filter->field()->value())) {
$propertyGroupCollection->add($filter);
} else {
$nestedGroupCollection->add($filter);
if (false === $propertyGroupCollection->isEmpty()) {
$propertyFilter->add($propertyGroupCollection);
}
if (false === $nestedGroupCollection->isEmpty()) {
$nestedFilter->add($nestedGroupCollection);
}
return [$propertyFilter, $nestedFilter];
}
private function addFilterToRequest(Request $request, QueryFilterCollection $filterCollection): void
{
[$propertyFilterCollection, $nestedFilterCollection] = $this->separatePropertyTypes($filterCollection);
$this->addPropertyFilterToRequest($request, $propertyFilterCollection);
$this->addNestedFilterToRequest($request, $nestedFilterCollection);
}
private function addPostFilterToRequest(Request $request, PostFilterCollection $filterCollection): void
{
[$propertyFilterCollection, $nestedFilterCollection] = $this->separatePropertyTypes($filterCollection);
$this->addPropertyPostFilterToRequest($request, $propertyFilterCollection);
$this->addNestedPostFilterToRequest($request, $nestedFilterCollection);
}
private function addPropertyFilterToRequest(Request $request, FilterCollection $filterCollection): void
{
$keywordFilterCollection = $this->getKeywordFilter($filterCollection);
foreach ($keywordFilterCollection->getFilter() as $filter) {
$request->getQuery()->getFilter()->add($filter);
}
foreach ($keywordFilterCollection->getShould() as $should) {
$request->getQuery()->getShould()->add($should);
$numberFilterCollection = $this->getNumberFilter($filterCollection);
foreach ($numberFilterCollection->getFilter() as $filter) {
$request->getQuery()->getFilter()->add($filter);
}
foreach ($numberFilterCollection->getShould() as $should) {
$request->getQuery()->getShould()->add($should);
}
}
private function addPropertyPostFilterToRequest(Request $request, FilterCollection $filterCollection): void
{
$keywordFilterCollection = $this->getKeywordFilter($filterCollection);
foreach ($keywordFilterCollection->getFilter() as $filter) {
$request->getPostFilter()->getFilter()->add($filter);
}
foreach ($keywordFilterCollection->getShould() as $should) {
$request->getPostFilter()->getShould()->add($should);
}
$numberFilterCollection = $this->getNumberFilter($filterCollection);
foreach ($numberFilterCollection->getFilter() as $filter) {
$request->getPostFilter()->getFilter()->add($filter);
}
foreach ($numberFilterCollection->getShould() as $should) {
$request->getPostFilter()->getShould()->add($should);
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
private function addNestedFilterToRequest(Request $request, FilterCollection $filterCollection): void
{
$keywordFilterCollection = $this->getKeywordFilter($filterCollection);
$keywordFilter = new Nested();
$keywordFilter->setPath('search_data');
if (false === $keywordFilterCollection->isEmpty()) {
$keywordNestedFilterQuery = clone $keywordFilter;
$keywordNestedFilterQuery->setQuery($keywordFilterCollection);
$request->getQuery()->getFilter()->add($keywordNestedFilterQuery);
}
$numberFilterCollection = $this->getNumberFilter($filterCollection);
$numberFilter = new Nested();
$numberFilter->setPath('search_data');
if (false === $numberFilterCollection->isEmpty()) {
$numberNestedFilterQuery = clone $numberFilter;
$numberNestedFilterQuery->setQuery($numberFilterCollection);
$request->getQuery()->getFilter()->add($numberNestedFilterQuery);
}
}
private function addNestedPostFilterToRequest(Request $request, FilterCollection $postFilter): void
{
$keywordFilterCollection = $this->getKeywordFilter($postFilter);
$keywordFilter = new Nested();
$keywordFilter->setPath('search_data');
if (false === $keywordFilterCollection->isEmpty()) {
$keywordNestedFilterQuery = clone $keywordFilter;
$keywordNestedFilterQuery->setQuery($keywordFilterCollection);
$request->getPostFilter()->getFilter()->add($keywordNestedFilterQuery);
}
$numberFilterCollection = $this->getNumberFilter($postFilter);
$numberFilter = new Nested();
$numberFilter->setPath('search_data');
if (false === $numberFilterCollection->isEmpty()) {
$numberNestedFilterQuery = clone $numberFilter;
$numberNestedFilterQuery->setQuery($numberFilterCollection);
$request->getPostFilter()->getFilter()->add($numberNestedFilterQuery);
}
private function getNumberFilter(FilterCollection $filterCollection, array $excludeFilter = []): Query
if ($filterCollection->isEmpty()) {
return $numberFilter;
}
foreach ($filterCollection as $filterGroup) {
/** @var FilterGroupCollection $filterGroup */
if ($filterGroup->isEmpty()) {
$group = $filterGroup->getLogicOperator() === LogicOperator::OR ? count($ranges) + 1 : 0;
if (!isset($ranges[$group])) {
$ranges[$group] = [];
foreach ($filterGroup as $filter) {
/** @var Filter $filter */
$value = $filter->value()->value();
$field = $filter->field()->value();
if (in_array($field, $excludeFilter, true)) {
continue;
}
if (in_array($filter->operator(), [FilterOperator::LT, FilterOperator::LTE], true)) {
$ranges[$group][$field][$filter->operator()->value] = $value;
if (in_array($filter->operator(), [FilterOperator::GT, FilterOperator::GTE], true)) {
$ranges[$group][$field][$filter->operator()->value] = $value;
foreach ($ranges as $iGroup => $group) {
foreach ($group as $field => $range) {
$isProperty = MappingValidator::isPropertyExists($this->configuration, $field);
$facet = $isProperty ? new Stats($field, $range) : new FilterNumberFacet(
$numberFilter->getFilter()->add($facet);
$numberFilter->getShould()->add($facet);
private function getKeywordFilter(FilterCollection $filterCollection, array $excludeFilter = []): Query
if ($filterCollection->isEmpty()) {
return $keywordFilter;
}
foreach ($filterCollection as $filterGroup) {
if ($filterGroup->isEmpty()) {
continue;
}
$should = $filterGroup->getLogicOperator() === LogicOperator::OR;
foreach ($filterGroup as $filter) {
/** @var Filter $filter */
$value = $filter->value()->value();
$field = $filter->field()->value();
$isProperty = MappingValidator::isPropertyExists($this->configuration, $field);
if (in_array($field, $excludeFilter, true)) {
continue;
}
if (in_array($filter->operator(), [FilterOperator::LT, FilterOperator::LTE], true)) {
if (in_array($filter->operator(), [FilterOperator::GT, FilterOperator::GTE], true)) {
if (is_array($value)) {
$value = array_map(static fn($v) => (string)$v, $value);
} else {
$value = (string)$value;
}
$keywordFilter->getShould()->add($isProperty ? new Terms($field, $value) : new FilterKeywordFacet($field, $value));
$keywordFilter->getFilter()->add($isProperty ? new Terms($field, $value) : new FilterKeywordFacet($field, $value));
}
}
return $keywordFilter;
}
private function aggs(Request $request, Criteria $criteria): Request
{
$request = clone $request;
if ($criteria->getFilters()->isEmpty() && $criteria->getSearch()->isEmpty()) {
return $request;
}
$request->getAggs()->add(
AggsFacetTerms::create(
'keyword_facet',
'keyword_facet'
)
);
$request->getAggs()->add(
AggsFacetStats::create(
'number_facet',
'number_facet'
)
);
$postFilters = $criteria->getFilters()->getFilterCollectionByType(FilterType::POST);
$getKey = static fn(string $type, string $name): string => sprintf('%s_facet_%s', $type, $name);
foreach ($postFilters as $filterGroup) {
/** @var FilterGroupCollection $filterGroup */
foreach ($filterGroup as $filter) {
/** @var Filter $filter */
$field = $filter->field()->value();
if ($filter->value() instanceof FilterNumber) {
if (in_array($filter->operator(), [], true)) {
if ($filter->value() instanceof FilterKeyword) {
$aggsFiltered = new Aggs($getKey('keyword', $field));
$aggsFiltered->addAggs(
AggsFacetTerms::create(
'agg_special',
'keyword_facet'
)
);
$queryKeywordFiltered = new Query();
$keywordFilter = $this->getKeywordFilter($postFilters, [$field]);
$numberFilter = $this->getNumberFilter($postFilters);
if (false === $keywordFilter->isEmpty()) {
$nestedFilterKeyword = new Nested();
$nestedFilterKeyword->setPath('search_data')
->setQuery($keywordFilter);
$queryKeywordFiltered->getFilter()->add($nestedFilterKeyword);
if (false === $numberFilter->isEmpty()) {
$nestedFilterNumber = new Nested();
$nestedFilterNumber->setPath('search_data')
->setQuery($numberFilter);
$queryKeywordFiltered->getFilter()->add($nestedFilterNumber);
if ($queryKeywordFiltered->isEmpty() === false) {
$aggsFiltered->setQuery($queryKeywordFiltered);
} else {
$aggsFiltered->setNested((new Nested())->setPath('search_data'));
}
$request->getAggs()->add($aggsFiltered);
}
$keywordFilter = $this->getKeywordFilter($postFilters);
$numberFilter = $this->getNumberFilter($postFilters);
$aggsKeywordFiltered = new Aggs('keyword_facet_filtered');
$aggsKeywordFiltered->addAggs(
AggsFacetTerms::create(
'all_keyword_facet_filtered',
'keyword_facet'
)
);
$queryKeywordFiltered = new Query();
$aggsNumberFiltered = new Aggs('number_facet_filtered');
$aggsNumberFiltered->addAggs(
AggsFacetStats::create(
'all_number_facet_filtered',
'number_facet'
)
);
$queryNumberFiltered = new Query();
if (false === $keywordFilter->isEmpty()) {
$nestedFilterKeyword = new Nested();
$nestedFilterKeyword->setPath('search_data')
->setQuery($keywordFilter);
$queryKeywordFiltered->getFilter()->add($nestedFilterKeyword);
$queryNumberFiltered->getFilter()->add($nestedFilterKeyword);
}
if (false === $numberFilter->isEmpty()) {
$nestedFilterNumber = new Nested();
$nestedFilterNumber->setPath('search_data')
->setQuery($numberFilter);
$queryKeywordFiltered->getFilter()->add($nestedFilterNumber);
$queryNumberFiltered->getFilter()->add($nestedFilterNumber);
}
if (false === $queryKeywordFiltered->isEmpty()) {
$aggsKeywordFiltered->setQuery($queryKeywordFiltered);
} else {
$aggsKeywordFiltered->setNested((new Nested())->setPath('search_data'));
}
if (false === $queryNumberFiltered->isEmpty()) {
$aggsNumberFiltered->setQuery($queryNumberFiltered);
} else {
$aggsNumberFiltered->setNested((new Nested())->setPath('search_data'));
$request->getAggs()
->add($aggsKeywordFiltered)
->add($aggsNumberFiltered);
return $request;