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
Showing
with 276 additions and 332 deletions
<?php
namespace IQDEV\ElasticSearch\Search\Aggs;
use IQDEV\ElasticSearch\Search\Nested;
final class AggsNumberFacet
{
public static function create(string $code, string $facet, string $path = 'search_data'): Aggs
{
$aggFacet = new Aggs($code);
$nested = new Nested();
$nested->setPath($path . '.' . $facet);
$aggFacet->setNested($nested);
$aggFacetCode = new Aggs("agg_{$facet}_code");
$aggFacetCode->setTerms(
(new Terms("{$path}.{$facet}.facet_code"))
);
$aggFacetValue = new Aggs("agg_{$facet}_min_value");
$aggFacetValue->setExtremumTerms(
(new ExtremumTerms("{$path}.{$facet}.facet_value"))
->setOperation('min')
);
$aggFacetCode->addAggs($aggFacetValue);
$aggFacetValue = new Aggs("agg_{$facet}_max_value");
$aggFacetValue->setExtremumTerms(
(new ExtremumTerms("{$path}.{$facet}.facet_value"))
->setOperation('max')
);
$aggFacetCode->addAggs($aggFacetValue);
$aggFacet->addAggs($aggFacetCode);
return $aggFacet;
}
}
<?php
namespace IQDEV\ElasticSearch\Search\Aggs;
use IQDEV\ElasticSearch\Esable;
final class ExtremumTerms implements Esable
{
private string $sOperation;
private string $field;
/**
* @param string $field
*/
public function __construct(string $field)
{
$this->field = $field;
}
public function setOperation($sOperation): self
{
$this->sOperation = $sOperation;
return $this;
}
public function es(): array
{
$data = [
'field' => $this->field
];
return [$this->sOperation => $data];
}
}
\ No newline at end of file
......@@ -3,17 +3,20 @@
namespace IQDEV\ElasticSearch\Search\Aggs;
use IQDEV\ElasticSearch\Esable;
use Ramsey\Collection\AbstractCollection;
final class BoolQueryCollection extends AbstractCollection implements Esable
final class Stats implements Esable
{
public function getType(): string
{
return Esable::class;
public function __construct(
private string $field
) {
}
public function es(): array
{
return array_map(static fn(Esable $facet) => $facet->es(), $this->toArray());
return [
'stats' => [
'field' => $this->field,
],
];
}
}
\ No newline at end of file
}
......@@ -2,33 +2,29 @@
namespace IQDEV\ElasticSearch\Search\Aggs;
use IQDEV\ElasticSearch\Esable;
final class Terms implements Esable
{
private array $options = [];
private string $field;
/**
* @param string $field
*/
public function __construct(string $field)
{
$this->field = $field;
public function __construct(
private string $field
) {
}
public function setSize(int $size): self
{
$this->options['size'] = $size;
return $this;
}
public function es(): array
{
$data = $this->options;
$data = $this->options;
$data['field'] = $this->field;
return ['terms' => $data];
}
}
\ No newline at end of file
}
......@@ -14,6 +14,6 @@ final class BoolQueryCollection extends AbstractCollection implements Esable
public function es(): array
{
return array_map(static fn(Esable $facet) => $facet->es(), $this->toArray());
return array_map(static fn (Esable $facet) => $facet->es(), $this->toArray());
}
}
\ No newline at end of file
}
......@@ -5,18 +5,16 @@ namespace IQDEV\ElasticSearch\Search\BoolQuery;
use IQDEV\ElasticSearch\Esable;
use IQDEV\ElasticSearch\Search\Nested;
final class FilterKeywordFacet implements Esable
{
public string $key;
/** @var string|string[] */
public $value;
public function __construct(string $key, $value)
{
$this->key = $key;
$this->value = $value;
/**
* @param string $key
* @param string|array<string> $value
*/
public function __construct(
public string $key,
public string|array $value
) {
}
public function es(): array
......@@ -26,9 +24,8 @@ final class FilterKeywordFacet implements Esable
$nested = new Nested();
$query = new Query();
$query
->filter(new Terms($path . '.facet_code', $this->key))
->filter(new Terms($path . '.facet_value', $this->value));
$query->getFilter()->add(new Terms($path . '.facet_code', $this->key));
$query->getFilter()->add(new Terms($path . '.facet_value', $this->value));
$nested
->setPath($path)
......@@ -36,4 +33,4 @@ final class FilterKeywordFacet implements Esable
return $nested->es();
}
}
\ No newline at end of file
}
......@@ -3,21 +3,16 @@
namespace IQDEV\ElasticSearch\Search\BoolQuery;
use IQDEV\ElasticSearch\Esable;
use IQDEV\ElasticSearch\Criteria\Filter\FilterOperator;
use IQDEV\ElasticSearch\Search\Nested;
final class FilterNumberFacet implements Esable
{
public string $key;
public float $min = 0;
public float $max = PHP_INT_MAX;
public function __construct(string $key, float $min, float $max)
{
$this->key = $key;
$this->min = $min;
$this->max = $max;
public function __construct(
public string $key,
public array $conditions
) {
}
public function es(): array
......@@ -28,8 +23,22 @@ final class FilterNumberFacet implements Esable
$query = new Query();
$query
->filter(new Terms($path . '.facet_code', $this->key))
->filter(new RangeTerms($path . '.facet_value', $this->min, $this->max));
->getFilter()->add(new Stats($path.'.facet_code', $this->key));
$conditions = [];
foreach ($this->conditions as $operator => $value) {
$key = in_array(FilterOperator::from($operator), [
FilterOperator::GTE,
FilterOperator::LTE,
FilterOperator::GT,
FilterOperator::LT,
]) ? $operator : null;
if (isset($key)) {
$conditions[$key] = $value;
}
}
$query->getFilter()->add(new Stats($path.'.facet_value', $conditions));
$nested
->setPath($path)
......@@ -37,4 +46,4 @@ final class FilterNumberFacet implements Esable
return $nested->es();
}
}
\ No newline at end of file
}
......@@ -5,16 +5,16 @@ namespace IQDEV\ElasticSearch\Search\BoolQuery;
use IQDEV\ElasticSearch\Esable;
use IQDEV\ElasticSearch\Search\Nested;
final class Query implements Esable
{
protected BoolQueryCollection $must;
protected BoolQueryCollection $filter;
protected BoolQueryCollection $should;
protected BoolQueryCollection $mustNot;
protected BoolQueryCollection $match;
protected string $type;
public const TYPE_QUERY = 'query';
public const TYPE_FILTER = 'filter';
......@@ -24,6 +24,7 @@ final class Query implements Esable
$this->filter = new BoolQueryCollection();
$this->should = new BoolQueryCollection();
$this->mustNot = new BoolQueryCollection();
$this->match = new BoolQueryCollection();
$this->type = self::TYPE_QUERY;
}
......@@ -31,28 +32,34 @@ final class Query implements Esable
* @param Terms|Nested $item
* @return $this
*/
public function must($item): self
public function getMatch(): BoolQueryCollection
{
$this->must->add($item);
return $this;
return $this->match;
}
public function filter(Esable $item): self
public function getMust(): BoolQueryCollection
{
$this->filter->add($item);
return $this;
return $this->must;
}
public function should(Esable $item): self
public function getFilter(): BoolQueryCollection
{
$this->should->add($item);
return $this;
return $this->filter;
}
public function mustNot(Esable $item): self
public function setFilter(BoolQueryCollection $filter): BoolQueryCollection
{
$this->mustNot->add($item);
return $this;
return $this->filter;
}
public function getShould(): BoolQueryCollection
{
return $this->should;
}
public function getMustNot(): BoolQueryCollection
{
return $this->mustNot;
}
public function isEmpty(): bool
......@@ -62,7 +69,7 @@ final class Query implements Esable
&& $this->filter->isEmpty()
&& $this->should->isEmpty();
}
public function setType(string $type): self
{
switch($type) {
......@@ -84,26 +91,61 @@ final class Query implements Esable
public function es(): array
{
$bool = [];
if ($this->filter->isEmpty() === false) {
if (false === $this->filter->isEmpty()) {
$bool['filter'] = $this->filter->es();
}
if ($this->must->isEmpty() === false) {
if (false === $this->must->isEmpty()) {
$bool['must'] = $this->must->es();
}
if ($this->mustNot->isEmpty() === false) {
if (false === $this->mustNot->isEmpty()) {
$bool['must_not'] = $this->mustNot->es();
}
if ($this->should->isEmpty() === false) {
if (false === $this->should->isEmpty()) {
$bool['should'] = $this->should->es();
$bool['minimum_should_match'] = 1;
}
if (false === $this->match->isEmpty()) {
$bool['match'] = $this->match->es();
}
return [
$this->type => [
'bool' => $bool,
]
],
];
}
}
\ No newline at end of file
public function modify(Query $another): self
{
foreach ($another->getMust() as $item) {
$this->getMust()->add($item);
}
foreach ($another->getFilter() as $item) {
$this->getFilter()->add($item);
}
foreach ($another->getShould() as $item) {
$this->getShould()->add($item);
}
foreach ($another->getMustNot() as $item) {
$this->getMustNot()->add($item);
}
foreach ($another->getMatch() as $item) {
$this->getMatch()->add($item);
}
return $this;
}
public function __clone(): void
{
$this->must = clone $this->must;
$this->should = clone $this->should;
$this->filter = clone $this->filter;
$this->mustNot = clone $this->mustNot;
$this->match = clone $this->match;
}
}
......@@ -2,37 +2,28 @@
namespace IQDEV\ElasticSearch\Search\BoolQuery;
use IQDEV\ElasticSearch\Esable;
final class RangeTerms implements Esable
final class Stats implements Esable
{
private string $key;
private float $min;
private float $max;
/**
* @param string $key
* @param float $min
* @param float $max
* @param string|float|array<string|float> $value
*/
public function __construct(string $key, float $min, float $max)
{
$this->key = $key;
$this->min = $min;
$this->max = $max;
public function __construct(
private string $key,
private string|float|array $value
) {
}
public function es(): array
{
$keyStats = is_array($this->value) ? 'range' : 'term';
return [
'range' => [
$this->key => [
'gte' => $this->min,
'lte' => $this->max
]
]
$keyStats => [
$this->key => $this->value,
],
];
}
}
\ No newline at end of file
}
......@@ -2,23 +2,18 @@
namespace IQDEV\ElasticSearch\Search\BoolQuery;
use IQDEV\ElasticSearch\Esable;
final class Terms implements Esable
{
private string $key;
/** @var string|float|string[]|float[] */
private $value;
/**
* @param string $key
* @param string|float|string[]|float[] $value
* @param string|float|bool|array<string|float> $value
*/
public function __construct(string $key, $value)
{
$this->key = $key;
$this->value = $value;
public function __construct(
private string $key,
private mixed $value
) {
}
public function es(): array
......@@ -27,8 +22,8 @@ final class Terms implements Esable
return [
$keyTerms => [
$this->key => $this->value
$this->key => $this->value,
],
];
}
}
\ No newline at end of file
}
......@@ -17,15 +17,22 @@ class Nested implements Esable
public function setPath(string $p): self
{
$this->path = $p;
return $this;
}
public function setQuery(Query $q): self
{
$this->query = $q;
return $this;
}
public function getQuery(): ?Query
{
return $this->query;
}
public function es(): array
{
$nested = [];
......@@ -34,7 +41,7 @@ class Nested implements Esable
$nested['path'] = $this->path;
}
if (isset($this->query) && $this->query->isEmpty() === false) {
if ($this->query && false === $this->query->isEmpty()) {
$nested['query'] = $this->query->es()['query'];
}
......
......@@ -6,13 +6,10 @@ use IQDEV\ElasticSearch\Esable;
class Pagination implements Esable
{
public ?int $size;
public ?int $from;
public function __construct(?int $size = null, ?int $from = null)
{
$this->size = $size;
$this->from = $from;
public function __construct(
private ?int $size = null,
private ?int $from = null
) {
}
public function es(): array
......
......@@ -3,9 +3,10 @@
namespace IQDEV\ElasticSearch\Search;
use IQDEV\ElasticSearch\Esable;
use IQDEV\ElasticSearch\Criteria\Match\QueryMatchCollection;
use IQDEV\ElasticSearch\Criteria\Order\OrderCollection;
use IQDEV\ElasticSearch\Search\Aggs\AggsCollection;
use IQDEV\ElasticSearch\Search\BoolQuery\Query;
use IQDEV\ElasticSearch\Search\Sorting\SortingCollection;
final class Request implements Esable
{
......@@ -13,102 +14,113 @@ final class Request implements Esable
private ?Query $postFilter = null;
private ?AggsCollection $aggs = null;
private ?Pagination $pagination = null;
private ?SortingCollection $sorting = null;
private array $match = [];
private ?OrderCollection $sort = null;
private ?QueryMatchCollection $matchCollection = null;
private ?array $source = null;
/**
* @param Pagination|null $pagination
* @return Request
*/
public function setPagination(?Pagination $pagination): self
{
$this->pagination = $pagination;
return $this;
}
/**
* @param SortingCollection|null $sorting
* @return $this
*/
public function setSorting(?SortingCollection $sorting): self
{
$this->sorting = $sorting;
return $this;
}
/**
* @return Query
*/
public function getQuery(): Query
{
if ($this->query === null) {
if (null === $this->query) {
$this->query = new Query();
}
return $this->query;
}
/**
* @return Query
*/
public function setQuery(Query $query): self
{
$this->query = $query;
return $this;
}
public function getPostFilter(): Query
{
if ($this->postFilter === null) {
if (null === $this->postFilter) {
$this->postFilter = new Query();
}
return $this->postFilter;
}
public function setPostFilter(Query $query): self
{
$this->postFilter = $query;
return $this;
}
public function getAggs(): AggsCollection
{
if ($this->aggs === null) {
if (null === $this->aggs) {
$this->aggs = new AggsCollection();
}
return $this->aggs;
}
/**
* @return Pagination|null
*/
public function setAggs(AggsCollection $aggs): self
{
$this->aggs = $aggs;
return $this;
}
public function getQueryMatch(): QueryMatchCollection
{
if (null === $this->matchCollection) {
$this->matchCollection = new QueryMatchCollection();
}
return $this->matchCollection;
}
public function getPagination(): ?Pagination
{
return $this->pagination;
}
/**
* @return SortingCollection|null
*/
public function getSorting(): ?SortingCollection
public function setSource(array $s): self
{
return $this->sorting;
$this->source = $s;
return $this;
}
public function addMatch(string $key, array $param): self
public function getSort(): OrderCollection
{
$this->match[$key] = $param;
return $this;
if (null === $this->sort) {
$this->sort = new OrderCollection();
}
return $this->sort;
}
public function es(): array
{
$request = [
'_source' => ['id']
];
$request = [];
if (isset($this->postFilter) && $this->postFilter->isEmpty() === false) {
if ($this->source) {
$request['_source'] = $this->source;
}
if ($this->postFilter && false === $this->postFilter->isEmpty()) {
$request['post_filter'] = $this->postFilter->es()['query'];
}
if (isset($this->query) && $this->query->isEmpty() === false) {
if ($this->query && false === $this->query->isEmpty()) {
$request['query'] = $this->query->es()['query'];
}
if (empty($this->match) === false) {
foreach ($this->match as $key => $value) {
$request['query']['match'][$key] = $value;
}
if ($this->matchCollection && false === $this->matchCollection->isEmpty()) {
$request['query']['match'] = $this->matchCollection->es();
}
if ($this->aggs) {
......@@ -118,11 +130,11 @@ final class Request implements Esable
if ($this->pagination) {
$request = array_merge($request, $this->pagination->es());
}
if ($this->sorting) {
$request['sort'] = $this->sorting->es();
if ($this->sort) {
$request['sort'] = $this->sort->es();
}
return $request;
}
}
\ No newline at end of file
}
<?php
namespace Docke\ElasticSearch\Search;
use IQDEV\ElasticSearch\Esable;
class Sorting implements Esable
{
public string $by;
public string $direction;
public function __construct(string $by = '', string $direction = 'asc')
{
$this->by = $by;
$this->direction = $direction;
}
public function es(): array
{
$sorting = [];
if ($this->by) {
$sorting['sort'] = [$this->by => $this->direction];
}
return $sorting;
}
}
\ No newline at end of file
<?php
namespace IQDEV\ElasticSearch\Search\Sorting;
class SortingField implements Sorting
{
public string $by;
public function __construct(string $by = '')
{
$this->by = $by;
}
public function es(): array
{
return [$this->by];
}
}
\ No newline at end of file
<?php
namespace IQDEV\ElasticSearch\Search\Sorting;
class SortingFieldsPair extends SortingPair
{
}
\ No newline at end of file
<?php
namespace IQDEV\ElasticSearch\Search\Sorting;
abstract class SortingPair implements Sorting
{
public string $by;
public string $direction;
public function __construct(string $by = '', string $direction = 'asc')
{
$this->by = $by;
$this->direction = $direction;
}
public function es(): array
{
return [$this->by => $this->direction];
}
}
\ No newline at end of file
<?php
namespace IQDEV\ElasticSearch\Search\Sorting;
class SortingPropertyKeywordPair extends SortingPair
{
public function es(): array
{
return [
'search_data.keyword_facet.facet_value' => [
'order' => $this->direction,
'nested' => [
'path' => 'search_data',
'filter' => [
'bool' => [
'must' => [
'term' => [
'search_data.keyword_facet.facet_code' => $this->by,
]
],
],
],
],
],
];
}
}
\ No newline at end of file
<?php
namespace IQDEV\ElasticSearch\Search\Sorting;
class SortingPropertyNumberPair extends SortingPair
{
public function es(): array
{
return [
'search_data.number_facet.facet_value' => [
'order' => $this->direction,
'nested' => [
'path' => 'search_data',
'filter' => [
'bool' => [
'must' => [
'term' => [
'search_data.number_facet.facet_code' => $this->by,
]
],
],
],
],
],
];
}
}
\ No newline at end of file
<?php
namespace IQDEV\ElasticSearch;
use Elastic\Elasticsearch\Client;
use Elastic\Elasticsearch\Exception\ClientResponseException;
use Elastic\Elasticsearch\Exception\ServerResponseException;
use IQDEV\ElasticSearch\Converter\Request\CriteriaToRequest;
use IQDEV\ElasticSearch\Converter\Result\EsResponseToResult;
use IQDEV\ElasticSearch\Criteria\Criteria;
class SearchService implements Searchable
{
private CriteriaToRequest $criteriaToRequest;
private EsResponseToResult $esResponseToResult;
public function __construct(
private Client $esClient,
private Configuration $configuration
) {
$this->criteriaToRequest = new CriteriaToRequest($this->configuration);
$this->esResponseToResult = new EsResponseToResult();
}
/**
* @throws ServerResponseException
* @throws ClientResponseException
*/
public function search(Criteria $criteria): Result
{
$request = $this->criteriaToRequest->fromCriteria($criteria);
$response = $this->esClient->search([
'index' => $this->configuration->getIndexName(),
'body' => $request->es(),
]);
return $this->esResponseToResult->fromResponse($response, $this->configuration);
}
}