From afbcee9ae4deb347aa88bb34c0f25e58d4bba7e9 Mon Sep 17 00:00:00 2001 From: Martin Kluska Date: Tue, 5 Apr 2022 15:26:09 +0200 Subject: [PATCH 1/5] Upgrade codebase to PHP 8.0 with improved interface - Add PHPstan and code convention tools --- README.md | 47 ++++-- UPGRADE-3.0.md | 19 ++- composer.json | 28 +++- ecs.php | 20 +++ phpstan.neon | 20 +++ phpunit.xml.dist | 43 ++--- rector.php | 38 +++++ src/Aggregation/AbstractAggregation.php | 53 +++++++ src/Aggregation/Aggregation.php | 150 ++++++++++-------- src/Aggregation/CardinalityAggregation.php | 46 ++++-- src/Aggregation/DateHistogramAggregation.php | 58 ++++--- src/Aggregation/FilterAggregation.php | 42 ++--- src/Aggregation/MaxAggregation.php | 4 +- src/Aggregation/MetricAggregation.php | 83 ++++++---- src/Aggregation/MinAggregation.php | 4 +- src/Aggregation/NestedAggregation.php | 36 +++-- src/Aggregation/ReverseNestedAggregation.php | 32 +--- src/Aggregation/SumAggregation.php | 4 +- src/Aggregation/TermsAggregation.php | 118 +++++++------- src/Aggregation/TopHitsAggregation.php | 43 +++-- src/Constants/SortDirections.php | 26 +++ src/Contracts/BuildsArray.php | 10 ++ src/Contracts/QueryInterface.php | 9 ++ src/Features/HasAggregations.php | 56 +++++++ src/Features/HasCollapse.php | 45 ++++++ src/Features/HasField.php | 22 +++ src/Features/HasOperator.php | 33 ++++ src/Features/HasSorting.php | 43 +++++ src/Options/Collapse.php | 64 ++++++++ src/Options/Field.php | 31 ++++ src/Options/InlineScript.php | 36 +++++ src/Options/InnerHit.php | 66 ++++++++ src/Options/SourceScript.php | 35 ++++ src/Query/AbstractMatchQuery.php | 35 ++-- src/Query/BoolQuery.php | 98 +++++++----- src/Query/ExistsQuery.php | 22 +-- src/Query/GeoDistanceQuery.php | 35 ++-- src/Query/GeoShapeQuery.php | 41 +++-- src/Query/MatchPhrasePrefixQuery.php | 2 + src/Query/MatchPhraseQuery.php | 2 + src/Query/MatchQuery.php | 2 + src/Query/MultiMatchQuery.php | 62 ++++---- src/Query/NestedQuery.php | 21 ++- src/Query/PrefixQuery.php | 44 ++--- src/Query/Query.php | 45 ++++-- src/Query/QueryInterface.php | 8 - src/Query/QueryStringQuery.php | 33 ++-- src/Query/RangeQuery.php | 50 +++--- src/Query/RankFeatureQuery.php | 40 ++--- src/Query/TermQuery.php | 37 ++--- src/Query/TermsQuery.php | 40 ++--- src/Query/WildcardQuery.php | 37 ++--- src/QueryBuilder.php | 104 ++++++------ src/QueryException.php | 2 + .../CardinalityAggregationTest.php | 49 ++++-- .../DateHistogramAggregationTest.php | 49 +++--- tests/Aggregation/MaxAggregationTest.php | 37 +++-- tests/Aggregation/MinAggregationTest.php | 22 +-- tests/Aggregation/SumAggregationTest.php | 22 +-- tests/Query/BoolQueryTest.php | 62 ++++++-- tests/Query/ExistsQueryTest.php | 5 +- tests/Query/GeoDistanceQueryTest.php | 4 +- tests/Query/GeoShapeQueryTest.php | 18 +-- tests/Query/MatchPhrasePrefixQueryTest.php | 6 +- tests/Query/MatchPhraseQueryTest.php | 6 +- tests/Query/MatchQueryTest.php | 6 +- tests/Query/QueryStringTest.php | 6 +- tests/Query/RankFeatureTest.php | 6 +- tests/Query/TermsQueryTest.php | 4 +- tests/QueryBuilderTest.php | 31 ++-- 70 files changed, 1628 insertions(+), 829 deletions(-) create mode 100644 ecs.php create mode 100644 phpstan.neon create mode 100644 rector.php create mode 100644 src/Aggregation/AbstractAggregation.php create mode 100644 src/Constants/SortDirections.php create mode 100644 src/Contracts/BuildsArray.php create mode 100644 src/Contracts/QueryInterface.php create mode 100644 src/Features/HasAggregations.php create mode 100644 src/Features/HasCollapse.php create mode 100644 src/Features/HasField.php create mode 100644 src/Features/HasOperator.php create mode 100644 src/Features/HasSorting.php create mode 100644 src/Options/Collapse.php create mode 100644 src/Options/Field.php create mode 100644 src/Options/InlineScript.php create mode 100644 src/Options/InnerHit.php create mode 100644 src/Options/SourceScript.php delete mode 100644 src/Query/QueryInterface.php diff --git a/README.md b/README.md index 4fe04b1..52031f8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ ElasticSearch Query Builder =========================== +![img](https://img.shields.io/badge/phpstan-8-green) +![php](https://img.shields.io/badge/php-8.0-brightgreen) + + This is a PHP library which helps you build query for an ElasticSearch client by using a fluent interface. WARNING: This branch contains the next 3.x release. Check the [corresponding issue](https://github.com/erichard/elasticsearch-query-builder/issues/7) for the roadmap. @@ -8,38 +12,63 @@ WARNING: This branch contains the next 3.x release. Check the [corresponding iss Installation ------------ -``` -$ composer require erichard/elasticsearch-query-builder +```bash +composer require erichard/elasticsearch-query-builder ``` Usage ----- -``` +```php use Erichard\ElasticQueryBuilder\QueryBuilder; use Erichard\ElasticQueryBuilder\Aggregation\Aggregation; use Erichard\ElasticQueryBuilder\Filter\Filter; -$qb = new QueryBuilder(); +$query = new QueryBuilder(); -$qb +$query ->setType('my_type') ->setIndex('app') ->setSize(10) ; // Add an aggregation -$qb->addAggregation(Aggregation::terms('agg_name')->setField('my_field')); +$query->addAggregation(Aggregation::terms('agg_name', 'my_field')); +$query->addAggregation(Aggregation::terms('agg_name_same_as_field')); // Add a filter $boolFilter = Filter::bool(); -$boolFilter->addFilter(Filter::terms()->setField('field')->setValue($value)); +$boolFilter->addFilter(Filter::terms('field', 'value')); -$qb->addFilter($boolFilter); +$query->addFilter($boolFilter); // I am using a client from elasticsearch/elasticsearch here -$results = $client->search($qb->getQuery()); +$results = $client->search($query->build()); +``` +with PHP 8.1 you can do this: + +```php +new BoolQuery(should: [ + new RangeQuery( + field: PriceTermsIndex::PREFIX_OPTION . OptionIds::PERSONS_MAX, + gte: $this->roomCount + ), + new RangeQuery( + field: PriceTermsIndex::PREFIX_OPTION . OptionIds::PERSONS_MAX, + gte: $this->roomCount + ), +]) +] ``` + +Contribution +------------ + +- Use PHPCS fixer and PHPStan + - `composer lint` +- Update tests (PHPUnit) + - `composer test` + diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index a7dbf2e..254d38a 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -1,6 +1,23 @@ # Upgrade guide for 3.0 + +- If you are type-hinting build aggregation use `AbstractAggregation` instead of `Aggregation` +- If you are implementing own `Aggregation` - extend `AbstractAggregation` +- If you are implementing own `Query` (filter) - implement `QueryInterface` + +## Rewrite filters to Query + +- All files were renamed from `Filter` suffix to `Query` +- namespace has been moved to `Query` +- Move values from `setField` (and other "required" properties) to a constructor of the filter +- Find in your IDE all usages of this text `use Erichard\ElasticQueryBuilder\Filter\` +- Then find all `Filter` definitions and rewrite them to Query +- Remove any lines with `use Erichard\ElasticQueryBuilder\Filter\` +- Use PHPStan to find bugs after refactoring. + ## New terminology -All xxxFilter classes have been renamed to xxxQuery to be more consistent with Elastic terms. +- All xxxFilter classes have been renamed to xxxQuery to be more consistent with Elastic terms. +- We are using strict types - ensure that values you are sending are valid. +- Required properties must be defined in constructor. Optional can be in constructor too (for PHP 8.1 - named arguments) diff --git a/composer.json b/composer.json index a1290d4..5305afd 100644 --- a/composer.json +++ b/composer.json @@ -10,24 +10,44 @@ } ], "autoload": { - "psr-4": { + "psr-4": { "Erichard\\ElasticQueryBuilder\\": "src/" } }, "autoload-dev": { - "psr-4": { + "psr-4": { "Tests\\Erichard\\ElasticQueryBuilder\\": "tests/" } }, "require": { - "php": ">=7.0" + "php": ">=8.0" }, "require-dev": { - "phpunit/phpunit": "^8.0" + "phpstan/phpstan": "^1.4.10", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.0.0", + "phpunit/phpunit": "^9.5.19", + "rector/rector": "^0.12.17", + "symplify/easy-coding-standard": "^10.1" + }, + "scripts": { + "lint:fix": "./vendor/bin/ecs --fix", + "lint:stan": "./vendor/bin/phpstan", + "lint:upgrade": "vendor/bin/rector process", + "lint": "composer lint:fix && composer lint:upgrade && composer lint:stan", + "test": "./vendor/bin/phpunit" }, "extra": { "branch-alias": { "dev-main": "3.0-dev" } + }, + "config": { + "preferred-install": "dist", + "sort-packages": true, + "optimize-autoloader": true, + "allow-plugins": { + "symfony/thanks": false + } } } diff --git a/ecs.php b/ecs.php new file mode 100644 index 0000000..8b60f71 --- /dev/null +++ b/ecs.php @@ -0,0 +1,20 @@ +import(SetList::PSR_12); + $containerConfigurator->import(SetList::SYMPLIFY); + $containerConfigurator->import(SetList::COMMON); + $containerConfigurator->import(SetList::CLEAN_CODE); + + $parameters = $containerConfigurator->parameters(); + + $parameters->set(Option::PARALLEL, true); + + $parameters->set(Option::PATHS, [__DIR__ . '/src', __DIR__ . '/tests', __DIR__ . '/ecs.php']); +}; diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..b71b0ed --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,20 @@ +includes: + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon + - vendor/phpstan/phpstan-deprecation-rules/rules.neon + +parameters: + + parallel: + processTimeout: 600.0 + + paths: + - src + - tests + + # The level 8 is the highest level + level: 8 + + # it is impossible to map build() + checkMissingIterableValueType: false + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 87e021b..cc78ed0 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,28 +1,17 @@ - - - - - ./tests/ - - - - - - ./ - - ./tests - ./vendor - - - - \ No newline at end of file + + + + ./ + + + ./tests + ./vendor + + + + + ./tests/ + + + diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..38f90de --- /dev/null +++ b/rector.php @@ -0,0 +1,38 @@ +parameters(); + $parameters->set(Option::PATHS, [ + __DIR__ . '/src', + __DIR__ . '/tests', + ]); + + $parameters->set(Option::PHP_VERSION_FEATURES, PhpVersion::PHP_80); + + // Define what rule sets will be applied + $containerConfigurator->import(LevelSetList::UP_TO_PHP_80); + + $containerConfigurator->import(SetList::CODE_QUALITY); + $containerConfigurator->import(SetList::CODING_STYLE); + + $services = $containerConfigurator->services(); + $services->set(AddVoidReturnTypeWhereNoReturnRector::class); + $services->set(BooleanInBooleanNotRuleFixerRector::class)->configure([ + BooleanInTernaryOperatorRuleFixerRector::TREAT_AS_NON_EMPTY => false, + ]); + +}; diff --git a/src/Aggregation/AbstractAggregation.php b/src/Aggregation/AbstractAggregation.php new file mode 100644 index 0000000..c13a505 --- /dev/null +++ b/src/Aggregation/AbstractAggregation.php @@ -0,0 +1,53 @@ + $aggregations + */ + public function __construct( + private string $name, + array $aggregations = [] + ) { + $this->aggregations = $aggregations; + } + + /** + * @return array|array|array + */ + public function build(): array + { + $data = [ + $this->getType() => $this->buildAggregation(), + ]; + + $this->buildAggregationsTo($data); + + return $data; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getName(): string + { + return $this->name; + } + + abstract protected function getType(): string; + + abstract protected function buildAggregation(): array; +} diff --git a/src/Aggregation/Aggregation.php b/src/Aggregation/Aggregation.php index 325256b..8e22db5 100644 --- a/src/Aggregation/Aggregation.php +++ b/src/Aggregation/Aggregation.php @@ -1,96 +1,112 @@ build(); - - if (!empty($this->aggregations)) { - $build['aggs'] = []; - - foreach($this->aggregations as $aggregation) { - $build['aggs'][$aggregation->getName()] = $aggregation->buildRecursivly(); - } - } - - return $build; - } - - public function __construct(string $name) - { - $this->name = $name; - } +declare(strict_types=1); - public function addAggregation(Aggregation $aggregation) - { - $this->aggregations[$aggregation->getName()] = $aggregation; +namespace Erichard\ElasticQueryBuilder\Aggregation; - return $this; - } +use Erichard\ElasticQueryBuilder\Contracts\QueryInterface; +use Erichard\ElasticQueryBuilder\Options\Field; +use Erichard\ElasticQueryBuilder\Options\InlineScript; +use Erichard\ElasticQueryBuilder\Options\SourceScript; - public function getName(): string - { - return $this->name; +class Aggregation +{ + /** + * @param array $aggregations + */ + public static function terms( + string $name, + string|Field|InlineScript $fieldOrSource, + array $aggregations = [] + ): TermsAggregation { + return new TermsAggregation($name, $fieldOrSource, $aggregations); } - public static function terms(string $name): TermsAggregation - { - return new TermsAggregation($name); + /** + * @param array $aggregations + */ + public static function dateHistogram( + string $nameAndField, + string $calendarInterval, + ?string $field = null, + array $aggregations = [] + ): DateHistogramAggregation { + return new DateHistogramAggregation($nameAndField, $calendarInterval, $field, $aggregations); } - public static function dateHistogram(string $name): DateHistogramAggregation + /** + * @param array $aggregations + */ + public static function nested(string $name, string $path, array $aggregations = []): NestedAggregation { - return new DateHistogramAggregation($name); + return new NestedAggregation($name, $path, $aggregations); } - public static function nested(string $name): NestedAggregation + /** + * @param array $aggregations + */ + public static function reverseNested(string $name, string $path, array $aggregations = []): ReverseNestedAggregation { - return new NestedAggregation($name); + return new ReverseNestedAggregation($name, $path, $aggregations); } - public static function reverseNested(string $name): ReverseNestedAggregation + /** + * @param array $aggregations + */ + public static function filter(string $name, QueryInterface $query, array $aggregations = []): FilterAggregation { - return new ReverseNestedAggregation($name); + return new FilterAggregation($name, $query, $aggregations); } - public static function filter(string $name): FilterAggregation - { - return new FilterAggregation($name); + /** + * @param array $aggregations + */ + public static function cardinality( + string $nameAndField, + string|SourceScript|Field|null $fieldOrSource = null, + array $aggregations = [] + ): CardinalityAggregation { + return new CardinalityAggregation($nameAndField, $fieldOrSource, $aggregations); } - public static function cardinality(string $name): CardinalityAggregation - { - return new CardinalityAggregation($name); - } - - public static function max(string $name): MaxAggregation - { - return new MaxAggregation($name); + /** + * @param array $aggregations + */ + public static function max( + string $nameAndField, + string|SourceScript|Field|null $fieldOrSource = null, + array $aggregations = [] + ): MaxAggregation { + return new MaxAggregation($nameAndField, $fieldOrSource, $aggregations); } - public static function min(string $name): MinAggregation - { - return new MinAggregation($name); + /** + * @param array $aggregations + */ + public static function min( + string $nameAndField, + string|SourceScript|Field|null $fieldOrSource = null, + array $aggregations = [] + ): MinAggregation { + return new MinAggregation($nameAndField, $fieldOrSource, $aggregations); } - public static function sum(string $name): SumAggregation - { - return new SumAggregation($name); + /** + * @param array $aggregations + */ + public static function sum( + string $nameAndField, + string|SourceScript|Field|null $fieldOrSource = null, + array $aggregations = [] + ): SumAggregation { + return new SumAggregation($nameAndField, $fieldOrSource, $aggregations); } - public static function topHits(string $name): TopHitsAggregation + /** + * @param array $aggregations + */ + public static function topHits(string $name, array $aggregations = []): TopHitsAggregation { - return new TopHitsAggregation($name); + return new TopHitsAggregation($name, $aggregations); } } diff --git a/src/Aggregation/CardinalityAggregation.php b/src/Aggregation/CardinalityAggregation.php index 4988697..a82be11 100644 --- a/src/Aggregation/CardinalityAggregation.php +++ b/src/Aggregation/CardinalityAggregation.php @@ -1,29 +1,51 @@ precisionThreshold = $precisionThreshold; - public function setPrecisionThreshold($precisionThreshold) - { - $this->precisionThreshold = $precisionThreshold; - } + return $this; + } + + public function getPrecisionThreshold(): ?int + { + return $this->precisionThreshold; + } - public function getMetricName(): string + protected function getType(): string { return 'cardinality'; } - public function build(): array + protected function buildAggregation(): array { - $build = parent::build(); + $build = parent::buildAggregation(); - if (null !== $this->precisionThreshold) { - $build['cardinality']['precision_threshold'] = $this->precisionThreshold; - } + if ($this->precisionThreshold !== null) { + $build['precision_threshold'] = $this->precisionThreshold; + } - return $build; + return $build; } } diff --git a/src/Aggregation/DateHistogramAggregation.php b/src/Aggregation/DateHistogramAggregation.php index 93b4292..5c9abc6 100644 --- a/src/Aggregation/DateHistogramAggregation.php +++ b/src/Aggregation/DateHistogramAggregation.php @@ -1,47 +1,53 @@ field = $field; - - return $this; + use HasField; // TODO enum + + /** + * @param array $aggregations + */ + public function __construct( + string $nameAndField, + private string $calendarInterval, + ?string $field = null, + array $aggregations = [] + ) { + parent::__construct($nameAndField, $aggregations); + $this->field = $field ?? $nameAndField; } - public function setCalendarInterval(string $calendarInterval) + public function setCalendarInterval(string $calendarInterval): self { $this->calendarInterval = $calendarInterval; return $this; } - public function build(): array + public function getCalendarInterval(): string { - if (null === $this->field) { - throw new QueryException('You should call DateHistogramAggregation::setField() before build.'); - } + return $this->calendarInterval; + } - if (null === $this->calendarInterval) { - throw new QueryException('You should call DateHistogramAggregation::calendarInterval() before build.'); - } + protected function getType(): string + { + return 'date_histogram'; + } + protected function buildAggregation(): array + { return [ - 'date_histogram' => [ - 'field' => $this->field, - 'calendar_interval' => $this->calendarInterval, - ] + 'field' => $this->field, + 'calendar_interval' => $this->calendarInterval, ]; } } diff --git a/src/Aggregation/FilterAggregation.php b/src/Aggregation/FilterAggregation.php index d1b5e52..f435d0f 100644 --- a/src/Aggregation/FilterAggregation.php +++ b/src/Aggregation/FilterAggregation.php @@ -1,35 +1,41 @@ $aggregations + */ + public function __construct( + string $name, + private QueryInterface $query, + array $aggregations = [] + ) { + parent::__construct($name, $aggregations); + } - public function setFilter(Filter $filter) + public function getQuery(): QueryInterface { - $this->filter = $filter; - - return $this; + return $this->query; } - public function setAggregation(Aggregation $aggregation) + public function setQuery(QueryInterface $query): void { - $this->aggregation = $aggregation; + $this->query = $query; + } - return $this; + protected function getType(): string + { + return 'filter'; } - public function build(): array + protected function buildAggregation(): array { - return [ - 'filter' => $this->filter->build(), - 'aggs' => [ - $this->aggregation->getName() => $this->aggregation->build(), - ], - ]; + return $this->query->build(); } } diff --git a/src/Aggregation/MaxAggregation.php b/src/Aggregation/MaxAggregation.php index 14f0e14..eddc988 100644 --- a/src/Aggregation/MaxAggregation.php +++ b/src/Aggregation/MaxAggregation.php @@ -1,10 +1,12 @@ $aggregations + */ + public function __construct( + string $nameAndField, + string|SourceScript|Field|null $fieldOrSource = null, + array $aggregations = [] + ) { + parent::__construct($nameAndField, $aggregations); - /** @var integer */ - private $missing; + if ($fieldOrSource === null) { + $fieldOrSource = $nameAndField; + } - abstract function getMetricName(): string; + if (is_string($fieldOrSource)) { + $this->field = new Field($fieldOrSource); + } elseif ($fieldOrSource instanceof Field) { + $this->field = $fieldOrSource; + } elseif ($fieldOrSource instanceof SourceScript) { + $this->script = $fieldOrSource; + } else { + throw new QueryException('Invalid field or source argument in metric aggregation'); + } + } - public function setField(string $field) + public function setField(string|Field $field): self { - $this->field = $field; + $this->script = null; + $this->field = is_string($field) ? new Field($field) : $field; return $this; } - public function setScript(string $script) + public function setScript(string|SourceScript $script): self { - $this->script = $script; + $this->field = null; + $this->script = is_string($script) ? new SourceScript($script) : $script; return $this; } - public function setMissing(int $missing) + public function getField(): ?Field + { + return $this->field; + } + + public function setMissing(?int $missing): self { $this->missing = $missing; return $this; } - - public function build(): array + protected function buildAggregation(): array { - if (null !== $this->script) { - $term = [ - 'script' => [ - 'source' => $this->script, - ], - ]; - } elseif (null !== $this->field) { - $term = [ - 'field' => $this->field, - ]; - } else { - throw new QueryException('You should call MinAggregation::setField() or MinAggregation::setScript() '); + $term = []; + if ($this->script !== null) { + $term = $this->script->build(); + } elseif ($this->field !== null) { + $term = $this->field->build(); } - if (null !== $this->missing) { + if ($this->missing !== null) { $term['missing'] = $this->missing; } - return [ - $this->getMetricName() => $term, - ]; + return $term; } } diff --git a/src/Aggregation/MinAggregation.php b/src/Aggregation/MinAggregation.php index 2d16cd6..37e9c0d 100644 --- a/src/Aggregation/MinAggregation.php +++ b/src/Aggregation/MinAggregation.php @@ -1,10 +1,12 @@ $aggregations + */ + public function __construct( + string $name, + private string $path, + array $aggregations = [] + ) { + parent::__construct($name, $aggregations); + } - public function setNestedPath(string $path) + public function setNestedPath(string $path): self { $this->path = $path; return $this; } - public function setAggregation(Aggregation $aggregation) + public function getNestedPath(): string { - $this->aggregation = $aggregation; + return $this->path; + } - return $this; + protected function getType(): string + { + return 'nested'; } - public function build(): array + protected function buildAggregation(): array { return [ - 'nested' => [ - 'path' => $this->path, - ], - 'aggs' => [ - $this->aggregation->getName() => $this->aggregation->build(), - ], + 'path' => $this->path, ]; } } diff --git a/src/Aggregation/ReverseNestedAggregation.php b/src/Aggregation/ReverseNestedAggregation.php index d0d9bc6..c62527d 100644 --- a/src/Aggregation/ReverseNestedAggregation.php +++ b/src/Aggregation/ReverseNestedAggregation.php @@ -1,35 +1,13 @@ path = $path; - - return $this; - } - - public function setAggregation(Aggregation $aggregation) - { - $this->aggregation = $aggregation; - - return $this; - } - - public function build(): array + protected function getType(): string { - return [ - 'reverse_nested' => [ - 'path' => $this->path, - ], - 'aggs' => [ - $this->aggregation->getName() => $this->aggregation->build(), - ], - ]; + return 'reverse_nested'; } } diff --git a/src/Aggregation/SumAggregation.php b/src/Aggregation/SumAggregation.php index 2ef1222..510313f 100644 --- a/src/Aggregation/SumAggregation.php +++ b/src/Aggregation/SumAggregation.php @@ -1,10 +1,12 @@ field = $field; - - return $this; + private ?InlineScript $script = null; + + private ?Field $field = null; + + /** + * @param string|Field|InlineScript $fieldOrSource string === field + */ + public function __construct( + string $name, + string|Field|InlineScript $fieldOrSource, + array $aggregations = [], + private string|null $orderField = null, + private string $orderValue = SortDirections::ASC, + private array|string|null $include = null, + private array|string|null $exclude = null, + private int $size = 10, + ) { + parent::__construct($name, $aggregations); + + if (is_string($fieldOrSource)) { + $this->field = new Field($fieldOrSource); + } elseif ($fieldOrSource instanceof Field) { + $this->field = $fieldOrSource; + } elseif ($fieldOrSource instanceof InlineScript) { + $this->script = $fieldOrSource; + } else { + throw new QueryException('Invalid field or source argument in metric aggregation'); + } } - public function setSize(int $size) + public function setSize(int $size): self { $this->size = $size; return $this; } - public function setScript(string $script) - { - $this->script = $script; - - return $this; - } - - public function setOrder(string $orderField, string $orderValue = 'ASC') + public function setOrder(string|null $orderField, string $orderValue = SortDirections::ASC): self { $this->orderField = $orderField; $this->orderValue = $orderValue; @@ -44,67 +59,52 @@ public function setOrder(string $orderField, string $orderValue = 'ASC') return $this; } - public function setInclude($include) + public function setInclude(array|string|null $include): self { $this->include = $include; return $this; } - public function setExclude($exclude) + public function setExclude(array|string|null $exclude): self { $this->exclude = $exclude; return $this; } - public function setAggregation(Aggregation $aggregation) + protected function buildAggregation(): array { - $this->aggregation = $aggregation; - - return $this; - } - - public function build(): array - { - if (null !== $this->script) { - $term = [ - 'script' => [ - 'inline' => $this->script, - 'lang' => 'painless', - ], + $build = []; + if ($this->script !== null) { + $build = [ + 'script' => $this->script->build(), ]; - } else { - $term = [ - 'field' => $this->field, + } elseif ($this->field !== null) { + $build = $this->field->build() + [ 'size' => $this->size, ]; } - $query = [ - 'terms' => $term, - ]; - - if (null !== $this->orderField) { - $query['terms']['order'] = [ + if ($this->orderField !== null) { + $build['order'] = [ $this->orderField => $this->orderValue, ]; } - if (null !== $this->include) { - $query['terms']['include'] = $this->include; + if ($this->include !== null) { + $build['include'] = $this->include; } - if (null !== $this->exclude) { - $query['terms']['exclude'] = $this->exclude; + if ($this->exclude !== null) { + $build['exclude'] = $this->exclude; } - if (null !== $this->aggregation) { - $query['aggs'] = [ - $this->aggregation->getName() => $this->aggregation->build(), - ]; - } + return $build; + } - return $query; + protected function getType(): string + { + return 'terms'; } } diff --git a/src/Aggregation/TopHitsAggregation.php b/src/Aggregation/TopHitsAggregation.php index 4f83fdc..0c63594 100644 --- a/src/Aggregation/TopHitsAggregation.php +++ b/src/Aggregation/TopHitsAggregation.php @@ -1,41 +1,52 @@ script = $script; return $this; } - public function setSize(int $size) + public function setSize(int $size): self { $this->size = $size; return $this; } - public function build(): array + protected function getType(): string + { + return 'top_hits'; + } + + protected function buildAggregation(): array { - if (null !== $this->script) { - $term = [ - '_source' => [ - 'includes' => $this->script, - ], - 'size' => $this->size, + $data = [ + 'size' => $this->size, + ]; + + if ($this->script !== null) { + $data['_source'] = [ + 'includes' => $this->script, ]; } - $query = [ - 'top_hits' => $term, - ]; + $this->buildSortTo($data); - return $query; + return $data; } } diff --git a/src/Constants/SortDirections.php b/src/Constants/SortDirections.php new file mode 100644 index 0000000..fb87817 --- /dev/null +++ b/src/Constants/SortDirections.php @@ -0,0 +1,26 @@ + self::ASC, + self::DESC => self::DESC, + ]; + } +} diff --git a/src/Contracts/BuildsArray.php b/src/Contracts/BuildsArray.php new file mode 100644 index 0000000..df59bb9 --- /dev/null +++ b/src/Contracts/BuildsArray.php @@ -0,0 +1,10 @@ + + */ + private array $aggregations = []; + + public function addAggregation(AbstractAggregation $aggregation): self + { + $this->aggregations[] = $aggregation; + + return $this; + } + + /** + * @param array $aggregations + */ + public function setAggregations(array $aggregations): self + { + $this->aggregations = $aggregations; + + return $this; + } + + /** + * @return array|null + */ + public function getAggregations(): ?array + { + return $this->aggregations; + } + + protected function buildAggregationsTo(array &$toArray): self + { + if (count($this->aggregations) === 0) { + return $this; + } + + $aggregations = []; + foreach ($this->aggregations as $aggregation) { + $aggregations[$aggregation->getName()] = $aggregation->build(); + } + + $toArray['aggs'] = $aggregations; + + return $this; + } +} diff --git a/src/Features/HasCollapse.php b/src/Features/HasCollapse.php new file mode 100644 index 0000000..3395710 --- /dev/null +++ b/src/Features/HasCollapse.php @@ -0,0 +1,45 @@ +collapse = is_string($collapseByField) + ? new Collapse($collapseByField) + : $collapseByField; + + return $this; + } + + public function getCollapse(): ?Collapse + { + return $this->collapse; + } + + /** + * Adds collapse to array if field collapsing is set. + */ + protected function buildCollapseTo(array &$toArray): self + { + if ($this->collapse !== null) { + $toArray['collapse'] = $this->collapse->build(); + } + + return $this; + } +} diff --git a/src/Features/HasField.php b/src/Features/HasField.php new file mode 100644 index 0000000..e31dd0b --- /dev/null +++ b/src/Features/HasField.php @@ -0,0 +1,22 @@ +field = $field; + + return $this; + } + + public function getField(): string + { + return $this->field; + } +} diff --git a/src/Features/HasOperator.php b/src/Features/HasOperator.php new file mode 100644 index 0000000..92462b6 --- /dev/null +++ b/src/Features/HasOperator.php @@ -0,0 +1,33 @@ +operator = $operator; + + return $this; + } + + public function buildOperatorTo(array &$array): self + { + if ($this->operator === null) { + return $this; + } + + $array['operator'] = $this->operator; + + return $this; + } + + public function getOperator(): ?string + { + return $this->operator; + } +} diff --git a/src/Features/HasSorting.php b/src/Features/HasSorting.php new file mode 100644 index 0000000..4b72ec7 --- /dev/null +++ b/src/Features/HasSorting.php @@ -0,0 +1,43 @@ +>|array + */ + protected array $sort = []; + + /** + * Adds sort. + * + * @param array|string $config Can be order direction ('desc') or config (['order' => 'asc']] + * + * @return $this + */ + public function addSort(string $field, string|array $config = SortDirections::ASC): self + { + $this->sort[$field] = $config; + + return $this; + } + + /** + * Adds sort settings to array if sorting was set. + */ + protected function buildSortTo(array &$toArray): self + { + if (empty($this->sort) === false) { + foreach ($this->sort as $sort => $config) { + $toArray['sort'][$sort] = $config; + } + } + + return $this; + } +} diff --git a/src/Options/Collapse.php b/src/Options/Collapse.php new file mode 100644 index 0000000..38d1527 --- /dev/null +++ b/src/Options/Collapse.php @@ -0,0 +1,64 @@ + + * + * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-inner-hits + */ + protected array $innerHits = []; + + /** + * The number of concurrent requests allowed to retrieve the inner_hits` per group. + */ + protected ?int $maxConcurrentSearchers = null; + + public function __construct(string $field) + { + $this->field = $field; + } + + public function addInnerHit(InnerHit $hit): self + { + $this->innerHits[] = $hit; + + return $this; + } + + public function build(): array + { + $result = [ + 'field' => $this->field, + ]; + + if ($this->maxConcurrentSearchers !== null) { + $result['max_concurrent_group_searches'] = $this->maxConcurrentSearchers; + } + + if (empty($this->innerHits) === false) { + $result['inner_hits'] = array_map(fn (InnerHit $hit) => $hit->build(), $this->innerHits); + } + + return $result; + } + + public function setMaxConcurrentSearchers(int $maxConcurrentSearchers): self + { + $this->maxConcurrentSearchers = $maxConcurrentSearchers; + + return $this; + } +} diff --git a/src/Options/Field.php b/src/Options/Field.php new file mode 100644 index 0000000..2629b1a --- /dev/null +++ b/src/Options/Field.php @@ -0,0 +1,31 @@ + $this->value, + ]; + } + + public function getValue(): string + { + return $this->value; + } + + public function setValue(string $value): void + { + $this->value = $value; + } +} diff --git a/src/Options/InlineScript.php b/src/Options/InlineScript.php new file mode 100644 index 0000000..45642c3 --- /dev/null +++ b/src/Options/InlineScript.php @@ -0,0 +1,36 @@ +script = $script; + + return $this; + } + + public function getScript(): string + { + return $this->script; + } + + public function build(): array + { + return [ + 'script' => [ + 'inline' => $this->script, + 'lang' => 'painless', + ], + ]; + } +} diff --git a/src/Options/InnerHit.php b/src/Options/InnerHit.php new file mode 100644 index 0000000..6612a64 --- /dev/null +++ b/src/Options/InnerHit.php @@ -0,0 +1,66 @@ + $this->from, + 'size' => $this->size, + 'name' => $this->name, + ]); + $this->buildSortTo($array); + $this->buildCollapseTo($array); + + return $array; + } + + public function getFrom(): ?string + { + return $this->from; + } + + public function setFrom(?string $from): void + { + $this->from = $from; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + return $this; + } +} diff --git a/src/Options/SourceScript.php b/src/Options/SourceScript.php new file mode 100644 index 0000000..f901997 --- /dev/null +++ b/src/Options/SourceScript.php @@ -0,0 +1,35 @@ +script = $script; + + return $this; + } + + public function build(): array + { + return [ + 'script' => [ + 'source' => $this->script, + ], + ]; + } + + public function getScript(): string + { + return $this->script; + } +} diff --git a/src/Query/AbstractMatchQuery.php b/src/Query/AbstractMatchQuery.php index c3977c5..66cb0e0 100644 --- a/src/Query/AbstractMatchQuery.php +++ b/src/Query/AbstractMatchQuery.php @@ -1,39 +1,32 @@ field = $field; - $this->query = $query; - } + use HasField; - public function setField(string $field) - { + public function __construct( + string $field, + protected string $query, + protected ?string $analyzer = null + ) { $this->field = $field; - - return $this; } - public function setQuery(string $query) + public function setQuery(string $query): self { $this->query = $query; return $this; } - public function setAnalyzer(string $analyzer) + public function setAnalyzer(?string $analyzer): self { $this->analyzer = $analyzer; @@ -52,7 +45,7 @@ public function build(): array ], ]; - if (null !== $this->analyzer) { + if ($this->analyzer !== null) { $query[$queryName][$this->field]['analyzer'] = $this->analyzer; } diff --git a/src/Query/BoolQuery.php b/src/Query/BoolQuery.php index 28e8de0..ea3c768 100644 --- a/src/Query/BoolQuery.php +++ b/src/Query/BoolQuery.php @@ -1,25 +1,34 @@ $must + * @param array $mustNot + * @param array $should + * @param array $filter + */ + public function __construct( + private array $must = [], + private array $mustNot = [], + private array $should = [], + private array $filter = [], + ) { + } public function addMust(QueryInterface $query): self { + if ($query === $this) { + throw new QueryException('You are trying to add self to a bool query'); + } + $this->must[] = $query; return $this; @@ -27,6 +36,10 @@ public function addMust(QueryInterface $query): self public function addMustNot(QueryInterface $query): self { + if ($query === $this) { + throw new QueryException('You are trying to add self to a bool query'); + } + $this->mustNot[] = $query; return $this; @@ -34,6 +47,10 @@ public function addMustNot(QueryInterface $query): self public function addShould(QueryInterface $query): self { + if ($query === $this) { + throw new QueryException('You are trying to add self to a bool query'); + } + $this->should[] = $query; return $this; @@ -41,6 +58,10 @@ public function addShould(QueryInterface $query): self public function addFilter(QueryInterface $query): self { + if ($query === $this) { + throw new QueryException('You are trying to add self to a bool query'); + } + $this->filter[] = $query; return $this; @@ -51,48 +72,45 @@ public function isEmpty(): bool return empty($this->must) && empty($this->mustNot) && empty($this->should) - && empty($this->filter) - ; + && empty($this->filter); } public function build(): array { $query = []; - if (!empty($this->must)) { - $query['must'] = []; - foreach ($this->must as $f) { - $query['must'][] = $f->build(); - } - } + $this + ->buildQueries($query, 'should', $this->should) + ->buildQueries($query, 'filter', $this->filter) + ->buildQueries($query, 'must_not', $this->mustNot) + ->buildQueries($query, 'must', $this->must); - if (!empty($this->mustNot)) { - $query['must_not'] = []; - foreach ($this->mustNot as $f) { - $query['must_not'][] = $f->build(); - } + if ((is_countable($query) ? count($query) : 0) === 0) { + throw new QueryException('Empty BoolQuery'); } - if (!empty($this->filter)) { - $query['filter'] = []; - foreach ($this->filter as $f) { - $query['filter'][] = $f->build(); - } - } + return [ + 'bool' => $query, + ]; + } - if (!empty($this->should)) { - $query['should'] = []; - foreach ($this->should as $f) { - $query['should'][] = $f->build(); - } + /** + * @param array $queries + * + * @return $this + */ + protected function buildQueries(array &$query, string $name, array $queries): self + { + if ($queries === []) { + return $this; } - if (empty($query)) { - throw new QueryException('Empty Query'); + $query[$name] = []; + + foreach ($queries as $filter) { + $query[$name][] = $filter->build(); } - return [ - 'bool' => $query, - ]; + return $this; } } diff --git a/src/Query/ExistsQuery.php b/src/Query/ExistsQuery.php index 377a330..adba55f 100644 --- a/src/Query/ExistsQuery.php +++ b/src/Query/ExistsQuery.php @@ -1,36 +1,26 @@ field = $field; } - public function setField(string $field): self - { - $this->field = $field; - - return $this; - } - public function build(): array { - if (null === $this->field) { - throw new QueryException('You need to call setField() on' . __CLASS__); - } - return [ 'exists' => [ - 'field' => $this->field + 'field' => $this->field, ], ]; } diff --git a/src/Query/GeoDistanceQuery.php b/src/Query/GeoDistanceQuery.php index d39f7f1..e6554e1 100644 --- a/src/Query/GeoDistanceQuery.php +++ b/src/Query/GeoDistanceQuery.php @@ -1,23 +1,25 @@ distance = $distance; + use HasField; + + /** + * @param float[]|int[] $position + */ + public function __construct( + private string $distance, + string $field, + private array $position + ) { $this->field = $field; - $this->position = $position; } public function setDistance(string $distance): self @@ -34,13 +36,6 @@ public function setPosition(array $position): self return $this; } - public function setField(string $field) - { - $this->field = $field; - - return $this; - } - public function build(): array { return [ diff --git a/src/Query/GeoShapeQuery.php b/src/Query/GeoShapeQuery.php index 7167872..2fcffb5 100644 --- a/src/Query/GeoShapeQuery.php +++ b/src/Query/GeoShapeQuery.php @@ -1,28 +1,23 @@ field = $field; - $this->type = $type; - $this->coordinates = $coordinates; + private string $relation = 'within'; + + /** + * @param mixed[]|float[]|int[] $coordinates + */ + public function __construct( + private string $field, + private string $type, + private array $coordinates + ) { } public function setType(string $type): self @@ -60,11 +55,11 @@ public function build(): array $this->field => [ 'shape' => [ 'type' => $this->type, - 'coordinates' => $this->coordinates + 'coordinates' => $this->coordinates, ], - 'relation' => $this->relation - ] - ] + 'relation' => $this->relation, + ], + ], ]; } -} \ No newline at end of file +} diff --git a/src/Query/MatchPhrasePrefixQuery.php b/src/Query/MatchPhrasePrefixQuery.php index c74361f..b3b2555 100644 --- a/src/Query/MatchPhrasePrefixQuery.php +++ b/src/Query/MatchPhrasePrefixQuery.php @@ -1,5 +1,7 @@ fields = $fields; - $this->query = $query; + use HasOperator; + + /** + * @param mixed[]|string[] $fields + */ + public function __construct( + protected array $fields, + protected string $query, + protected ?string $type = null, + protected ?string $fuzziness = null, + ?string $operator = null + ) { + $this->operator = $operator; } public function setFields(array $fields): self @@ -52,29 +54,23 @@ public function setFuzziness(string $fuzziness): self public function build(): array { - if (null === $this->fields) { - throw new QueryException('You need to call setFields() on'.__CLASS__); - } - - if (null === $this->query) { - throw new QueryException('You need to call setQuery() on'.__CLASS__); - } - - $query = [ - 'multi_match' => [ - 'query' => $this->query, - 'fields' => $this->fields, - ], + $data = [ + 'query' => $this->query, + 'fields' => $this->fields, ]; - if (null !== $this->type) { - $query['multi_match']['type'] = $this->type; + if ($this->type !== null) { + $data['type'] = $this->type; } - if (null !== $this->fuzziness) { - $query['multi_match']['fuzziness'] = $this->fuzziness; + if ($this->fuzziness !== null) { + $data['fuzziness'] = $this->fuzziness; } - return $query; + $this->buildOperatorTo($data); + + return [ + 'multi_match' => $data, + ]; } } diff --git a/src/Query/NestedQuery.php b/src/Query/NestedQuery.php index faa6418..c2b01a7 100644 --- a/src/Query/NestedQuery.php +++ b/src/Query/NestedQuery.php @@ -1,28 +1,27 @@ path = $path; - $this->query = $query; + public function __construct( + protected ?string $path, + protected QueryInterface $query + ) { } - public function setNestedPath(string $path) + public function setNestedPath(string $path): self { $this->path = $path; return $this; } - public function setQuery(QueryInterface $query) + public function setQuery(QueryInterface $query): self { $this->query = $query; diff --git a/src/Query/PrefixQuery.php b/src/Query/PrefixQuery.php index 0328a8c..29c64c9 100644 --- a/src/Query/PrefixQuery.php +++ b/src/Query/PrefixQuery.php @@ -1,40 +1,35 @@ field = $field; - $this->value = $value; - } + use HasField; - public function setField(string $field) - { + public function __construct( + string $field, + protected string $value, + protected ?float $boost = null + ) { $this->field = $field; - - return $this; } - public function setValue($value) + public function setValue(string $value): self { $this->value = $value; return $this; } - public function setBoost($boost) + public function setBoost(?float $boost): self { $this->boost = $boost; @@ -43,16 +38,9 @@ public function setBoost($boost) public function build(): array { - if (null === $this->field) { - throw new QueryException('You need to call setField() on'.__CLASS__); - } - if (null === $this->value) { - throw new QueryException('You need to call setValue() on'.__CLASS__); - } - $value = $this->value; - if (null !== $this->boost) { + if ($this->boost !== null) { $value = [ 'value' => $this->value, 'boost' => $this->boost, diff --git a/src/Query/Query.php b/src/Query/Query.php index bb466b7..345f89a 100644 --- a/src/Query/Query.php +++ b/src/Query/Query.php @@ -1,27 +1,44 @@ $values + */ public static function terms(string $field, array $values): TermsQuery { return new TermsQuery($field, $values); } - public static function term(string $field, $value): TermQuery + public static function term(string $field, string|int|float|bool $value): TermQuery { return new TermQuery($field, $value); } - public static function wildcard(string $field, $value): WildcardQuery + public static function wildcard(string $field, string $value): WildcardQuery { return new WildcardQuery($field, $value); } - public static function bool(): BoolQuery - { - return new BoolQuery(); + /** + * @param array $must + * @param array $mustNot + * @param array $should + * @param array $filter + */ + public static function bool( + array $must = [], + array $mustNot = [], + array $should = [], + array $filter = [], + ): BoolQuery { + return new BoolQuery($must, $mustNot, $should, $filter); } public static function range(string $field): RangeQuery @@ -34,44 +51,50 @@ public static function nested(string $field, QueryInterface $query): NestedQuery return new NestedQuery($field, $query); } - public static function match(string $field, $query): MatchQuery + public static function match(string $field, string $query): MatchQuery { return new MatchQuery($field, $query); } - public static function matchPhrase(string $field, $query): MatchPhraseQuery + public static function matchPhrase(string $field, string $query): MatchPhraseQuery { return new MatchPhraseQuery($field, $query); } - public static function matchPhrasePrefix(string $field, $query): MatchPhrasePrefixQuery + public static function matchPhrasePrefix(string $field, string $query): MatchPhrasePrefixQuery { return new MatchPhrasePrefixQuery($field, $query); } - public static function multiMatch(array $fields, $query): MultiMatchQuery + public static function multiMatch(array $fields, string $query): MultiMatchQuery { return new MultiMatchQuery($fields, $query); } + /** + * @param float[]|int[] $position + */ public static function geoDistance(string $field, string $distance, array $position): GeoDistanceQuery { return new GeoDistanceQuery($distance, $field, $position); } + /** + * @param mixed[]|float[]|int[] $coordinates + */ public static function geoShape(string $field, string $type, array $coordinates): GeoShapeQuery { return new GeoShapeQuery($field, $type, $coordinates); } - public static function prefix(string $field, $value): PrefixQuery + public static function prefix(string $field, string $value): PrefixQuery { return new PrefixQuery($field, $value); } public static function queryString(string $query, string $defaultField = null): QueryStringQuery { - return new QueryStringQuery($query); + return new QueryStringQuery($query, $defaultField); } public static function rankFeature(string $field): RankFeatureQuery diff --git a/src/Query/QueryInterface.php b/src/Query/QueryInterface.php deleted file mode 100644 index da2d8c4..0000000 --- a/src/Query/QueryInterface.php +++ /dev/null @@ -1,8 +0,0 @@ -query = $query; + public function __construct( + protected string $query, + protected ?string $defaultField = null + ) { } public function setQuery(string $query): self @@ -22,7 +21,7 @@ public function setQuery(string $query): self return $this; } - public function setDefaultField(string $defaultField): self + public function setDefaultField(?string $defaultField): self { $this->defaultField = $defaultField; @@ -31,16 +30,16 @@ public function setDefaultField(string $defaultField): self public function build(): array { - $query = [ - 'query_string' => [ - 'query' => $this->query, - ], + $build = [ + 'query' => $this->query, ]; - if (null !== $this->defaultField) { - $query['query_string']['default_field'] = $this->defaultField; + if ($this->defaultField !== null) { + $build['default_field'] = $this->defaultField; } - return $query; + return [ + 'query_string' => $build, + ]; } } diff --git a/src/Query/RangeQuery.php b/src/Query/RangeQuery.php index a5e53e6..2026f9d 100644 --- a/src/Query/RangeQuery.php +++ b/src/Query/RangeQuery.php @@ -1,52 +1,49 @@ field = $field; } - public function setField(string $field) - { - $this->field = $field; - - return $this; - } - - public function gt($value) + public function gt(int|float|string|null $value): self { $this->gt = $value; return $this; } - public function lt($value) + public function lt(int|float|string|null $value): self { $this->lt = $value; return $this; } - public function gte($value) + public function gte(int|float|string|null $value): self { $this->gte = $value; return $this; } - public function lte($value) + public function lte(int|float|string|null $value): self { $this->lte = $value; @@ -57,21 +54,24 @@ public function build(): array { $query = []; - if (null !== $this->gt) { + if ($this->gt !== null) { $query['gt'] = $this->gt; } - if (null !== $this->lt) { + + if ($this->lt !== null) { $query['lt'] = $this->lt; } - if (null !== $this->gte) { + + if ($this->gte !== null) { $query['gte'] = $this->gte; } - if (null !== $this->lte) { + + if ($this->lte !== null) { $query['lte'] = $this->lte; } if (empty($query)) { - throw new QueryException('Empty Query'); + throw new QueryException('Empty RangeQuery'); } return [ diff --git a/src/Query/RankFeatureQuery.php b/src/Query/RankFeatureQuery.php index f4e666f..fb853c7 100644 --- a/src/Query/RankFeatureQuery.php +++ b/src/Query/RankFeatureQuery.php @@ -1,30 +1,24 @@ field = $field; - } + use HasField; - public function setField(string $field): self - { + public function __construct( + string $field, + protected ?float $boost = null + ) { $this->field = $field; - - return $this; } - public function setBoost(float $boost): self + public function setBoost(?float $boost): self { $this->boost = $boost; @@ -33,16 +27,16 @@ public function setBoost(float $boost): self public function build(): array { - $query = [ - 'rank_feature' => [ - 'field' => $this->field, - ], + $build = [ + 'field' => $this->field, ]; - if (null !== $this->boost) { - $query['rank_feature']['boost'] = $this->boost; + if ($this->boost !== null) { + $build['boost'] = $this->boost; } - return $query; + return [ + 'rank_feature' => $build, + ]; } } diff --git a/src/Query/TermQuery.php b/src/Query/TermQuery.php index b7bf442..cf98ce7 100644 --- a/src/Query/TermQuery.php +++ b/src/Query/TermQuery.php @@ -1,31 +1,27 @@ field = $field; - $this->value = $value; } - public function setField(string $field) - { - $this->field = $field; - - return $this; - } - - public function setValue($value) + public function setValue(string|int|float|bool $value): self { $this->value = $value; @@ -34,13 +30,6 @@ public function setValue($value) public function build(): array { - if (null === $this->field) { - throw new QueryException('You need to call setField() on'.__CLASS__); - } - if (null === $this->value) { - throw new QueryException('You need to call setValue() on'.__CLASS__); - } - return [ 'term' => [ $this->field => $this->value, diff --git a/src/Query/TermsQuery.php b/src/Query/TermsQuery.php index 4fbabdc..d879202 100644 --- a/src/Query/TermsQuery.php +++ b/src/Query/TermsQuery.php @@ -1,28 +1,24 @@ field = $field; - $this->values = $values; - } - - public function setField(string $field): self - { + use HasField; + + /** + * @param array $values + */ + public function __construct( + string $field, + protected array $values + ) { $this->field = $field; - - return $this; } public function setValues(array $values): self @@ -34,16 +30,10 @@ public function setValues(array $values): self public function build(): array { - if (null === $this->field) { - throw new QueryException('You need to call setField() on'.__CLASS__); - } - if (empty($this->values)) { - throw new QueryException('You need to call setValues() on'.__CLASS__); - } - return [ 'terms' => [ - $this->field => $this->values, + $this->field => array_values($this->values), + // Ensure that user did not provide incorrect dictionary ], ]; } diff --git a/src/Query/WildcardQuery.php b/src/Query/WildcardQuery.php index 2114610..d806312 100644 --- a/src/Query/WildcardQuery.php +++ b/src/Query/WildcardQuery.php @@ -1,41 +1,35 @@ field = $field; - $this->value = $value; } - public function setField(string $field): self - { - $this->field = $field; - - return $this; - } - - public function setValue($value): self + public function setValue(string $value): self { $this->value = $value; return $this; } - public static function escapeWildcards($string): string + public static function escapeWildcards(string $string): string { $escapeChars = ['*', '?']; foreach ($escapeChars as $escapeChar) { - $string = str_replace($escapeChar, '\\'.$escapeChar, $string); + $string = str_replace($escapeChar, '\\' . $escapeChar, $string); } return $string; @@ -43,13 +37,6 @@ public static function escapeWildcards($string): string public function build(): array { - if (null === $this->field) { - throw new QueryException('You need to call setField() on'.__CLASS__); - } - if (null === $this->value) { - throw new QueryException('You need to call setValue() on'.__CLASS__); - } - return [ 'wildcard' => [ $this->field => $this->value, diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index fbb0964..2e72a16 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -1,38 +1,33 @@ source = $source; @@ -60,20 +55,6 @@ public function setSize(int $size): self return $this; } - public function addSort(string $field, array $config): self - { - $this->sort[$field] = $config; - - return $this; - } - - public function addAggregation(Aggregation $aggregation): self - { - $this->aggregations[] = $aggregation; - - return $this; - } - public function setQuery(QueryInterface $query): self { $this->query = $query; @@ -94,44 +75,59 @@ public function build(): array 'body' => [], ]; - if (null !== $this->index) { + if ($this->index !== null) { $query['index'] = $this->index; } - if (null !== $this->from) { + if ($this->from !== null) { $query['from'] = $this->from; } - if (null !== $this->size) { + if ($this->size !== null) { $query['size'] = $this->size; } - if (null !== $this->source) { + if ($this->source !== null) { $query['_source'] = $this->source; } - if (!empty($this->aggregations)) { - $query['body']['aggs'] = []; - foreach ($this->aggregations as $aggregation) { - $query['body']['aggs'][$aggregation->getName()] = $aggregation->buildRecursivly(); - } - } - - if (null !== $this->query) { + if ($this->query !== null) { $query['body']['query'] = $this->query->build(); } - if (null !== $this->postFilter) { + if ($this->postFilter !== null) { $query['body']['post_filter'] = $this->postFilter->build(); } - if (!empty($this->sort)) { - $query['body']['sort'] = []; - foreach ($this->sort as $sort => $config) { - $query['body']['sort'][$sort] = $config; - } - } + $this->buildSortTo($query['body']) + ->buildAggregationsTo($query['body']) + ->buildCollapseTo($query['body']); return $query; } + + public function getSource(): bool|array|string|null + { + return $this->source; + } + + public function getFrom(): ?int + { + return $this->from; + } + + public function getSize(): ?int + { + return $this->size; + } + + public function getQuery(): ?QueryInterface + { + return $this->query; + } + + public function getPostFilter(): ?QueryInterface + { + return $this->postFilter; + } } diff --git a/src/QueryException.php b/src/QueryException.php index 58f009e..678abcf 100644 --- a/src/QueryException.php +++ b/src/QueryException.php @@ -1,5 +1,7 @@ setField('city'); $this->assertEquals([ 'cardinality' => [ @@ -20,7 +21,20 @@ public function test_it_build_the_aggregation_using_a_field() ], $query->build()); } - public function test_it_build_the_aggregation_using_a_script() + public function testItBuildTheAggregationUsingAFieldViaSet(): void + { + $query = new CardinalityAggregation('city'); + $query->setScript('test2'); + $query->setField('test'); + + $this->assertEquals([ + 'cardinality' => [ + 'field' => 'test', + ], + ], $query->build()); + } + + public function testItBuildTheAggregationUsingAScriptViaSet(): void { $query = new CardinalityAggregation('city'); $query->setScript('doc.city.value'); @@ -34,16 +48,31 @@ public function test_it_build_the_aggregation_using_a_script() ], $query->build()); } - public function test_it_fail_building_the_aggregation_without_field() + public function testItBuildTheAggregationUsingAScript(): void { - $query = new CardinalityAggregation('city'); + $query = new CardinalityAggregation('city', new SourceScript('asd')); - $this->expectException(QueryException::class); + $this->assertEquals([ + 'cardinality' => [ + 'script' => [ + 'source' => 'asd', + ], + ], + ], $query->build()); + } - $query->build(); + public function testItBuildTheAggregationUsingAFieldAndDifferentName(): void + { + $query = new CardinalityAggregation('city', 'test'); + + $this->assertEquals([ + 'cardinality' => [ + 'field' => 'test', + ], + ], $query->build()); } - public function test_it_build_the_aggregation_with_missing_value() + public function testItBuildTheAggregationWithMissingValue(): void { $query = new CardinalityAggregation('city'); $query->setField('city'); @@ -57,7 +86,7 @@ public function test_it_build_the_aggregation_with_missing_value() ], $query->build()); } - public function test_it_build_the_aggregation_with_a_precision_threshold() + public function testItBuildTheAggregationWithAPrecisionThreshold(): void { $query = new CardinalityAggregation('city'); $query->setField('city'); diff --git a/tests/Aggregation/DateHistogramAggregationTest.php b/tests/Aggregation/DateHistogramAggregationTest.php index 0b3bbed..10afce8 100644 --- a/tests/Aggregation/DateHistogramAggregationTest.php +++ b/tests/Aggregation/DateHistogramAggregationTest.php @@ -1,57 +1,50 @@ expectException(QueryException::class); - - $aggregation->build(); - } - - public function test_it_cannot_be_built_without_calendar_interval() - { - $aggregation = new DateHistogramAggregation('price_evolution'); - $aggregation->setField('price'); - - $this->expectException(QueryException::class); + $aggregation = new DateHistogramAggregation('price_evolution', '1d'); - $aggregation->build(); + $this->assertEquals([ + 'date_histogram' => [ + 'field' => 'price_evolution', + 'calendar_interval' => '1d', + ], + ], $aggregation->build()); } - public function test_it_build_the_aggregation() + public function testWithDifferentName(): void { - $aggregation = new DateHistogramAggregation('price_evolution'); - $aggregation->setField('price'); - $aggregation->setCalendarInterval('1d'); + $aggregation = new DateHistogramAggregation('price_evolution', '1d', 'price'); $this->assertEquals([ 'date_histogram' => [ 'field' => 'price', 'calendar_interval' => '1d', - ] + ], ], $aggregation->build()); } - public function test_it_build_the_aggregation_recursivly() + public function testItBuildTheAggregationWithSet(): void { - $aggregation = new DateHistogramAggregation('price_evolution'); - $aggregation->setField('price'); - $aggregation->setCalendarInterval('1d'); + $aggregation = new DateHistogramAggregation('price_evolution', '1d', 'price'); + + $aggregation->setField('price2'); + $aggregation->setCalendarInterval('2d'); $this->assertEquals([ 'date_histogram' => [ - 'field' => 'price', - 'calendar_interval' => '1d', - ] + 'field' => 'price2', + 'calendar_interval' => '2d', + ], ], $aggregation->build()); } } diff --git a/tests/Aggregation/MaxAggregationTest.php b/tests/Aggregation/MaxAggregationTest.php index a6cf823..31d85c5 100644 --- a/tests/Aggregation/MaxAggregationTest.php +++ b/tests/Aggregation/MaxAggregationTest.php @@ -1,14 +1,27 @@ assertEquals([ + 'max' => [ + 'field' => 'max_price', + ], + ], $query->build()); + } + + public function testItBuildTheAggregationUsingAField(): void { $query = new MaxAggregation('max_price'); $query->setField('price'); @@ -20,10 +33,9 @@ public function test_it_build_the_aggregation_using_a_field() ], $query->build()); } - public function test_it_build_the_aggregation_using_a_script() + public function testItBuildTheAggregationUsingAScript(): void { - $query = new MaxAggregation('max_price'); - $query->setScript('doc.price.value'); + $query = new MaxAggregation('max_price', new SourceScript('doc.price.value')); $this->assertEquals([ 'max' => [ @@ -34,16 +46,21 @@ public function test_it_build_the_aggregation_using_a_script() ], $query->build()); } - public function test_it_fail_building_the_aggregation_without_field() + public function testItBuildTheAggregationUsingAScriptViaSet(): void { $query = new MaxAggregation('max_price'); + $query->setScript('doc.price.value'); - $this->expectException(QueryException::class); - - $query->build(); + $this->assertEquals([ + 'max' => [ + 'script' => [ + 'source' => 'doc.price.value', + ], + ], + ], $query->build()); } - public function test_it_build_the_aggregation_with_missing_value() + public function testItBuildTheAggregationWithMissingValue(): void { $query = new MaxAggregation('max_price'); $query->setField('price'); diff --git a/tests/Aggregation/MinAggregationTest.php b/tests/Aggregation/MinAggregationTest.php index 423e47c..152f3c3 100644 --- a/tests/Aggregation/MinAggregationTest.php +++ b/tests/Aggregation/MinAggregationTest.php @@ -1,14 +1,15 @@ setField('price'); @@ -20,7 +21,7 @@ public function test_it_build_the_aggregation_using_a_field() ], $query->build()); } - public function test_it_build_the_aggregation_using_a_script() + public function testItBuildTheAggregationUsingAScript(): void { $query = new MinAggregation('min_price'); $query->setScript('doc.price.value'); @@ -34,19 +35,20 @@ public function test_it_build_the_aggregation_using_a_script() ], $query->build()); } - public function test_it_fail_building_the_aggregation_without_field() + public function testWithFieldName(): void { $query = new MinAggregation('min_price'); - $this->expectException(QueryException::class); - - $query->build(); + $this->assertEquals([ + 'min' => [ + 'field' => 'min_price', + ], + ], $query->build()); } - public function test_it_build_the_aggregation_with_missing_value() + public function testItBuildTheAggregationWithMissingValue(): void { - $query = new MinAggregation('min_price'); - $query->setField('price'); + $query = new MinAggregation('min_price', 'price'); $query->setMissing(10); $this->assertEquals([ diff --git a/tests/Aggregation/SumAggregationTest.php b/tests/Aggregation/SumAggregationTest.php index 278b43e..e5feaac 100644 --- a/tests/Aggregation/SumAggregationTest.php +++ b/tests/Aggregation/SumAggregationTest.php @@ -1,14 +1,15 @@ setField('price'); @@ -20,7 +21,7 @@ public function test_it_build_the_aggregation_using_a_field() ], $query->build()); } - public function test_it_build_the_aggregation_using_a_script() + public function testItBuildTheAggregationUsingAScript(): void { $query = new SumAggregation('sum_price'); $query->setScript('doc.price.value'); @@ -34,19 +35,20 @@ public function test_it_build_the_aggregation_using_a_script() ], $query->build()); } - public function test_it_fail_building_the_aggregation_without_field() + public function testWithFieldName(): void { $query = new SumAggregation('sum_price'); - $this->expectException(QueryException::class); - - $query->build(); + $this->assertEquals([ + 'sum' => [ + 'field' => 'sum_price', + ], + ], $query->build()); } - public function test_it_build_the_aggregation_with_missing_value() + public function testItBuildTheAggregationWithMissingValue(): void { - $query = new SumAggregation('sum_price'); - $query->setField('price'); + $query = new SumAggregation('sum_price', 'price'); $query->setMissing(10); $this->assertEquals([ diff --git a/tests/Query/BoolQueryTest.php b/tests/Query/BoolQueryTest.php index 779e9fe..90dfac8 100644 --- a/tests/Query/BoolQueryTest.php +++ b/tests/Query/BoolQueryTest.php @@ -1,5 +1,7 @@ expectException(QueryException::class); $boolQuery->build(); } - public function test_it_add_a_must_clause() + public function testAddFilterWithSameObject(): void { - $boolQuery = new BoolQuery('must_clause'); + $this->expectExceptionMessage('You are trying to add self to a bool query'); + $boolQuery = new BoolQuery(); + + $boolQuery->addFilter($boolQuery); + $boolQuery->build(); + } + + public function testAddShouldWithSameObject(): void + { + $this->expectExceptionMessage('You are trying to add self to a bool query'); + $boolQuery = new BoolQuery(); + + $boolQuery->addShould($boolQuery); + $boolQuery->build(); + } + + public function testAddMustNotWithSameObject(): void + { + $this->expectExceptionMessage('You are trying to add self to a bool query'); + $boolQuery = new BoolQuery(); + + $boolQuery->addMustNot($boolQuery); + $boolQuery->build(); + } + + public function testAddMustWithSameObject(): void + { + $this->expectExceptionMessage('You are trying to add self to a bool query'); + $boolQuery = new BoolQuery(); + + $boolQuery->addMust($boolQuery); + $boolQuery->build(); + } + + public function testItAddAMustClause(): void + { + $boolQuery = new BoolQuery(); $boolQuery->addMust(Query::term('field', 'value')); + $query = $boolQuery->build(); $this->assertEquals([ @@ -38,11 +77,12 @@ public function test_it_add_a_must_clause() ], $query); } - public function test_it_add_a_must_not_clause() + public function testItAddAMustNotClause(): void { - $boolQuery = new BoolQuery('must_clause'); + $boolQuery = new BoolQuery(); $boolQuery->addMustNot(Query::term('field', 'value')); + $query = $boolQuery->build(); $this->assertEquals([ @@ -58,11 +98,12 @@ public function test_it_add_a_must_not_clause() ], $query); } - public function test_it_add_a_should_clause() + public function testItAddAShouldClause(): void { - $boolQuery = new BoolQuery('must_clause'); + $boolQuery = new BoolQuery(); $boolQuery->addShould(Query::term('field', 'value')); + $query = $boolQuery->build(); $this->assertEquals([ @@ -78,11 +119,12 @@ public function test_it_add_a_should_clause() ], $query); } - public function test_it_add_a_filter_clause() + public function testItAddAFilterClause(): void { - $boolQuery = new BoolQuery('must_clause'); + $boolQuery = new BoolQuery(); $boolQuery->addFilter(Query::term('field', 'value')); + $query = $boolQuery->build(); $this->assertEquals([ diff --git a/tests/Query/ExistsQueryTest.php b/tests/Query/ExistsQueryTest.php index 108da4c..fa8779c 100644 --- a/tests/Query/ExistsQueryTest.php +++ b/tests/Query/ExistsQueryTest.php @@ -1,5 +1,7 @@ build()); } - } diff --git a/tests/Query/GeoDistanceQueryTest.php b/tests/Query/GeoDistanceQueryTest.php index 7070c9d..ccce72e 100644 --- a/tests/Query/GeoDistanceQueryTest.php +++ b/tests/Query/GeoDistanceQueryTest.php @@ -1,5 +1,7 @@ setRelation('outside'); @@ -20,11 +20,11 @@ public function test_it_builds_the_query() 'coordinates' => [ 'shape' => [ 'type' => 'polygon', - 'coordinates' => [[-70, 40]] + 'coordinates' => [[-70, 40]], ], - 'relation' => 'outside' - ] - ] + 'relation' => 'outside', + ], + ], ], $query->build()); } -} \ No newline at end of file +} diff --git a/tests/Query/MatchPhrasePrefixQueryTest.php b/tests/Query/MatchPhrasePrefixQueryTest.php index da01d76..0b7a44f 100644 --- a/tests/Query/MatchPhrasePrefixQueryTest.php +++ b/tests/Query/MatchPhrasePrefixQueryTest.php @@ -1,5 +1,7 @@ build()); } - public function test_it_build_the_query_with_an_analyzer() + public function testItBuildTheQueryWithAnAnalyzer(): void { $query = new MatchPhrasePrefixQuery('title', 'a brown fox'); $query->setAnalyzer('custom_analyzer'); diff --git a/tests/Query/MatchPhraseQueryTest.php b/tests/Query/MatchPhraseQueryTest.php index 33b9b9c..900eb9c 100644 --- a/tests/Query/MatchPhraseQueryTest.php +++ b/tests/Query/MatchPhraseQueryTest.php @@ -1,5 +1,7 @@ build()); } - public function test_it_build_the_query_with_an_analyzer() + public function testItBuildTheQueryWithAnAnalyzer(): void { $query = new MatchPhraseQuery('title', 'a brown fox'); $query->setAnalyzer('custom_analyzer'); diff --git a/tests/Query/MatchQueryTest.php b/tests/Query/MatchQueryTest.php index a87248d..108199a 100644 --- a/tests/Query/MatchQueryTest.php +++ b/tests/Query/MatchQueryTest.php @@ -1,5 +1,7 @@ build()); } - public function test_it_build_the_query_with_an_analyzer() + public function testItBuildTheQueryWithAnAnalyzer(): void { $query = new MatchQuery('title', 'a brown fox'); $query->setAnalyzer('custom_analyzer'); diff --git a/tests/Query/QueryStringTest.php b/tests/Query/QueryStringTest.php index 43631b2..b53ce15 100644 --- a/tests/Query/QueryStringTest.php +++ b/tests/Query/QueryStringTest.php @@ -1,5 +1,7 @@ [ 'query' => 'brown fox', 'default_field' => 'test', - ] + ], ], $queryStringQuery->build()); } } diff --git a/tests/Query/RankFeatureTest.php b/tests/Query/RankFeatureTest.php index b75a939..c33f2e6 100644 --- a/tests/Query/RankFeatureTest.php +++ b/tests/Query/RankFeatureTest.php @@ -1,5 +1,7 @@ [ 'field' => 'rank', 'boost' => 0.9, - ] + ], ], $rankFeatureQuery->build()); } } diff --git a/tests/Query/TermsQueryTest.php b/tests/Query/TermsQueryTest.php index d1be400..5e48de4 100644 --- a/tests/Query/TermsQueryTest.php +++ b/tests/Query/TermsQueryTest.php @@ -1,5 +1,7 @@ assertFalse($query['_source']); } - public function test_it_set_the_source() + public function testItSetTheSource(): void { $queryBuilder = new QueryBuilder(); @@ -29,7 +31,7 @@ public function test_it_set_the_source() $this->assertEquals('obj.*', $query['_source']); } - public function test_it_set_the_index() + public function testItSetTheIndex(): void { $queryBuilder = new QueryBuilder(); @@ -40,7 +42,7 @@ public function test_it_set_the_index() $this->assertEquals('index1', $query['index']); } - public function test_it_set_the_size() + public function testItSetTheSize(): void { $queryBuilder = new QueryBuilder(); @@ -51,7 +53,7 @@ public function test_it_set_the_size() $this->assertEquals(50, $query['size']); } - public function test_it_set_the_from() + public function testItSetTheFrom(): void { $queryBuilder = new QueryBuilder(); @@ -62,18 +64,27 @@ public function test_it_set_the_from() $this->assertEquals(50, $query['from']); } - public function test_it_allow_to_sort() + public function testItAllowToSort(): void { $queryBuilder = new QueryBuilder(); - $queryBuilder->addSort('field', ['order' => 'desc']); - $queryBuilder->addSort('field2', ['order' => 'asc']); + $queryBuilder->addSort('field', [ + 'order' => 'desc', + ]); + $queryBuilder->addSort('field2', [ + 'order' => 'asc', + ]); $query = $queryBuilder->build(); $this->assertEquals([ - 'field' => ['order' => 'desc'], - 'field2' => ['order' => 'asc'], + 'field' => [ + 'order' => 'desc', + ], + 'field2' => [ + 'order' => 'asc', + + ], ], $query['body']['sort']); } } From 3f0b7cddea1b29224767babb51902268dbff71a2 Mon Sep 17 00:00:00 2001 From: Martin Kluska Date: Tue, 5 Apr 2022 15:26:43 +0200 Subject: [PATCH 2/5] Add range aggregation --- src/Aggregation/RangesAggregation.php | 77 ++++++++++ tests/Aggregation/RangesAggregationTest.php | 159 ++++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 src/Aggregation/RangesAggregation.php create mode 100644 tests/Aggregation/RangesAggregationTest.php diff --git a/src/Aggregation/RangesAggregation.php b/src/Aggregation/RangesAggregation.php new file mode 100644 index 0000000..eae2528 --- /dev/null +++ b/src/Aggregation/RangesAggregation.php @@ -0,0 +1,77 @@ + $ranges pass desired ranges that will be converted to + * linear range + * @param array $aggregations + * @param bool $equalConditionOnToRange Se to true if you want to do a histogram with 0 + * - 10, 10 - 15, and correctly count the number + * (entry with 10 will be in first and seconds + * segment + */ + public function __construct( + string $name, + string $field, + private array $ranges, + array $aggregations = [], + private bool $equalConditionOnToRange = false + ) { + parent::__construct($name, $aggregations); + $this->field = $field; + } + + public function getRanges(): array + { + return $this->ranges; + } + + public function setRanges(array $ranges): self + { + $this->ranges = $ranges; + + return $this; + } + + protected function getType(): string + { + return 'range'; + } + + protected function buildAggregation(): array + { + $ranges = []; + $prevValue = 0; + foreach ($this->ranges as $range) { + $to = $range + ($this->equalConditionOnToRange ? 1 : 0); // To value is not included - increase it by 1 + $ranges[] = [ + 'from' => $prevValue, + 'to' => $to, + 'key' => $range, + ]; + $prevValue = $range; // Do not use increased value + } + + // Append "others" + $ranges[] = [ + 'key' => $prevValue . '-*', + 'from' => $prevValue, + ]; + + return [ + 'field' => $this->field, + 'ranges' => $ranges, + ]; + } +} diff --git a/tests/Aggregation/RangesAggregationTest.php b/tests/Aggregation/RangesAggregationTest.php new file mode 100644 index 0000000..c6acfaa --- /dev/null +++ b/tests/Aggregation/RangesAggregationTest.php @@ -0,0 +1,159 @@ + [ + 'field' => 'option_236', + 'ranges' => [ + 0 => [ + 'from' => 0, + 'to' => 10, + 'key' => 10, + ], + 1 => [ + 'from' => 10, + 'to' => 50, + 'key' => 50, + ], + 2 => [ + 'from' => 50, + 'to' => 100, + 'key' => 100, + ], + 3 => [ + 'from' => 100, + 'to' => 200, + 'key' => 200, + ], + 4 => [ + 'from' => 200, + 'to' => 350, + 'key' => 350, + ], + 5 => [ + 'from' => 350, + 'to' => 500, + 'key' => 500, + ], + 6 => [ + 'from' => 500, + 'to' => 750, + 'key' => 750, + ], + 7 => [ + 'from' => 750, + 'to' => 1000, + 'key' => 1000, + ], + 8 => [ + 'from' => 1000, + 'to' => 1500, + 'key' => 1500, + ], + 9 => [ + 'from' => 1500, + 'to' => 2500, + 'key' => 2500, + ], + 10 => [ + 'key' => '2500-*', + 'from' => 2500, + ], + ], + ], + ]; + $built = $instance->build(); + $this->assertEquals($expected, $built); + } + + public function testGetBeachDistancesRangesAggregationWithEqualRanges(): void + { + $instance = new RangesAggregation( + 'beach', + 'option_236', + [10, 50, 100, 200, 350, 500, 750, 1000, 1500, 2500], + [], + true + ); + + $expected = [ + 'range' => [ + 'field' => 'option_236', + 'ranges' => [ + 0 => [ + 'from' => 0, + 'to' => 11, + 'key' => 10, + ], + 1 => [ + 'from' => 10, + 'to' => 51, + 'key' => 50, + ], + 2 => [ + 'from' => 50, + 'to' => 101, + 'key' => 100, + ], + 3 => [ + 'from' => 100, + 'to' => 201, + 'key' => 200, + ], + 4 => [ + 'from' => 200, + 'to' => 351, + 'key' => 350, + ], + 5 => [ + 'from' => 350, + 'to' => 501, + 'key' => 500, + ], + 6 => [ + 'from' => 500, + 'to' => 751, + 'key' => 750, + ], + 7 => [ + 'from' => 750, + 'to' => 1001, + 'key' => 1000, + ], + 8 => [ + 'from' => 1000, + 'to' => 1501, + 'key' => 1500, + ], + 9 => [ + 'from' => 1500, + 'to' => 2501, + 'key' => 2500, + ], + 10 => [ + 'key' => '2500-*', + 'from' => 2500, + ], + ], + ], + ]; + $built = $instance->build(); + $this->assertEquals($expected, $built); + } +} From 7b9f47fd15efa8e300c396afb989008baadce9ba Mon Sep 17 00:00:00 2001 From: Martin Kluska Date: Tue, 5 Apr 2022 15:27:23 +0200 Subject: [PATCH 3/5] Add histogram and width histogram aggregations --- src/Aggregation/HistogramAggregation.php | 48 +++++++++++++++++++ src/Aggregation/WidthHistogramAggregation.php | 35 ++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 src/Aggregation/HistogramAggregation.php create mode 100644 src/Aggregation/WidthHistogramAggregation.php diff --git a/src/Aggregation/HistogramAggregation.php b/src/Aggregation/HistogramAggregation.php new file mode 100644 index 0000000..5562dea --- /dev/null +++ b/src/Aggregation/HistogramAggregation.php @@ -0,0 +1,48 @@ + $aggregations + */ + public function __construct( + string $name, + string $field, + private int $interval, + array $aggregations = [] + ) { + parent::__construct($name, $aggregations); + $this->field = $field; + } + + public function getInterval(): int + { + return $this->interval; + } + + public function setInterval(int $interval): void + { + $this->interval = $interval; + } + + protected function getType(): string + { + return 'histogram'; + } + + protected function buildAggregation(): array + { + return [ + 'field' => $this->field, + 'interval' => $this->interval, + ]; + } +} diff --git a/src/Aggregation/WidthHistogramAggregation.php b/src/Aggregation/WidthHistogramAggregation.php new file mode 100644 index 0000000..88521dc --- /dev/null +++ b/src/Aggregation/WidthHistogramAggregation.php @@ -0,0 +1,35 @@ +field = $field; + } + + protected function getType(): string + { + return 'variable_width_histogram'; + } + + protected function buildAggregation(): array + { + return [ + 'field' => $this->field, + 'buckets' => $this->buckets, + ]; + } +} From 5a7032f0599da8217f34cb8d13e929a3316d4286 Mon Sep 17 00:00:00 2001 From: Martin Kluska Date: Tue, 5 Apr 2022 15:27:36 +0200 Subject: [PATCH 4/5] Add geo bounding box query --- src/Entities/GpsPointEntity.php | 14 ++++++++++ src/Query/GeoBoundingBoxQuery.php | 45 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/Entities/GpsPointEntity.php create mode 100644 src/Query/GeoBoundingBoxQuery.php diff --git a/src/Entities/GpsPointEntity.php b/src/Entities/GpsPointEntity.php new file mode 100644 index 0000000..7f95c0a --- /dev/null +++ b/src/Entities/GpsPointEntity.php @@ -0,0 +1,14 @@ +field = $field; + } + + public function build(): array + { + return [ + 'geo_bounding_box' => [ + $this->field => [ + 'top_left' => $this->pointToArray($this->topLeft), + 'bottom_right' => $this->pointToArray($this->bottomRight), + ], + ], + ]; + } + + protected function pointToArray(GpsPointEntity $entity): array + { + return [ + 'lat' => $entity->lat, + 'lon' => $entity->lon, + ]; + } +} From 6da5cae72c53d4de03ea79a2550ae5216e3295f3 Mon Sep 17 00:00:00 2001 From: Martin Kluska Date: Tue, 5 Apr 2022 15:31:31 +0200 Subject: [PATCH 5/5] Add stats aggregation --- src/Aggregation/StatsAggregation.php | 16 ++++++ tests/Aggregation/StatsAggregationTest.php | 61 ++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 src/Aggregation/StatsAggregation.php create mode 100644 tests/Aggregation/StatsAggregationTest.php diff --git a/src/Aggregation/StatsAggregation.php b/src/Aggregation/StatsAggregation.php new file mode 100644 index 0000000..8153032 --- /dev/null +++ b/src/Aggregation/StatsAggregation.php @@ -0,0 +1,16 @@ +setField('price'); + + $this->assertEquals([ + 'stats' => [ + 'field' => 'price', + ], + ], $query->build()); + } + + public function testItBuildTheAggregationUsingAScript(): void + { + $query = new StatsAggregation('price'); + $query->setScript('doc.price.value'); + + $this->assertEquals([ + 'stats' => [ + 'script' => [ + 'source' => 'doc.price.value', + ], + ], + ], $query->build()); + } + + public function testWithFieldName(): void + { + $query = new StatsAggregation('price'); + + $this->assertEquals([ + 'stats' => [ + 'field' => 'price', + ], + ], $query->build()); + } + + public function testItBuildTheAggregationWithMissingValue(): void + { + $query = new StatsAggregation('price', 'price'); + $query->setMissing(10); + + $this->assertEquals([ + 'stats' => [ + 'field' => 'price', + 'missing' => 10, + ], + ], $query->build()); + } +}