Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Commits on Source (11)
Showing
with 1105 additions and 477 deletions
<?php
namespace IQDEV\ElasticSearch\Converter;
use IQDEV\ElasticSearch\Filter\PostFilterCollection;
use IQDEV\ElasticSearch\Filter\QueryFilterCollection;
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\FilterCollection;
use IQDEV\Search\Filter\FilterGroupCollection;
use IQDEV\Search\Filter\FilterKeyword;
use IQDEV\Search\Filter\FilterNumber;
use IQDEV\Search\Filter\FilterOperator;
use IQDEV\Search\Filter\FilterType;
use IQDEV\Search\Filter\LogicOperator;
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 $filterGroup) {
/** @var FilterGroupCollection $filterGroup */
foreach ($filterGroup 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;
}
}
}
[$queryFilters, $postFilters] = $this->groupFilters($criteria->filters());
$keywordQueryFilter = $this->getKeywordFilter($queryFilters);
$keywordPostFilter = $this->getKeywordFilter($postFilters);
if (false === $keywordQueryFilter->isEmpty() || false === $keywordPostFilter->isEmpty()) {
$keywordNestedFilter = new Nested();
$keywordNestedFilter->setPath('search_data');
if (false === $keywordQueryFilter->isEmpty()) {
$keywordNestedFilterQuery = clone $keywordNestedFilter;
$keywordNestedFilterQuery->setQuery($keywordQueryFilter);
$request->getQuery()->filter($keywordNestedFilterQuery);
}
if (false === $keywordPostFilter->isEmpty()) {
$keywordNestedFilterPost = clone $keywordNestedFilter;
$keywordNestedFilterPost->setQuery($keywordPostFilter);
$request->getPostFilter()->filter($keywordNestedFilterPost);
}
}
$numberQueryFilter = $this->getNumberFilter($queryFilters);
$numberPostFilter = $this->getNumberFilter($postFilters);
if (false === $numberQueryFilter->isEmpty() || false === $numberPostFilter->isEmpty()) {
$numberNestedFilter = new Nested();
$numberNestedFilter->setPath('search_data');
if (false === $numberQueryFilter->isEmpty()) {
$numberNestedFilterQuery = clone $numberNestedFilter;
$numberNestedFilterQuery->setQuery($numberQueryFilter);
$request->getQuery()->filter($numberNestedFilterQuery);
}
if (false === $numberPostFilter->isEmpty()) {
$numberNestedFilterPost = clone $numberNestedFilter;
$numberNestedFilterPost->setQuery($numberPostFilter);
$request->getPostFilter()->filter($numberNestedFilterPost);
}
}
return $request;
}
private function getNumberFilter(FilterCollection $filterCollection, array $excludeFilter = []): Query
{
$numberFilter = new Query();
if ($filterCollection->isEmpty()) {
return $numberFilter;
}
$ranges = [];
foreach ($filterCollection as $filterGroup) {
/** @var FilterGroupCollection $filterGroup */
if ($filterGroup->isEmpty()) {
continue;
}
$group = $filterGroup->getLogicalType()->value() === 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()->value(), [FilterOperator::LT, FilterOperator::LTE], true)) {
$ranges[$group][$field][$filter->operator()->value()] = $value;
continue;
}
if (in_array($filter->operator()->value(), [FilterOperator::GT, FilterOperator::GTE], true)) {
$ranges[$group][$field][$filter->operator()->value()] = $value;
}
}
}
if (false === empty($ranges)) {
foreach ($ranges as $iGroup => $group) {
foreach ($group as $field => $range) {
$facet = new FilterNumberFacet(
$field,
$range
);
if ($iGroup === 0) {
$numberFilter->filter($facet);
} else {
$numberFilter->should($facet);
}
}
}
}
return $numberFilter;
}
private function getKeywordFilter(FilterCollection $filterCollection, array $excludeFilter = []): Query
{
$keywordFilter = new Query();
if ($filterCollection->isEmpty()) {
return $keywordFilter;
}
foreach ($filterCollection as $filterGroup) {
/** @var FilterGroupCollection $filterGroup */
if ($filterGroup->isEmpty()) {
continue;
}
$should = $filterGroup->getLogicalType()->value() === LogicOperator::OR;
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()->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;
}
if ($should) {
$keywordFilter->should(new FilterKeywordFacet($field, $value));
} else {
$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'
)
);
[$queryFilters, $postFilters] = $this->groupFilters($criteria->filters());
$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) {
continue;
}
if (in_array($filter->operator()->value(), [], true)) {
continue;
}
if ('search' === $field) {
continue;
}
if ('category_id' === $field) {
continue;
}
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->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($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->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;
}
/**
* @param FilterCollection $filters
* @return FilterCollection[]
*/
private function groupFilters(FilterCollection $filters): array
{
$queryFilters = new QueryFilterCollection();
$postFilters = new PostFilterCollection();
foreach ($filters as $filterGroup) {
/** @var FilterGroupCollection $filterGroup */
if ($filterGroup->isEmpty()) {
continue;
}
switch ($filterGroup->getFilterType()->value()) {
case FilterType::QUERY:
$queryFilters->add($filterGroup);
break;
case FilterType::POST:
$postFilters->add($filterGroup);
break;
}
}
return [$queryFilters, $postFilters];
}
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request\Aggregation;
use IQDEV\ElasticSearch\Configuration;
use IQDEV\ElasticSearch\Criteria\Criteria;
use IQDEV\ElasticSearch\Criteria\Filter\FilterType;
use IQDEV\ElasticSearch\Search\Aggs\AggsCollection;
use IQDEV\ElasticSearch\Search\Aggs\AggsFacetStats;
use IQDEV\ElasticSearch\Search\Aggs\AggsFacetTerms;
class Aggregation
{
public function __construct(
private readonly Configuration $configuration,
private readonly Criteria $criteria,
private readonly AggsCollection $aggregations = new AggsCollection(),
) {
$this->convertToQuery();
}
public function convertToQuery(): void
{
$this->aggregations->add(
AggsFacetTerms::create(
'keyword_facet',
'keyword_facet'
)
);
$this->aggregations->add(
AggsFacetStats::create(
'number_facet',
'number_facet'
)
);
$postFilterCollection = $this->criteria->getFilters()->getFilterCollectionByType(FilterType::POST);
$filterAggregation = new FilterAggregation($this->configuration, $postFilterCollection);
$filterAggregation->updateRequestAggregation($this->aggregations);
$fullAggregation = new FullAggregation($this->configuration, $postFilterCollection);
$fullAggregation->updateRequestAggregation($this->aggregations);
}
public function getAggregation(): AggsCollection
{
return $this->aggregations;
}
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request\Aggregation;
use IQDEV\ElasticSearch\Configuration;
use IQDEV\ElasticSearch\Converter\Request\FilterQuery;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\FilterCollection;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\FilterGroupCollection;
use IQDEV\ElasticSearch\Criteria\Filter\Filter;
use IQDEV\ElasticSearch\Search\Aggs\Aggs;
use IQDEV\ElasticSearch\Search\Aggs\AggsCollection;
use IQDEV\ElasticSearch\Search\Aggs\AggsFacetTerms;
use IQDEV\ElasticSearch\Search\Nested;
class FilterAggregation
{
private ?Aggs $aggregation = null;
public function __construct(
private readonly Configuration $configuration,
private readonly FilterCollection $filterCollection,
) {
}
public function updateRequestAggregation(AggsCollection $original): void
{
foreach ($this->filterCollection as $filterGroup) {
/** @var FilterGroupCollection $filterGroup */
foreach ($filterGroup as $filter) {
/** @var Filter $filter */
$field = $filter->field()->value();
$aggregation = new Aggs($this->getKey('keyword', $field));
$aggregation->addAggs(
AggsFacetTerms::create(
'agg_special',
'keyword_facet'
)
);
$queryFilterBuilder = new FilterQuery($this->configuration, $this->filterCollection, [$field]);
$query = $queryFilterBuilder->getQuery();
if (false === $query->isEmpty()) {
$aggregation->setQuery($query);
} else {
$aggregation->setNested((new Nested())->setPath('search_data'));
}
$original->add($aggregation);
}
}
}
private function getKey(string $type, string $name): string
{
return sprintf('%s_facet_%s', $type, $name);
}
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request\Aggregation;
use IQDEV\ElasticSearch\Configuration;
use IQDEV\ElasticSearch\Converter\Request\FilterQuery;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\FilterCollection;
use IQDEV\ElasticSearch\Search\Aggs\Aggs;
use IQDEV\ElasticSearch\Search\Aggs\AggsCollection;
use IQDEV\ElasticSearch\Search\Aggs\AggsFacetStats;
use IQDEV\ElasticSearch\Search\Aggs\AggsFacetTerms;
use IQDEV\ElasticSearch\Search\Nested;
class FullAggregation
{
public function __construct(
private readonly Configuration $configuration,
private readonly FilterCollection $filterCollection,
) {
}
public function updateRequestAggregation(AggsCollection $original): void
{
$keywordQuery = (new FilterQuery($this->configuration, $this->filterCollection->getKeywordFilters()))->getQuery();
$numberQuery = (new FilterQuery($this->configuration, $this->filterCollection->getNumberFilters()))->getQuery();
$aggsKeywordFiltered = new Aggs('keyword_facet_filtered');
$aggsKeywordFiltered->addAggs(
AggsFacetTerms::create(
'all_keyword_facet_filtered',
'keyword_facet'
)
);
$aggsNumberFiltered = new Aggs('number_facet_filtered');
$aggsNumberFiltered->addAggs(
AggsFacetStats::create(
'all_number_facet_filtered',
'number_facet'
)
);
if (false === $keywordQuery->getFilter()->isEmpty()) {
foreach ($keywordQuery->getFilter() as $item) {
$numberQuery->getFilter()->add($item);
}
}
if (false === $numberQuery->getFilter()->isEmpty()) {
foreach ($numberQuery->getFilter() as $item) {
$keywordQuery->getFilter()->add($item);
}
}
if (false === $keywordQuery->getFilter()->isEmpty()) {
$aggsKeywordFiltered->setQuery($keywordQuery);
} else {
$aggsKeywordFiltered->setNested((new Nested())->setPath('search_data'));
}
if (false === $numberQuery->getFilter()->isEmpty()) {
$aggsNumberFiltered->setQuery($numberQuery);
} else {
$aggsNumberFiltered->setNested((new Nested())->setPath('search_data'));
}
$original->add($aggsKeywordFiltered)->add($aggsNumberFiltered);
}
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request\Collection;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\FilterCollection;
class NestedFilterCollection extends FilterCollection
{
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request\Collection;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\FilterCollection;
class PropertyFilterCollection extends FilterCollection
{
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request;
use IQDEV\ElasticSearch\Configuration;
use IQDEV\ElasticSearch\Converter\Request\Aggregation\Aggregation;
use IQDEV\ElasticSearch\Criteria\Criteria;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\PostFilterCollection;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\QueryFilterCollection;
use IQDEV\ElasticSearch\Criteria\Search\Search;
use IQDEV\ElasticSearch\Criteria\Search\SearchQuery;
use IQDEV\ElasticSearch\Document\Property\PropertyType;
use IQDEV\ElasticSearch\Search\Pagination;
class CriteriaRequestBuilder extends RequestBuilder
{
public function __construct(
private readonly Configuration $configuration,
private readonly Criteria $criteria
) {
parent::__construct();
}
public function build(): void
{
$this->setPagination();
if (false === $this->criteria->getSorting()->isEmpty()) {
$this->setSort();
}
if (false === $this->criteria->getSearch()->isEmpty()) {
$this->setSearch();
}
if (false === $this->criteria->getFilters()->isEmpty()) {
$this->setFilter();
}
if (false === $this->criteria->getSearch()->isEmpty() || false === $this->criteria->getFilters()->isEmpty()) {
$this->setAggregations();
}
}
public function setPagination(): void
{
$request = clone $this->request;
$request->setPagination(new Pagination(
$this->criteria->getPagination()->limit,
$this->criteria->getPagination()->offset
));
$this->request = $request;
}
public function setSort(): void
{
$request = clone $this->request;
foreach ($this->criteria->getSorting() as $sort) {
$request->getSort()->add($sort);
}
$this->request = $request;
}
public function setSearch(): void
{
$request = clone $this->request;
foreach ($this->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));
}
}
$this->request = $request;
}
public function setFilter(): void
{
$queryFilterCollection = $this->criteria->getFilters()->getQueryFilterCollection();
if (false === $queryFilterCollection->isEmpty()) {
$this->setQueryFilter($queryFilterCollection);
}
$postFilterCollection = $this->criteria->getFilters()->getPostFilterCollection();
if (false === $postFilterCollection->isEmpty()) {
$this->setPostFilter($postFilterCollection);
}
}
private function setQueryFilter(QueryFilterCollection $filterCollection): void
{
$request = clone $this->request;
$filterQuery = new FilterQuery($this->configuration, $filterCollection);
$request->getQuery()->modify($filterQuery->getQuery());
$this->request = $request;
}
private function setPostFilter(PostFilterCollection $filterCollection): void
{
$request = clone $this->request;
$filterQuery = new FilterQuery($this->configuration, $filterCollection);
$request->getPostFilter()->modify($filterQuery->getQuery());
$this->request = $request;
}
public function setAggregations(): void
{
$request = clone $this->request;
$aggregation = new Aggregation($this->configuration, $this->criteria);
$request->setAggs($aggregation->getAggregation());
$this->request = $request;
}
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request;
use IQDEV\ElasticSearch\Configuration;
use IQDEV\ElasticSearch\Criteria\Criteria;
use IQDEV\ElasticSearch\Search\Request;
final class CriteriaToRequest
{
public function __construct(
private readonly Configuration $configuration,
) {
}
public function fromCriteria(Criteria $criteria): Request
{
$builder = new CriteriaRequestBuilder($this->configuration, $criteria);
$builder->build();
return $builder->getRequest();
}
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request\Filter;
use IQDEV\ElasticSearch\Esable;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\FilterGroupCollection;
use IQDEV\ElasticSearch\Criteria\Filter\LogicOperator;
use IQDEV\ElasticSearch\Search\BoolQuery\Query;
use IQDEV\ElasticSearch\Search\Nested;
abstract class AbstractFilterQuery
{
protected Query $query;
public function __construct() {
$this->query = new Query();
}
protected function setFilterByLogic(Query $query, LogicOperator $logicOperator, Esable $filter): void
{
match ($logicOperator) {
LogicOperator::AND => $query->getFilter()->add($filter),
LogicOperator::OR => $query->getShould()->add($filter),
LogicOperator::NOT => $query->getMustNot()->add($filter),
};
}
abstract public function getQuery(FilterGroupCollection $filterGroupCollection, array $exclude): Query|Nested;
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request\Filter;
use IQDEV\ElasticSearch\Converter\Request\Filter\Type\KeywordFilterType;
use IQDEV\ElasticSearch\Converter\Request\Filter\Type\RangeFilterType;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\FilterGroupCollection;
use IQDEV\ElasticSearch\Criteria\Filter\Filter;
use IQDEV\ElasticSearch\Search\BoolQuery\Query;
use IQDEV\ElasticSearch\Search\BoolQuery\Terms;
use IQDEV\ElasticSearch\Search\Nested;
class NestedFilter extends AbstractFilterQuery
{
public const NESTED_RANGE_PATH = 'search_data.number_facet';
public const NESTED_KEYWORD_PATH = 'search_data.keyword_facet';
public function getQuery(FilterGroupCollection $filterGroupCollection, array $exclude): Nested
{
$query = new Query();
$keywordFiltersGroup = $filterGroupCollection->getKeywordFilters($exclude);
foreach ($keywordFiltersGroup as $keywordFilter) {
/** @var Filter $keywordFilter */
$esableFilter = new KeywordFilterType($keywordFilter);
$this->setFilterByLogic($query, $filterGroupCollection->getLogicOperator(), $this->getNested($esableFilter, self::NESTED_KEYWORD_PATH));
}
$rangeFilterGroup = $filterGroupCollection->getRangeFilters($exclude);
$rangesFilter = RangeFilterType::getFiltersByOneProperty($rangeFilterGroup);
foreach ($rangesFilter as $filterGroup) {
/** @var FilterGroupCollection $filterGroup */
$esableFilter = new RangeFilterType($filterGroup);
$this->setFilterByLogic($query, $filterGroupCollection->getLogicOperator(), $this->getNested($esableFilter, self::NESTED_RANGE_PATH));
}
return (new Nested())
->setPath('search_data')
->setQuery($query);
}
private function getNested(RangeFilterType|KeywordFilterType $filter, string $path): Nested
{
$cloneFilter = clone $filter;
$nested = new Nested();
$query = new Query();
$query->getFilter()->add(new Terms($path . '.facet_code', $cloneFilter->getField()));
$cloneFilter->setField($path . '.facet_value');
$query->getFilter()->add($cloneFilter->getEsable());
$nested
->setPath($path)
->setQuery($query);
return $nested;
}
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request\Filter;
use IQDEV\ElasticSearch\Converter\Request\Filter\Type\KeywordFilterType;
use IQDEV\ElasticSearch\Converter\Request\Filter\Type\RangeFilterType;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\FilterGroupCollection;
use IQDEV\ElasticSearch\Criteria\Filter\Filter;
use IQDEV\ElasticSearch\Search\BoolQuery\Query;
class PropertyFilter extends AbstractFilterQuery
{
public function getQuery(FilterGroupCollection $filterGroupCollection, array $exclude = []): Query
{
$query = new Query();
$keywordFiltersGroup = $filterGroupCollection->getKeywordFilters($exclude);
foreach ($keywordFiltersGroup as $keywordFilter) {
/** @var Filter $keywordFilter */
$esableFilter = new KeywordFilterType($keywordFilter);
$this->setFilterByLogic($query, $filterGroupCollection->getLogicOperator(), $esableFilter->getEsable());
}
$rangeFilterGroup = $filterGroupCollection->getRangeFilters($exclude);
$rangesFilter = RangeFilterType::getFiltersByOneProperty($rangeFilterGroup);
foreach ($rangesFilter as $filterGroup) {
/** @var FilterGroupCollection $filterGroup */
$esableFilter = new RangeFilterType($filterGroup);
$this->setFilterByLogic($query, $filterGroupCollection->getLogicOperator(), $esableFilter->getEsable());
}
return $query;
}
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request\Filter\Type;
class AbstractFilterType
{
protected string $field = '';
public function getField(): string
{
return $this->field;
}
public function setField(string $field): self
{
$this->field = $field;
return $this;
}
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request\Filter\Type;
use IQDEV\ElasticSearch\Esable;
use IQDEV\ElasticSearch\Criteria\Filter\Filter;
use IQDEV\ElasticSearch\Search\BoolQuery\Terms;
class KeywordFilterType extends AbstractFilterType
{
public function __construct(
private readonly Filter $filter,
) {
$this->field = $this->filter->field()->value();
}
public function getEsable(): Esable
{
return new Terms($this->field, $this->filter->value()->value());
}
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request\Filter\Type;
use IQDEV\ElasticSearch\Esable;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\FilterCollection;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\FilterGroupCollection;
use IQDEV\ElasticSearch\Criteria\Filter\Filter;
use IQDEV\ElasticSearch\Search\BoolQuery\Stats;
class RangeFilterType extends AbstractFilterType
{
public function __construct(
private readonly FilterGroupCollection $filterGroupCollection,
) {
$this->field = $this->filterGroupCollection->first()->field()->value();
}
public function getEsable(): Esable
{
$ranges = [];
foreach ($this->filterGroupCollection as $filter) {
/** @var Filter $filter */
$value = $filter->value()->value();
$ranges[$filter->operator()->value] = $value;
}
return new Stats($this->field, $ranges);
}
public static function getFiltersByOneProperty(FilterGroupCollection $filterGroupCollection): FilterCollection
{
$rangeFilters = new FilterCollection();
$properties = [];
foreach ($filterGroupCollection as $filter) {
/** @var Filter $filter */
$properties[$filter->field()->value()][] = $filter;
}
foreach ($properties as $propertyFilters) {
$rangeFilters->add(new FilterGroupCollection($propertyFilters));
}
return $rangeFilters;
}
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request;
use IQDEV\ElasticSearch\Config\MappingValidator;
use IQDEV\ElasticSearch\Configuration;
use IQDEV\ElasticSearch\Converter\Request\Collection\NestedFilterCollection;
use IQDEV\ElasticSearch\Converter\Request\Collection\PropertyFilterCollection;
use IQDEV\ElasticSearch\Converter\Request\Filter\AbstractFilterQuery;
use IQDEV\ElasticSearch\Converter\Request\Filter\NestedFilter;
use IQDEV\ElasticSearch\Converter\Request\Filter\PropertyFilter;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\FilterCollection;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\FilterGroupCollection;
use IQDEV\ElasticSearch\Criteria\Filter\Filter;
use IQDEV\ElasticSearch\Search\BoolQuery\Query;
use IQDEV\ElasticSearch\Search\Nested;
class FilterQuery
{
private Query $query;
public function __construct(
private readonly Configuration $configuration,
private readonly FilterCollection $filterCollection,
private array $exclude = [],
) {
$this->query = new Query();
$this->convertToQuery();
}
private function convertToQuery(): void
{
[$propertyFilterCollection, $nestedFilterCollection] = $this->separatePropertyTypes($this->filterCollection);
if (false === $propertyFilterCollection->isEmpty()) {
$this->fillQuery($propertyFilterCollection, new PropertyFilter());
}
if (false === $nestedFilterCollection->isEmpty()) {
$this->fillQuery($nestedFilterCollection, new NestedFilter());
}
}
private function fillQuery(FilterCollection $filterCollection, AbstractFilterQuery $filterQuery): void
{
foreach ($filterCollection as $filterGroup) {
/** @var FilterGroupCollection $filterGroup */
$this->setQuery($filterGroup, $filterQuery);
}
}
private function setQuery(FilterGroupCollection $filterGroup, AbstractFilterQuery $filterQuery): void
{
$filters = $filterQuery->getQuery($filterGroup, $this->exclude);
if ($filters instanceof Query) {
if ($filters->isEmpty()) {
return;
}
foreach ($filters->getFilter() as $filter) {
$this->query->getFilter()->add($filter);
}
foreach ($filters->getShould() as $filter) {
$this->query->getShould()->add($filter);
}
foreach ($filters->getMustNot() as $filter) {
$this->query->getMustNot()->add($filter);
}
$this->query = $filters;
} elseif ($filters instanceof Nested) {
if ($filters->getQuery()->isEmpty()) {
return;
}
$this->query->getFilter()->add($filters);
}
}
public function getQuery(): Query
{
return $this->query;
}
private function separatePropertyTypes(FilterCollection $filterCollection): array
{
$propertyFilter = new PropertyFilterCollection();
$nestedFilter = new NestedFilterCollection();
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];
}
}
<?php
declare(strict_types=1);
namespace IQDEV\ElasticSearch\Converter\Request;
use IQDEV\ElasticSearch\Search\Request;
abstract class RequestBuilder
{
public function __construct(
protected Request $request = new Request(),
) {
}
public function getRequest(): Request
{
return $this->request;
}
abstract public function setPagination(): void;
abstract public function setSort(): void;
abstract public function setSearch(): void;
abstract public function setFilter(): void;
abstract public function setAggregations(): void;
abstract public function build(): void;
}
<?php
namespace IQDEV\ElasticSearch\Converter;
namespace IQDEV\ElasticSearch\Converter\Result;
use Elastic\Elasticsearch\Response\Elasticsearch;
use IQDEV\Search\Document\Product;
use IQDEV\Search\Facet\Facet;
use IQDEV\Search\Facet\Item\FacetItemList;
use IQDEV\Search\Facet\Item\FacetItemRange;
use IQDEV\Search\Facet\Item\FacetItemRangeDTO;
use IQDEV\Search\Facet\Type\FacetListType;
use IQDEV\Search\Facet\Type\FacetRangeType;
use IQDEV\Search\Result;
use IQDEV\ElasticSearch\Document\Product;
use IQDEV\ElasticSearch\Facet\FacetResult;
use IQDEV\ElasticSearch\Facet\FacetType;
use IQDEV\ElasticSearch\Facet\Item\FacetItemList;
use IQDEV\ElasticSearch\Facet\Item\FacetItemRange;
use IQDEV\ElasticSearch\Facet\Item\FacetItemRangeDTO;
use IQDEV\ElasticSearch\Result;
final class EsResponseToResult
{
......@@ -91,7 +90,7 @@ final class EsResponseToResult
$code = $bucket['key'];
$valueBucket = $bucket['agg_keyword_facet_value']['buckets'];
$facet = new Facet(new FacetListType(), $code);
$facet = new FacetResult(FacetType::LIST, $code);
foreach ($valueBucket as $value) {
$count = 0;
......@@ -127,9 +126,9 @@ final class EsResponseToResult
foreach ($buckets as $bucket) {
$code = $bucket['key'];
$workBucket = $bucket['agg_number_facet_value'];
$selectedBuket = !empty($bucketsFiltered[$code]) ? $bucketsFiltered[$code] : null;
$selectedBucket = !empty($bucketsFiltered[$code]) ? $bucketsFiltered[$code] : null;
$facet = new Facet(new FacetRangeType(), $code);
$facet = new FacetResult(FacetType::RANGE, $code);
$facetItem = FacetItemRange::create(
FacetItemRangeDTO::create(
$workBucket['min'],
......@@ -137,14 +136,14 @@ final class EsResponseToResult
$workBucket['avg'],
$workBucket['sum']
),
$selectedBuket !== null ? FacetItemRangeDTO::create(
$selectedBuket['min'],
$selectedBuket['max'],
$selectedBuket['avg'],
$selectedBuket['sum']
$selectedBucket !== null ? FacetItemRangeDTO::create(
$selectedBucket['min'],
$selectedBucket['max'],
$selectedBucket['avg'],
$selectedBucket['sum']
) : FacetItemRangeDTO::create(),
$selectedBuket ? $selectedBuket['count'] : 0,
$selectedBuket !== null
$selectedBucket ? $selectedBucket['count'] : 0,
$selectedBucket !== null
);
$facet->products->add($facetItem);
......
<?php
namespace IQDEV\ElasticSearch\Criteria;
use IQDEV\ElasticSearch\Criteria\Filter\Collection\FilterCollection;
use IQDEV\ElasticSearch\Criteria\Order\OrderCollection;
use IQDEV\ElasticSearch\Criteria\Search\SearchCollection;
final class Criteria
{
private SearchCollection $search;
private FilterCollection $filters;
private OrderCollection $sorting;
private Pagination $pagination;
public function __construct()
{
$this->search = new SearchCollection();
$this->filters = new FilterCollection();
$this->sorting = new OrderCollection();
$this->pagination = new Pagination();
}
public function getSearch(): SearchCollection
{
return $this->search;
}
public function getFilters(): FilterCollection
{
return $this->filters;
}
public function getSorting(): OrderCollection
{
return $this->sorting;
}
public function getPagination(): Pagination
{
return $this->pagination;
}
public function __clone(): void
{
$this->search = clone $this->search;
$this->filters = clone $this->filters;
$this->sorting = clone $this->sorting;
$this->pagination = clone $this->pagination;
}
}
<?php
namespace IQDEV\ElasticSearch\Criteria\Filter\Collection;
use IQDEV\ElasticSearch\Criteria\Filter\Filter;
use IQDEV\ElasticSearch\Criteria\Filter\FilterOperator;
use IQDEV\ElasticSearch\Criteria\Filter\FilterType;
use IQDEV\ElasticSearch\Criteria\Filter\LogicOperator;
use Ramsey\Collection\AbstractCollection;
/**
* @method self add(FilterGroupCollection $item)
*/
class FilterCollection extends AbstractCollection
{
/** @var LogicOperator Тип логической операции для коллекции */
protected LogicOperator $type;
/**
* @param FilterGroupCollection[] $data
*/
public function __construct(array $data = [])
{
parent::__construct($data);
$this->type = LogicOperator::AND;
}
/**
* @inheritDoc
*/
public function getType(): string
{
return FilterGroupCollection::class;
}
/**
* Установка типа логической операции
*
* @param LogicOperator $type
*
* @return $this
*/
public function setLogicalType(LogicOperator $type): self
{
$this->type = $type;
return $this;
}
/**
* Получение типа логической операции
*
* @return LogicOperator
*/
public function getLogicalType(): LogicOperator
{
return $this->type;
}
public function getFilterCollectionByType(FilterType $type): PostFilterCollection|QueryFilterCollection
{
$collection = match ($type) {
FilterType::POST => new PostFilterCollection(),
FilterType::QUERY => new QueryFilterCollection(),
};
$collection->data = array_filter(
$this->toArray(),
static fn (FilterGroupCollection $group) => $group->getFilterType() === $type
);
return $collection;
}
public function getQueryFilterCollection(): QueryFilterCollection
{
$collection = new QueryFilterCollection();
$collection->data = array_filter(
$this->toArray(),
static fn (FilterGroupCollection $group) => $group->getFilterType() === FilterType::QUERY
);
return $collection;
}
public function getPostFilterCollection(): PostFilterCollection
{
$collection = new PostFilterCollection();
$collection->data = array_filter(
$this->toArray(),
static fn (FilterGroupCollection $group) => $group->getFilterType() === FilterType::POST
);
return $collection;
}
public function getKeywordFilters(array $excludeFilter = []): FilterCollection
{
$filterCollection = new FilterCollection();
foreach ($this->data as $filterGroup) {
/** @var FilterGroupCollection $filterGroup */
$keywordFilterGroup = new FilterGroupCollection();
foreach ($filterGroup->data as $filter) {
/** @var Filter $filter */
$field = $filter->field()->value();
if (in_array($field, $excludeFilter, true)
|| in_array($filter->operator(), [
FilterOperator::LT,
FilterOperator::LTE,
FilterOperator::GT,
FilterOperator::GTE
], true)
) {
continue;
}
$keywordFilterGroup->add($filter);
}
if (false === $keywordFilterGroup->isEmpty()) {
$keywordFilterGroup->setLogicOperator($filterGroup->getLogicOperator());
$filterCollection->add($keywordFilterGroup);
}
}
return $filterCollection;
}
public function getNumberFilters(array $excludeFilter = []): FilterCollection
{
$filterCollection = new FilterCollection();
foreach ($this as $filterGroup) {
/** @var FilterGroupCollection $filterGroup */
$numberFilterGroup = new FilterGroupCollection();
foreach ($filterGroup as $filter) {
/** @var Filter $filter */
$field = $filter->field()->value();
if (in_array($field, $excludeFilter, true)) {
continue;
}
if (in_array($filter->operator(), [
FilterOperator::LT,
FilterOperator::LTE,
FilterOperator::GT,
FilterOperator::GTE
], true)) {
$numberFilterGroup->add($filter);
}
}
if (false === $numberFilterGroup->isEmpty()) {
$numberFilterGroup->setLogicOperator($filterGroup->getLogicOperator());
$filterCollection->add($numberFilterGroup);
}
}
return $filterCollection;
}
}
<?php
namespace IQDEV\ElasticSearch\Criteria\Filter\Collection;
use IQDEV\ElasticSearch\Criteria\Filter\Filter;
use IQDEV\ElasticSearch\Criteria\Filter\FilterOperator;
use IQDEV\ElasticSearch\Criteria\Filter\FilterType;
use IQDEV\ElasticSearch\Criteria\Filter\LogicOperator;
use Ramsey\Collection\AbstractCollection;
/**
* @method bool add(Filter $item)
*/
class FilterGroupCollection extends AbstractCollection
{
/** @var LogicOperator Тип логической операции для коллекции */
protected LogicOperator $logicOperator;
/** @var FilterType Тип фильтра для коллекции */
protected FilterType $filterType;
/**
* @param Filter[] $data
*/
public function __construct(array $data = [])
{
parent::__construct($data);
$this->logicOperator = LogicOperator::AND;
$this->filterType = FilterType::POST;
}
/**
* @inheritDoc
*/
public function getType(): string
{
return Filter::class;
}
/**
* Установка типа логической операции
*
* @param LogicOperator $type
*
* @return $this
*/
public function setLogicOperator(LogicOperator $type): self
{
$this->logicOperator = $type;
return $this;
}
/**
* Получение типа логической операции
*
* @return LogicOperator
*/
public function getLogicOperator(): LogicOperator
{
return $this->logicOperator;
}
/**
* Установка типа фильтрации
*
* @param FilterType $type
*
* @return $this
*/
public function setFilterType(FilterType $type): self
{
$this->filterType = $type;
return $this;
}
/**
* Получение типа фильтрации
*
* @return FilterType
*/
public function getFilterType(): FilterType
{
return $this->filterType;
}
public function getKeywordFilters(array $excludeFilter = []): FilterGroupCollection
{
return $this->getFilters(false, $excludeFilter);
}
public function getRangeFilters(array $excludeFilter = []): FilterGroupCollection
{
return $this->getFilters(true, $excludeFilter);
}
private function getFilters(bool $range = false, array $excludeFilter = []): FilterGroupCollection
{
$filterGroup = new FilterGroupCollection();
$filterGroup->setLogicOperator($this->getLogicOperator());
foreach ($this->data as $filter) {
/** @var Filter $filter */
$field = $filter->field()->value();
if (false === in_array($field, $excludeFilter, true)
&& $range === in_array($filter->operator(), [
FilterOperator::LT,
FilterOperator::LTE,
FilterOperator::GT,
FilterOperator::GTE
], true)
) {
$filterGroup->add($filter);
}
}
return $filterGroup;
}
}