Skip to content
Snippets Groups Projects
CriteriaToEsRequest.php 13.2 KiB
Newer Older
Pavel's avatar
Pavel committed
<?php

namespace IQDEV\ElasticSearch\Converter;

use IQDEV\ElasticSearch\Order\OrderAscType;
use IQDEV\ElasticSearch\Order\OrderDescType;
use IQDEV\ElasticSearch\Order\OrderField;
use IQDEV\ElasticSearch\Order\OrderKeywordProperty;
use IQDEV\ElasticSearch\Order\OrderNumberProperty;
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\Terms;
use IQDEV\ElasticSearch\Search\Nested;
use IQDEV\ElasticSearch\Search\Pagination;
use IQDEV\ElasticSearch\Search\Request;
use IQDEV\Search\Criteria;
use IQDEV\Search\Document\Property\AttrType;
use IQDEV\Search\Document\Property\PropertyType;
use IQDEV\Search\FIlter\Filter;
use IQDEV\Search\Filter\FilterOperator;
use IQDEV\Search\Order\Order;
use IQDEV\Search\Order\OrderAscType as SOrderAscType;
use IQDEV\Search\Order\OrderDescType as SOrderDescType;

final class CriteriaToEsRequest
{
    public function fromCriteria(Criteria $criteria): Request
    {
        $request = new Request();
        $request = $this->pagination($request, $criteria);
        $request = $this->order($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->pagination()->limit, $criteria->pagination()->offset));

        return $request;
    }

    private function order(Request $request, Criteria $criteria): Request
    {
        $request = clone $request;

        if (true === $criteria->sorting()->isEmpty()) {
            return $request;
        }

        foreach ($criteria->sorting() as $order) {
            /** @var Order $order */
            $direction = null;
            if ($order->orderType() instanceof SOrderAscType) {
                $direction = new OrderAscType();
            }

            if ($order->orderType() instanceof SOrderDescType) {
                $direction = new OrderDescType();
            }

            if ($order->orderBy() instanceof AttrType) {
                $request->getSort()->add(new OrderField($order->orderBy()->key(), $direction));
            } elseif ($order->orderBy() instanceof PropertyType) {
                if ($order->orderBy()->type() === PropertyType::TYPE_KEYWORD) {
                    $request->getSort()->add(new OrderKeywordProperty($order->orderBy()->key(), $direction));
                } elseif ($order->orderBy()->type() === PropertyType::TYPE_NUMBER) {
                    $request->getSort()->add(new OrderNumberProperty($order->orderBy()->key(), $direction));
                }
            }
        }

        return $request;
    }

    private function filter(Request $request, Criteria $criteria): Request
    {
        $request = clone $request;
        if ($criteria->filters()->isEmpty()) {
            return $request;
        }

        foreach ($criteria->filters() as $filter) {
            /** @var Filter $filter */
            $value = $filter->value()->value();
            $field = $filter->field()->value();

            if ('search' === $field) {
                if ($filter->operator()->value() === FilterOperator::CONTAINS) {
                    $request->addMatch(
                        'suggest_search_content',
                        [
                            'query' => $value,
                        ],
                    );
                } else {
                    $request->addMatch(
                        'full_search_content',
                        [
                            'query' => $value,
                        ],
                    );
                }
                continue;
            }

            if ('category_id' === $field) {
                $request->getQuery()->must(
                    new Terms('category_id', $filter->value()->value())
                );
                continue;
            }
        }

        $keywordFilter = $this->getKeywordFilter($criteria);
        if (false === $keywordFilter->isEmpty()) {
            $keywordNestedFilter = new Nested();
            $keywordNestedFilter->setPath('search_data')
                ->setQuery($keywordFilter);

            $request->getPostFilter()->filter($keywordNestedFilter);
        }

        $numberFilter = $this->getNumberFilter($criteria);
        if (false === $numberFilter->isEmpty()) {
            $numberNestedFilter = new Nested();
            $numberNestedFilter->setPath('search_data')
                ->setQuery($numberFilter);

            $request->getPostFilter()->filter($numberNestedFilter);
        }

        return $request;
    }

    private function getNumberFilter(Criteria $criteria, array $excludeFilter = []): Query
    {
        $numberFilter = new Query();

        $ranges = [];

        foreach ($criteria->filters() 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()->value(), [FilterOperator::LT, FilterOperator::LTE], true)) {
                $ranges[$field][$filter->operator()->value()] = $value;
                continue;
            }

            if (in_array($filter->operator()->value(), [FilterOperator::GT, FilterOperator::GTE], true)) {
                $ranges[$field][$filter->operator()->value()] = $value;
            }
        }

        if (false === empty($ranges)) {
            foreach ($ranges as $field => $range) {
                $numberFilter->filter(
                    new FilterNumberFacet(
                        $field,
                        $range
                    )
                );
            }
        }

        return $numberFilter;
    }

    private function getKeywordFilter(Criteria $criteria, array $excludeFilter = []): Query
    {
        $keywordFilter = new Query();
        foreach ($criteria->filters() 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()->value(), [FilterOperator::LT, FilterOperator::LTE], true)) {
                continue;
            }

            if (in_array($filter->operator()->value(), [FilterOperator::GT, FilterOperator::GTE], true)) {
                continue;
            }

            if ('search' === $field) {
                continue;
            }

            if ('category_id' === $field) {
                continue;
            }

            if (is_array($value)) {
                $value = array_map(static fn($v) => (string)$v, $value);
            } else {
                $value = (string)$value;
            }

            $keywordFilter->filter(new FilterKeywordFacet($field, $value));
        }

        return $keywordFilter;
    }

    private function aggs(Request $request, Criteria $criteria): Request
    {
        $request = clone $request;

        if ($criteria->filters()->isEmpty()) {
            return $request;
        }

        $request->getAggs()->add(
            AggsFacetTerms::create(
                'keyword_facet',
                'keyword_facet'
            )
        );

        $request->getAggs()->add(
            AggsFacetStats::create(
                'number_facet',
                'number_facet'
            )
        );

        $getKey = static fn(string $type, string $name): string => sprintf('%s_facet_%s', $type, $name);
        foreach ($criteria->filters() as $filter) {
            /** @var Filter $filter */
            $field = $filter->field()->value();

            if (in_array(
                $filter->operator()->value(),
                [
                    FilterOperator::LT,
                    FilterOperator::LTE,
                    FilterOperator::GT,
                    FilterOperator::GTE,
                ],
                true
            )
            ) {
                $aggsFiltered = new Aggs($getKey('number', $field));
                $aggsFiltered->addAggs(
                    AggsFacetStats::create(
                        'agg_special',
                        'number_facet'
                    )
                );

                $queryNumberFiltered = new Query();
                $keywordFilter = $this->getKeywordFilter($criteria);
                $numberFilter = $this->getNumberFilter($criteria, [$field]);

                if (false === $keywordFilter->isEmpty()) {
                    $nestedFilterKeyword = new Nested();
                    $nestedFilterKeyword->setPath('search_data')
                        ->setQuery($keywordFilter);
                    $queryNumberFiltered->filter($nestedFilterKeyword);
                }

                if (false === $numberFilter->isEmpty()) {
                    $nestedFilterNumber = new Nested();
                    $nestedFilterNumber->setPath('search_data')
                        ->setQuery($numberFilter);
                    $queryNumberFiltered->filter($nestedFilterNumber);
                }

                if ($queryNumberFiltered->isEmpty() === false) {
                    $aggsFiltered->setQuery($queryNumberFiltered);
                } else {
                    $aggsFiltered->setNested((new Nested())->setPath('search_data'));
                }

                $request->getAggs()->add($aggsFiltered);
                continue;
            }

            if (in_array($filter->operator()->value(), [], true)) {
                continue;
            }

            if ('search' === $field) {
                continue;
            }

            if ('category_id' === $field) {
                continue;
            }

            $aggsFiltered = new Aggs($getKey('keyword', $field));
            $aggsFiltered->addAggs(
                AggsFacetTerms::create(
                    'agg_special',
                    'keyword_facet'
                )
            );
            $queryKeywordFiltered = new Query();
            $keywordFilter = $this->getKeywordFilter($criteria, [$field]);
            $numberFilter = $this->getNumberFilter($criteria);
            if (false === $keywordFilter->isEmpty()) {
                $nestedFilterKeyword = new Nested();
                $nestedFilterKeyword->setPath('search_data')
                    ->setQuery($keywordFilter);
                $queryKeywordFiltered->filter($nestedFilterKeyword);
            }

            if (false === $numberFilter->isEmpty()) {
                $nestedFilterNumber = new Nested();
                $nestedFilterNumber->setPath('search_data')
                    ->setQuery($numberFilter);
                $queryKeywordFiltered->filter($nestedFilterNumber);
            }

            if ($queryKeywordFiltered->isEmpty() === false) {
                $aggsFiltered->setQuery($queryKeywordFiltered);
            } else {
                $aggsFiltered->setNested((new Nested())->setPath('search_data'));
            }

            $request->getAggs()->add($aggsFiltered);
        }

        $keywordFilter = $this->getKeywordFilter($criteria);
        $numberFilter = $this->getNumberFilter($criteria);

        $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->filter($nestedFilterKeyword);
            $queryNumberFiltered->filter($nestedFilterKeyword);
        }

        if (false === $numberFilter->isEmpty()) {
            $nestedFilterNumber = new Nested();
            $nestedFilterNumber->setPath('search_data')
                ->setQuery($numberFilter);

            $queryKeywordFiltered->filter($nestedFilterNumber);
            $queryNumberFiltered->filter($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;
    }
}