├── tools └── php-cs-fixer │ ├── .gitignore │ └── composer.json ├── .gitignore ├── src ├── QueryException.php ├── Contracts │ ├── QueryInterface.php │ └── BuildsArray.php ├── Entities │ └── GpsPointEntity.php ├── Aggregation │ ├── MaxAggregation.php │ ├── MinAggregation.php │ ├── SumAggregation.php │ ├── AvgAggregation.php │ ├── ReverseNestedAggregation.php │ ├── StatsAggregation.php │ ├── ValueCountAggregation.php │ ├── WidthHistogramAggregation.php │ ├── FilterAggregation.php │ ├── NestedAggregation.php │ ├── TopHitsAggregation.php │ ├── AbstractAggregation.php │ ├── CardinalityAggregation.php │ ├── HistogramAggregation.php │ ├── DateHistogramAggregation.php │ ├── RangesAggregation.php │ ├── MetricAggregation.php │ ├── TermsAggregation.php │ └── Aggregation.php ├── Query │ ├── MatchPhraseQuery.php │ ├── MatchPhrasePrefixQuery.php │ ├── ExistsQuery.php │ ├── FunctionsQuery.php │ ├── RankFeatureQuery.php │ ├── NestedQuery.php │ ├── TermsQuery.php │ ├── WildcardQuery.php │ ├── GeoDistanceQuery.php │ ├── AbstractMatchQuery.php │ ├── MatchQuery.php │ ├── PrefixQuery.php │ ├── TermQuery.php │ ├── GeoShapeQuery.php │ ├── FunctionScoreQuery.php │ ├── GeoBoundingBoxQuery.php │ ├── MultiMatchQuery.php │ ├── RangeQuery.php │ ├── QueryStringQuery.php │ ├── BoolQuery.php │ ├── Query.php │ └── SimpleQueryStringQuery.php ├── Features │ ├── HasField.php │ ├── HasBoost.php │ ├── HasFormat.php │ ├── HasRewrite.php │ ├── HasOperator.php │ ├── HasFuzziness.php │ ├── HasCaseInsensitive.php │ ├── HasMinimumShouldMatch.php │ ├── HasExtendedBounds.php │ ├── HasSorting.php │ ├── HasCollapse.php │ └── HasAggregations.php ├── Constants │ └── SortDirections.php ├── Options │ ├── Field.php │ ├── SourceScript.php │ ├── InlineScript.php │ ├── Collapse.php │ └── InnerHit.php └── QueryBuilder.php ├── phpstan.neon ├── tests ├── Query │ ├── ExistsQueryTest.php │ ├── TermsQueryTest.php │ ├── RankFeatureQueryTest.php │ ├── PrefixQueryTest.php │ ├── RangeQueryTest.php │ ├── TermQueryTest.php │ ├── GeoDistanceQueryTest.php │ ├── QueryStringQueryTest.php │ ├── GeoShapeQueryTest.php │ ├── MatchPhraseQueryTest.php │ ├── FunctionsQueryTest.php │ ├── MatchPhrasePrefixQueryTest.php │ ├── MultiMatchQueryTest.php │ ├── MatchQueryTest.php │ ├── FunctionScoreQueryTest.php │ ├── GeoBoundingBoxQueryTest.php │ ├── BoolQueryTest.php │ └── SimpleQueryStringQueryTest.php ├── Aggregation │ ├── MinAggregationTest.php │ ├── StatsAggregationTest.php │ ├── SumAggregationTest.php │ ├── AvgAggregationTest.php │ ├── MaxAggregationTest.php │ ├── DateHistogramAggregationTest.php │ ├── CardinalityAggregationTest.php │ └── RangesAggregationTest.php └── QueryBuilderTest.php ├── phpunit.xml.dist ├── LICENSE ├── UPGRADE-3.0.md ├── .github └── workflows │ └── php.yml ├── composer.json ├── .php_cs.dist.php └── README.md /tools/php-cs-fixer/.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | .phpunit.result.cache 4 | .editorconfig 5 | .php-cs-fixer.cache -------------------------------------------------------------------------------- /src/QueryException.php: -------------------------------------------------------------------------------- 1 | field = $field; 12 | 13 | return $this; 14 | } 15 | 16 | public function getField(): string 17 | { 18 | return $this->field; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - vendor/phpstan/phpstan-phpunit/extension.neon 3 | - vendor/phpstan/phpstan-phpunit/rules.neon 4 | - vendor/phpstan/phpstan-deprecation-rules/rules.neon 5 | 6 | parameters: 7 | 8 | parallel: 9 | processTimeout: 600.0 10 | 11 | paths: 12 | - src 13 | - tests 14 | 15 | # The level 9 is the highest level 16 | level: 9 17 | 18 | # it is impossible to map build() 19 | checkMissingIterableValueType: false 20 | 21 | -------------------------------------------------------------------------------- /src/Constants/SortDirections.php: -------------------------------------------------------------------------------- 1 | self::ASC, 21 | self::DESC => self::DESC, 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Query/ExistsQueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 17 | 'exists' => [ 18 | 'field' => 'someFieldName', 19 | ], 20 | ], $query->build()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Query/ExistsQuery.php: -------------------------------------------------------------------------------- 1 | field = $field; 15 | } 16 | 17 | public function build(): array 18 | { 19 | return [ 20 | 'exists' => [ 21 | 'field' => $this->field, 22 | ], 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Query/TermsQueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 17 | 'terms' => [ 18 | 'field' => ['value1', 'value2'], 19 | 'boost' => 1.4, 20 | ], 21 | ], $query->build()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Options/Field.php: -------------------------------------------------------------------------------- 1 | $this->value, 17 | ]; 18 | } 19 | 20 | public function getValue(): string 21 | { 22 | return $this->value; 23 | } 24 | 25 | public function setValue(string $value): void 26 | { 27 | $this->value = $value; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Features/HasBoost.php: -------------------------------------------------------------------------------- 1 | boost = $boost; 12 | 13 | return $this; 14 | } 15 | 16 | public function buildBoostTo(array &$array): self 17 | { 18 | if (null === $this->boost) { 19 | return $this; 20 | } 21 | 22 | $array['boost'] = $this->boost; 23 | 24 | return $this; 25 | } 26 | 27 | public function getBoost(): ?float 28 | { 29 | return $this->boost; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Query/RankFeatureQueryTest.php: -------------------------------------------------------------------------------- 1 | setBoost(0.9); 17 | 18 | $this->assertEquals([ 19 | 'rank_feature' => [ 20 | 'field' => 'rank', 21 | 'boost' => 0.9, 22 | ], 23 | ], $rankFeatureQuery->build()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Features/HasFormat.php: -------------------------------------------------------------------------------- 1 | format = $format; 12 | 13 | return $this; 14 | } 15 | 16 | public function buildFormatTo(array &$array): self 17 | { 18 | if (null === $this->format) { 19 | return $this; 20 | } 21 | 22 | $array['format'] = $this->format; 23 | 24 | return $this; 25 | } 26 | 27 | public function getFormat(): ?string 28 | { 29 | return $this->format; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Query/PrefixQueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 17 | 'prefix' => [ 18 | 'title' => [ 19 | 'value' => 'a brown fox', 20 | 'case_insensitive' => true, 21 | ], 22 | ], 23 | ], $query->build()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Features/HasRewrite.php: -------------------------------------------------------------------------------- 1 | rewrite = $rewrite; 12 | 13 | return $this; 14 | } 15 | 16 | public function buildRewriteTo(array &$array): self 17 | { 18 | if (null === $this->rewrite) { 19 | return $this; 20 | } 21 | 22 | $array['rewrite'] = $this->rewrite; 23 | 24 | return $this; 25 | } 26 | 27 | public function getRewrite(): ?string 28 | { 29 | return $this->rewrite; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Query/RangeQueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 17 | 'range' => [ 18 | 'points' => [ 19 | 'gt' => 10, 20 | 'lt' => 50, 21 | 'boost' => 0.8, 22 | ], 23 | ], 24 | ], $query->build()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Features/HasOperator.php: -------------------------------------------------------------------------------- 1 | operator = $operator; 12 | 13 | return $this; 14 | } 15 | 16 | public function buildOperatorTo(array &$array): self 17 | { 18 | if (null === $this->operator) { 19 | return $this; 20 | } 21 | 22 | $array['operator'] = $this->operator; 23 | 24 | return $this; 25 | } 26 | 27 | public function getOperator(): ?string 28 | { 29 | return $this->operator; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Features/HasFuzziness.php: -------------------------------------------------------------------------------- 1 | fuzziness = $fuzziness; 12 | 13 | return $this; 14 | } 15 | 16 | public function buildFuzzinessTo(array &$array): self 17 | { 18 | if (null === $this->fuzziness) { 19 | return $this; 20 | } 21 | 22 | $array['fuzziness'] = $this->fuzziness; 23 | 24 | return $this; 25 | } 26 | 27 | public function getFuzziness(): ?string 28 | { 29 | return $this->fuzziness; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Query/TermQueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 17 | 'term' => [ 18 | 'title' => [ 19 | 'value' => 'a brown fox', 20 | 'boost' => 1.1, 21 | 'case_insensitive' => true, 22 | ], 23 | ], 24 | ], $query->build()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Query/GeoDistanceQueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 17 | 'geo_distance' => [ 18 | 'distance' => '200km', 19 | 'geolocation' => [ 20 | 'lat' => 40, 21 | 'lon' => -70, 22 | ], 23 | ], 24 | ], $query->build()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Options/SourceScript.php: -------------------------------------------------------------------------------- 1 | script = $script; 16 | 17 | return $this; 18 | } 19 | 20 | public function build(): array 21 | { 22 | return [ 23 | 'script' => [ 24 | 'source' => $this->script, 25 | ], 26 | ]; 27 | } 28 | 29 | public function getScript(): string 30 | { 31 | return $this->script; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./ 6 | 7 | 8 | ./tests 9 | ./vendor 10 | 11 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Features/HasCaseInsensitive.php: -------------------------------------------------------------------------------- 1 | caseInsensitive = $caseInsensitive; 12 | 13 | return $this; 14 | } 15 | 16 | public function buildCaseInsensitiveTo(array &$array): self 17 | { 18 | if (null === $this->caseInsensitive) { 19 | return $this; 20 | } 21 | 22 | $array['case_insensitive'] = $this->caseInsensitive; 23 | 24 | return $this; 25 | } 26 | 27 | public function getCaseInsensitive(): ?bool 28 | { 29 | return $this->caseInsensitive; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Options/InlineScript.php: -------------------------------------------------------------------------------- 1 | script = $script; 16 | 17 | return $this; 18 | } 19 | 20 | public function getScript(): string 21 | { 22 | return $this->script; 23 | } 24 | 25 | public function build(): array 26 | { 27 | return [ 28 | 'script' => [ 29 | 'inline' => $this->script, 30 | 'lang' => 'painless', 31 | ], 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Query/QueryStringQueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 17 | 'query_string' => [ 18 | 'query' => 'brown fox', 19 | 'default_field' => 'test', 20 | 'default_operator' => 'AND', 21 | 'boost' => 1.1, 22 | 'minimum_should_match' => '10%', 23 | ], 24 | ], $queryStringQuery->build()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Features/HasMinimumShouldMatch.php: -------------------------------------------------------------------------------- 1 | minimumShouldMatch = $minimumShouldMatch; 12 | 13 | return $this; 14 | } 15 | 16 | public function buildMinimumShouldMatchTo(array &$array): self 17 | { 18 | if (null === $this->minimumShouldMatch) { 19 | return $this; 20 | } 21 | 22 | $array['minimum_should_match'] = $this->minimumShouldMatch; 23 | 24 | return $this; 25 | } 26 | 27 | public function getMinimumShouldMatch(): ?string 28 | { 29 | return $this->minimumShouldMatch; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Aggregation/ValueCountAggregation.php: -------------------------------------------------------------------------------- 1 | field = $field; 19 | } 20 | 21 | protected function getType(): string 22 | { 23 | return 'variable_width_histogram'; 24 | } 25 | 26 | protected function buildAggregation(): array 27 | { 28 | return [ 29 | 'field' => $this->field, 30 | 'buckets' => $this->buckets, 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Query/GeoShapeQueryTest.php: -------------------------------------------------------------------------------- 1 | setRelation('outside'); 17 | 18 | $this->assertEquals([ 19 | 'geo_shape' => [ 20 | 'coordinates' => [ 21 | 'shape' => [ 22 | 'type' => 'polygon', 23 | 'coordinates' => [[-70, 40]], 24 | ], 25 | 'relation' => 'outside', 26 | ], 27 | ], 28 | ], $query->build()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Query/FunctionsQuery.php: -------------------------------------------------------------------------------- 1 | weight = $weight; 18 | 19 | return $this; 20 | } 21 | 22 | public function build(): array 23 | { 24 | $functions = []; 25 | $functions['filter'] = [ 26 | 'term' => [ 27 | '_index' => $this->field, 28 | ], 29 | ]; 30 | 31 | if (null !== $this->weight) { 32 | $functions['weight'] = $this->weight; 33 | } 34 | 35 | return $functions; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Features/HasExtendedBounds.php: -------------------------------------------------------------------------------- 1 | min = $min; 17 | 18 | return $this; 19 | } 20 | 21 | public function getMin(): ?string 22 | { 23 | return $this->min; 24 | } 25 | 26 | public function setMax(?string $max): self 27 | { 28 | $this->max = $max; 29 | 30 | return $this; 31 | } 32 | 33 | public function getMax(): ?string 34 | { 35 | return $this->max; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Aggregation/FilterAggregation.php: -------------------------------------------------------------------------------- 1 | $aggregations 11 | */ 12 | public function __construct( 13 | string $name, 14 | private QueryInterface $query, 15 | array $aggregations = [] 16 | ) { 17 | parent::__construct($name, $aggregations); 18 | } 19 | 20 | public function getQuery(): QueryInterface 21 | { 22 | return $this->query; 23 | } 24 | 25 | public function setQuery(QueryInterface $query): void 26 | { 27 | $this->query = $query; 28 | } 29 | 30 | protected function getType(): string 31 | { 32 | return 'filter'; 33 | } 34 | 35 | protected function buildAggregation(): array 36 | { 37 | return $this->query->build(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Query/RankFeatureQuery.php: -------------------------------------------------------------------------------- 1 | field = $field; 20 | $this->boost = $boost; 21 | } 22 | 23 | public function setParams(array $params): self 24 | { 25 | $this->params = $params; 26 | 27 | return $this; 28 | } 29 | 30 | public function build(): array 31 | { 32 | $build = $this->params; 33 | $build['field'] = $this->field; 34 | 35 | $this->buildBoostTo($build); 36 | 37 | return [ 38 | 'rank_feature' => $build, 39 | ]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Aggregation/NestedAggregation.php: -------------------------------------------------------------------------------- 1 | $aggregations 9 | */ 10 | public function __construct( 11 | string $name, 12 | private ?string $path = null, 13 | array $aggregations = [], 14 | ) { 15 | parent::__construct($name, $aggregations); 16 | } 17 | 18 | public function setNestedPath(string $path): self 19 | { 20 | $this->path = $path; 21 | 22 | return $this; 23 | } 24 | 25 | public function getNestedPath(): string 26 | { 27 | return $this->path; 28 | } 29 | 30 | protected function getType(): string 31 | { 32 | return 'nested'; 33 | } 34 | 35 | protected function buildAggregation(): array 36 | { 37 | if (null === $this->path) { 38 | return []; 39 | } 40 | 41 | return [ 42 | 'path' => $this->path, 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Features/HasSorting.php: -------------------------------------------------------------------------------- 1 | >|array 11 | */ 12 | protected array $sort = []; 13 | 14 | /** 15 | * Adds sort. 16 | * 17 | * @param array|string $config Can be order direction ('desc') or config (['order' => 'asc']] 18 | * 19 | * @return $this 20 | */ 21 | public function addSort(string $field, string|array $config = SortDirections::ASC): self 22 | { 23 | $this->sort[$field] = $config; 24 | 25 | return $this; 26 | } 27 | 28 | /** 29 | * Adds sort settings to array if sorting was set. 30 | */ 31 | protected function buildSortTo(array &$toArray): self 32 | { 33 | if (false === empty($this->sort)) { 34 | foreach ($this->sort as $sort => $config) { 35 | $toArray['sort'][$sort] = $config; 36 | } 37 | } 38 | 39 | return $this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Erwan Richard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Query/NestedQuery.php: -------------------------------------------------------------------------------- 1 | path = $path; 18 | 19 | return $this; 20 | } 21 | 22 | public function setQuery(QueryInterface $query): self 23 | { 24 | $this->query = $query; 25 | 26 | return $this; 27 | } 28 | 29 | public function setParams(array $params): self 30 | { 31 | $this->params = $params; 32 | 33 | return $this; 34 | } 35 | 36 | public function build(): array 37 | { 38 | $build = $this->params; 39 | $build['nested'] = [ 40 | 'path' => $this->path, 41 | 'query' => $this->query->build(), 42 | ]; 43 | 44 | return $build; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /UPGRADE-3.0.md: -------------------------------------------------------------------------------- 1 | # Upgrade guide for 3.0 2 | 3 | 4 | - If you are type-hinting build aggregation use `AbstractAggregation` instead of `Aggregation` 5 | - If you are implementing own `Aggregation` - extend `AbstractAggregation` 6 | - If you are implementing own `Query` (filter) - implement `QueryInterface` 7 | 8 | ## Rewrite filters to Query 9 | 10 | - All files were renamed from `Filter` suffix to `Query` 11 | - namespace has been moved to `Query` 12 | - Move values from `setField` (and other "required" properties) to a constructor of the filter 13 | - Find in your IDE all usages of this text `use Erichard\ElasticQueryBuilder\Filter\` 14 | - Then find all `Filter` definitions and rewrite them to Query 15 | - Remove any lines with `use Erichard\ElasticQueryBuilder\Filter\` 16 | - Use PHPStan to find bugs after refactoring. 17 | 18 | ## New terminology 19 | 20 | - All xxxFilter classes have been renamed to xxxQuery to be more consistent with Elastic terms. 21 | - We are using strict types - ensure that values you are sending are valid. 22 | - Required properties must be defined in constructor. Optional can be in constructor too (for PHP 8.1 - named arguments) 23 | 24 | -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Validate composer.json and composer.lock 21 | run: composer validate --strict 22 | 23 | - name: Cache Composer packages 24 | id: composer-cache 25 | uses: actions/cache@v3 26 | with: 27 | path: vendor 28 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-php- 31 | 32 | - name: Install dependencies 33 | run: composer install --prefer-dist --no-progress 34 | 35 | - name: Install php-cs-fixer 36 | run: composer install --working-dir=tools/php-cs-fixer 37 | 38 | - name: Check coding standards 39 | run: composer run-script lint:fix 40 | 41 | - name: Check PHPStan 42 | run: composer run-script lint:stan 43 | 44 | - name: Run test suite 45 | run: composer run-script test 46 | -------------------------------------------------------------------------------- /src/Aggregation/TopHitsAggregation.php: -------------------------------------------------------------------------------- 1 | script = $script; 18 | 19 | return $this; 20 | } 21 | 22 | public function setSize(int $size): self 23 | { 24 | $this->size = $size; 25 | 26 | return $this; 27 | } 28 | 29 | protected function getType(): string 30 | { 31 | return 'top_hits'; 32 | } 33 | 34 | protected function buildAggregation(): array 35 | { 36 | $data = [ 37 | 'size' => $this->size, 38 | ]; 39 | 40 | if (null !== $this->script) { 41 | $data['_source'] = [ 42 | 'includes' => $this->script, 43 | ]; 44 | } 45 | 46 | $this->buildSortTo($data); 47 | 48 | return $data; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Query/MatchPhraseQueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 17 | 'match_phrase' => [ 18 | 'title' => [ 19 | 'query' => 'a brown fox', 20 | ], 21 | ], 22 | ], $query->build()); 23 | } 24 | 25 | public function testItBuildTheQueryWithAnAnalyzer(): void 26 | { 27 | $query = new MatchPhraseQuery('title', 'a brown fox'); 28 | $query->setAnalyzer('custom_analyzer'); 29 | 30 | $this->assertEquals([ 31 | 'match_phrase' => [ 32 | 'title' => [ 33 | 'query' => 'a brown fox', 34 | 'analyzer' => 'custom_analyzer', 35 | ], 36 | ], 37 | ], $query->build()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Query/FunctionsQueryTest.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'term' => [ 19 | '_index' => 'column1', 20 | ], 21 | ], 22 | ]; 23 | 24 | $this->assertEquals($response, $functionsQuery->build()); 25 | } 26 | 27 | public function testFunctionsQuerySetWeight(): void 28 | { 29 | $functionsQuery = new FunctionsQuery('column1'); 30 | $functionsQuery->setWeight(2.50); 31 | 32 | $response = [ 33 | 'filter' => [ 34 | 'term' => [ 35 | '_index' => 'column1', 36 | ], 37 | ], 38 | 'weight' => 2.50, 39 | ]; 40 | 41 | $this->assertEquals($response, $functionsQuery->build()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Query/MatchPhrasePrefixQueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 17 | 'match_phrase_prefix' => [ 18 | 'title' => [ 19 | 'query' => 'a brown fox', 20 | ], 21 | ], 22 | ], $query->build()); 23 | } 24 | 25 | public function testItBuildTheQueryWithAnAnalyzer(): void 26 | { 27 | $query = new MatchPhrasePrefixQuery('title', 'a brown fox'); 28 | $query->setAnalyzer('custom_analyzer'); 29 | 30 | $this->assertEquals([ 31 | 'match_phrase_prefix' => [ 32 | 'title' => [ 33 | 'query' => 'a brown fox', 34 | 'analyzer' => 'custom_analyzer', 35 | ], 36 | ], 37 | ], $query->build()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Query/MultiMatchQueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 17 | 'multi_match' => [ 18 | 'fields' => ['subject', 'body'], 19 | 'query' => 'a brown fox', 20 | 'type' => 'cross_fields', 21 | 'operator' => 'AND', 22 | ], 23 | ], $query->build()); 24 | } 25 | 26 | public function testItBuildTheQueryWithAFuzziness(): void 27 | { 28 | $query = new MultiMatchQuery(['subject', 'body'], 'a brown fox'); 29 | $query->setFuzziness('AUTO'); 30 | 31 | $this->assertEquals([ 32 | 'multi_match' => [ 33 | 'fields' => ['subject', 'body'], 34 | 'query' => 'a brown fox', 35 | 'fuzziness' => 'AUTO', 36 | ], 37 | ], $query->build()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Features/HasCollapse.php: -------------------------------------------------------------------------------- 1 | collapse = is_string($collapseByField) 21 | ? new Collapse($collapseByField) 22 | : $collapseByField; 23 | 24 | return $this; 25 | } 26 | 27 | public function getCollapse(): ?Collapse 28 | { 29 | return $this->collapse; 30 | } 31 | 32 | /** 33 | * Adds collapse to array if field collapsing is set. 34 | */ 35 | protected function buildCollapseTo(array &$toArray): self 36 | { 37 | if (null !== $this->collapse) { 38 | $toArray['collapse'] = $this->collapse->build(); 39 | } 40 | 41 | return $this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Query/TermsQuery.php: -------------------------------------------------------------------------------- 1 | $values 16 | */ 17 | public function __construct( 18 | string $field, 19 | protected array $values, 20 | ?float $boost = null, 21 | protected array $params = [], 22 | ) { 23 | $this->field = $field; 24 | $this->boost = $boost; 25 | } 26 | 27 | public function setValues(array $values): self 28 | { 29 | $this->values = $values; 30 | 31 | return $this; 32 | } 33 | 34 | public function setParams(array $params): self 35 | { 36 | $this->params = $params; 37 | 38 | return $this; 39 | } 40 | 41 | public function build(): array 42 | { 43 | $build = $this->params; 44 | $build[$this->field] = array_values($this->values); 45 | 46 | $this->buildBoostTo($build); 47 | 48 | return [ 49 | 'terms' => $build, 50 | ]; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Query/WildcardQuery.php: -------------------------------------------------------------------------------- 1 | field = $field; 18 | } 19 | 20 | public function setValue(string $value): self 21 | { 22 | $this->value = $value; 23 | 24 | return $this; 25 | } 26 | 27 | public static function escapeWildcards(string $string): string 28 | { 29 | $escapeChars = ['*', '?']; 30 | foreach ($escapeChars as $escapeChar) { 31 | $string = str_replace($escapeChar, '\\'.$escapeChar, $string); 32 | } 33 | 34 | return $string; 35 | } 36 | 37 | public function setParams(array $params): self 38 | { 39 | $this->params = $params; 40 | 41 | return $this; 42 | } 43 | 44 | public function build(): array 45 | { 46 | $build = $this->params; 47 | $build['wildcard'] = [ 48 | $this->field => $this->value, 49 | ]; 50 | 51 | return $build; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Features/HasAggregations.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | private array $aggregations = []; 13 | 14 | public function addAggregation(AbstractAggregation $aggregation): self 15 | { 16 | $this->aggregations[] = $aggregation; 17 | 18 | return $this; 19 | } 20 | 21 | /** 22 | * @param array $aggregations 23 | */ 24 | public function setAggregations(array $aggregations): self 25 | { 26 | $this->aggregations = $aggregations; 27 | 28 | return $this; 29 | } 30 | 31 | /** 32 | * @return array|null 33 | */ 34 | public function getAggregations(): ?array 35 | { 36 | return $this->aggregations; 37 | } 38 | 39 | protected function buildAggregationsTo(array &$toArray): self 40 | { 41 | if (0 === count($this->aggregations)) { 42 | return $this; 43 | } 44 | 45 | $aggregations = []; 46 | foreach ($this->aggregations as $aggregation) { 47 | $aggregations[$aggregation->getName()] = $aggregation->build(); 48 | } 49 | 50 | $toArray['aggs'] = $aggregations; 51 | 52 | return $this; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Aggregation/AbstractAggregation.php: -------------------------------------------------------------------------------- 1 | $aggregations 14 | */ 15 | public function __construct( 16 | private string $name, 17 | array $aggregations = [], 18 | ) { 19 | $this->aggregations = $aggregations; 20 | } 21 | 22 | /** 23 | * @return array|array|array 24 | */ 25 | public function build(): array 26 | { 27 | $build = $this->buildAggregation(); 28 | 29 | if ([] === $build) { 30 | $build = new \stdClass(); 31 | } 32 | 33 | $data = [ 34 | $this->getType() => $build, 35 | ]; 36 | 37 | $this->buildAggregationsTo($data); 38 | 39 | return $data; 40 | } 41 | 42 | public function setName(string $name): self 43 | { 44 | $this->name = $name; 45 | 46 | return $this; 47 | } 48 | 49 | public function getName(): string 50 | { 51 | return $this->name; 52 | } 53 | 54 | abstract protected function getType(): string; 55 | 56 | abstract protected function buildAggregation(): array; 57 | } 58 | -------------------------------------------------------------------------------- /src/Query/GeoDistanceQuery.php: -------------------------------------------------------------------------------- 1 | field = $field; 22 | } 23 | 24 | public function setDistance(string $distance): self 25 | { 26 | $this->distance = $distance; 27 | 28 | return $this; 29 | } 30 | 31 | public function setPosition(array $position): self 32 | { 33 | $this->position = $position; 34 | 35 | return $this; 36 | } 37 | 38 | public function setParams(array $params): self 39 | { 40 | $this->params = $params; 41 | 42 | return $this; 43 | } 44 | 45 | public function build(): array 46 | { 47 | $build = $this->params; 48 | $build['geo_distance'] = [ 49 | 'distance' => $this->distance, 50 | $this->field => [ 51 | 'lat' => $this->position[0], 52 | 'lon' => $this->position[1], 53 | ], 54 | ]; 55 | 56 | return $build; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Aggregation/CardinalityAggregation.php: -------------------------------------------------------------------------------- 1 | precisionThreshold = $precisionThreshold; 25 | 26 | return $this; 27 | } 28 | 29 | public function getPrecisionThreshold(): ?int 30 | { 31 | return $this->precisionThreshold; 32 | } 33 | 34 | protected function getType(): string 35 | { 36 | return 'cardinality'; 37 | } 38 | 39 | protected function buildAggregation(): array 40 | { 41 | $build = parent::buildAggregation(); 42 | 43 | if (null !== $this->precisionThreshold) { 44 | $build['precision_threshold'] = $this->precisionThreshold; 45 | } 46 | 47 | return $build; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Query/AbstractMatchQuery.php: -------------------------------------------------------------------------------- 1 | field = $field; 19 | } 20 | 21 | public function setQuery(string $query): self 22 | { 23 | $this->query = $query; 24 | 25 | return $this; 26 | } 27 | 28 | public function setAnalyzer(?string $analyzer): self 29 | { 30 | $this->analyzer = $analyzer; 31 | 32 | return $this; 33 | } 34 | 35 | public function setParams(array $params): self 36 | { 37 | $this->params = $params; 38 | 39 | return $this; 40 | } 41 | 42 | public function build(): array 43 | { 44 | $queryName = $this->getQueryName(); 45 | 46 | $query = $this->params; 47 | $query[$queryName] = [ 48 | $this->field => [ 49 | 'query' => $this->query, 50 | ], 51 | ]; 52 | 53 | if (null !== $this->analyzer) { 54 | $query[$queryName][$this->field]['analyzer'] = $this->analyzer; 55 | } 56 | 57 | return $query; 58 | } 59 | 60 | abstract public function getQueryName(): string; 61 | } 62 | -------------------------------------------------------------------------------- /src/Query/MatchQuery.php: -------------------------------------------------------------------------------- 1 | operator = $operator; 27 | $this->minimumShouldMatch = $minimumShouldMatch; 28 | $this->fuzziness = $fuzziness; 29 | } 30 | 31 | public function getQueryName(): string 32 | { 33 | return 'match'; 34 | } 35 | 36 | public function setParams(array $params): self 37 | { 38 | $this->params = $params; 39 | 40 | return $this; 41 | } 42 | 43 | public function build(): array 44 | { 45 | $build = parent::build(); 46 | 47 | $this->buildOperatorTo($build[$this->getQueryName()][$this->field]); 48 | $this->buildMinimumShouldMatchTo($build[$this->getQueryName()][$this->field]); 49 | $this->buildFuzzinessTo($build[$this->getQueryName()][$this->field]); 50 | 51 | return $build; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "erichard/elasticsearch-query-builder", 3 | "license": "MIT", 4 | "type": "library", 5 | "description": "Create elastic search query with a fluent interface", 6 | "authors": [ 7 | { 8 | "name": "Erwan Richard", 9 | "email": "erwan.richard@protonmail.com" 10 | } 11 | ], 12 | "autoload": { 13 | "psr-4": { 14 | "Erichard\\ElasticQueryBuilder\\": "src/" 15 | } 16 | }, 17 | "autoload-dev": { 18 | "psr-4": { 19 | "Tests\\Erichard\\ElasticQueryBuilder\\": "tests/" 20 | } 21 | }, 22 | "require": { 23 | "php": ">=8.0" 24 | }, 25 | "require-dev": { 26 | "phpstan/phpstan": "^1.4.10", 27 | "phpstan/phpstan-deprecation-rules": "^1.0.0", 28 | "phpstan/phpstan-phpunit": "^1.0.0", 29 | "phpunit/phpunit": "^9.5.19", 30 | "rector/rector": "^0.12.17", 31 | "symplify/easy-coding-standard": "^10.1" 32 | }, 33 | "scripts": { 34 | "lint:fix": "php tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --config=./.php_cs.dist.php", 35 | "lint:stan": "./vendor/bin/phpstan", 36 | "lint": "composer lint:fix && composer lint:stan", 37 | "test": "./vendor/bin/phpunit" 38 | }, 39 | "extra": { 40 | "branch-alias": { 41 | "dev-main": "3.0-dev" 42 | } 43 | }, 44 | "config": { 45 | "preferred-install": "dist", 46 | "sort-packages": true, 47 | "optimize-autoloader": true, 48 | "allow-plugins": { 49 | "symfony/thanks": false 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/Aggregation/HistogramAggregation.php: -------------------------------------------------------------------------------- 1 | $aggregations 15 | */ 16 | public function __construct( 17 | string $name, 18 | string $field, 19 | private int $interval, 20 | array $aggregations = [], 21 | ?string $min = null, 22 | ?string $max = null, 23 | ) { 24 | parent::__construct($name, $aggregations); 25 | $this->field = $field; 26 | $this->min = $min; 27 | $this->max = $max; 28 | } 29 | 30 | public function getInterval(): int 31 | { 32 | return $this->interval; 33 | } 34 | 35 | public function setInterval(int $interval): void 36 | { 37 | $this->interval = $interval; 38 | } 39 | 40 | protected function getType(): string 41 | { 42 | return 'histogram'; 43 | } 44 | 45 | protected function buildAggregation(): array 46 | { 47 | $build = [ 48 | 'field' => $this->field, 49 | 'interval' => $this->interval, 50 | ]; 51 | 52 | if (null !== $this->min && null !== $this->max) { 53 | $build['extended_bounds']['min'] = $this->min; 54 | $build['extended_bounds']['max'] = $this->max; 55 | } 56 | 57 | return $build; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Query/PrefixQuery.php: -------------------------------------------------------------------------------- 1 | field = $field; 27 | $this->rewrite = $rewrite; 28 | $this->caseInsensitive = $caseInsensitive; 29 | } 30 | 31 | public function setValue(string $value): self 32 | { 33 | $this->value = $value; 34 | 35 | return $this; 36 | } 37 | 38 | public function setParams(array $params): self 39 | { 40 | $this->params = $params; 41 | 42 | return $this; 43 | } 44 | 45 | public function build(): array 46 | { 47 | $build = $this->params; 48 | $build['value'] = $this->value; 49 | 50 | $this->buildRewriteTo($build); 51 | $this->buildCaseInsensitiveTo($build); 52 | 53 | return [ 54 | 'prefix' => [ 55 | $this->field => $build, 56 | ], 57 | ]; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Query/TermQuery.php: -------------------------------------------------------------------------------- 1 | field = $field; 27 | $this->boost = $boost; 28 | $this->caseInsensitive = $caseInsensitive; 29 | } 30 | 31 | public function setValue(string|int|float|bool $value): self 32 | { 33 | $this->value = $value; 34 | 35 | return $this; 36 | } 37 | 38 | public function setParams(array $params): self 39 | { 40 | $this->params = $params; 41 | 42 | return $this; 43 | } 44 | 45 | public function build(): array 46 | { 47 | $build = $this->params; 48 | $build[$this->field] = [ 49 | 'value' => $this->value, 50 | ]; 51 | 52 | $this->buildBoostTo($build[$this->field]); 53 | $this->buildCaseInsensitiveTo($build[$this->field]); 54 | 55 | return [ 56 | 'term' => $build, 57 | ]; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Aggregation/MinAggregationTest.php: -------------------------------------------------------------------------------- 1 | setField('price'); 16 | 17 | $this->assertEquals([ 18 | 'min' => [ 19 | 'field' => 'price', 20 | ], 21 | ], $query->build()); 22 | } 23 | 24 | public function testItBuildTheAggregationUsingAScript(): void 25 | { 26 | $query = new MinAggregation('min_price'); 27 | $query->setScript('doc.price.value'); 28 | 29 | $this->assertEquals([ 30 | 'min' => [ 31 | 'script' => [ 32 | 'source' => 'doc.price.value', 33 | ], 34 | ], 35 | ], $query->build()); 36 | } 37 | 38 | public function testWithFieldName(): void 39 | { 40 | $query = new MinAggregation('min_price'); 41 | 42 | $this->assertEquals([ 43 | 'min' => [ 44 | 'field' => 'min_price', 45 | ], 46 | ], $query->build()); 47 | } 48 | 49 | public function testItBuildTheAggregationWithMissingValue(): void 50 | { 51 | $query = new MinAggregation('min_price', 'price'); 52 | $query->setMissing(10); 53 | 54 | $this->assertEquals([ 55 | 'min' => [ 56 | 'field' => 'price', 57 | 'missing' => 10, 58 | ], 59 | ], $query->build()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Aggregation/StatsAggregationTest.php: -------------------------------------------------------------------------------- 1 | setField('price'); 16 | 17 | $this->assertEquals([ 18 | 'stats' => [ 19 | 'field' => 'price', 20 | ], 21 | ], $query->build()); 22 | } 23 | 24 | public function testItBuildTheAggregationUsingAScript(): void 25 | { 26 | $query = new StatsAggregation('price'); 27 | $query->setScript('doc.price.value'); 28 | 29 | $this->assertEquals([ 30 | 'stats' => [ 31 | 'script' => [ 32 | 'source' => 'doc.price.value', 33 | ], 34 | ], 35 | ], $query->build()); 36 | } 37 | 38 | public function testWithFieldName(): void 39 | { 40 | $query = new StatsAggregation('price'); 41 | 42 | $this->assertEquals([ 43 | 'stats' => [ 44 | 'field' => 'price', 45 | ], 46 | ], $query->build()); 47 | } 48 | 49 | public function testItBuildTheAggregationWithMissingValue(): void 50 | { 51 | $query = new StatsAggregation('price', 'price'); 52 | $query->setMissing(10); 53 | 54 | $this->assertEquals([ 55 | 'stats' => [ 56 | 'field' => 'price', 57 | 'missing' => 10, 58 | ], 59 | ], $query->build()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Aggregation/SumAggregationTest.php: -------------------------------------------------------------------------------- 1 | setField('price'); 16 | 17 | $this->assertEquals([ 18 | 'sum' => [ 19 | 'field' => 'price', 20 | ], 21 | ], $query->build()); 22 | } 23 | 24 | public function testItBuildTheAggregationUsingAScript(): void 25 | { 26 | $query = new SumAggregation('sum_price'); 27 | $query->setScript('doc.price.value'); 28 | 29 | $this->assertEquals([ 30 | 'sum' => [ 31 | 'script' => [ 32 | 'source' => 'doc.price.value', 33 | ], 34 | ], 35 | ], $query->build()); 36 | } 37 | 38 | public function testWithFieldName(): void 39 | { 40 | $query = new SumAggregation('sum_price'); 41 | 42 | $this->assertEquals([ 43 | 'sum' => [ 44 | 'field' => 'sum_price', 45 | ], 46 | ], $query->build()); 47 | } 48 | 49 | public function testItBuildTheAggregationWithMissingValue(): void 50 | { 51 | $query = new SumAggregation('sum_price', 'price'); 52 | $query->setMissing(10); 53 | 54 | $this->assertEquals([ 55 | 'sum' => [ 56 | 'field' => 'price', 57 | 'missing' => 10, 58 | ], 59 | ], $query->build()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Query/GeoShapeQuery.php: -------------------------------------------------------------------------------- 1 | type = $type; 24 | 25 | return $this; 26 | } 27 | 28 | public function setCoordinates(array $coordinates): self 29 | { 30 | $this->coordinates = $coordinates; 31 | 32 | return $this; 33 | } 34 | 35 | public function setField(string $field): self 36 | { 37 | $this->field = $field; 38 | 39 | return $this; 40 | } 41 | 42 | public function setRelation(string $relation): self 43 | { 44 | $this->relation = $relation; 45 | 46 | return $this; 47 | } 48 | 49 | public function setParams(array $params): self 50 | { 51 | $this->params = $params; 52 | 53 | return $this; 54 | } 55 | 56 | public function build(): array 57 | { 58 | $build = $this->params; 59 | $build['geo_shape'] = [ 60 | $this->field => [ 61 | 'shape' => [ 62 | 'type' => $this->type, 63 | 'coordinates' => $this->coordinates, 64 | ], 65 | 'relation' => $this->relation, 66 | ], 67 | ]; 68 | 69 | return $build; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Query/FunctionScoreQuery.php: -------------------------------------------------------------------------------- 1 | boost = $boost; 26 | 27 | return $this; 28 | } 29 | 30 | public function setBoostMode(string $boostMode): self 31 | { 32 | $this->boostMode = $boostMode; 33 | 34 | return $this; 35 | } 36 | 37 | public function setFunctions(array $functions): self 38 | { 39 | $this->functions = $functions; 40 | 41 | return $this; 42 | } 43 | 44 | public function build(): array 45 | { 46 | $build = []; 47 | if (null !== $this->boostMode) { 48 | $build['boost_mode'] = $this->boostMode; 49 | } 50 | 51 | if (null !== $this->functions) { 52 | $build['functions'] = $this->functions; 53 | } 54 | 55 | $build['query'] = [ 56 | 'query_string' => [ 57 | 'query' => $this->query, 58 | 'fields' => $this->fields, 59 | ], 60 | ]; 61 | 62 | if (null !== $this->boost) { 63 | $build['query']['query_string'] = $this->boost; 64 | } 65 | 66 | return [ 67 | 'function_score' => $build, 68 | ]; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Options/Collapse.php: -------------------------------------------------------------------------------- 1 | 17 | * 18 | * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-inner-hits 19 | */ 20 | protected array $innerHits = []; 21 | 22 | /** 23 | * The number of concurrent requests allowed to retrieve the inner_hits` per group. 24 | */ 25 | protected ?int $maxConcurrentSearchers = null; 26 | 27 | public function __construct(string $field) 28 | { 29 | $this->field = $field; 30 | } 31 | 32 | public function addInnerHit(InnerHit $hit): self 33 | { 34 | $this->innerHits[] = $hit; 35 | 36 | return $this; 37 | } 38 | 39 | public function build(): array 40 | { 41 | $result = [ 42 | 'field' => $this->field, 43 | ]; 44 | 45 | if (null !== $this->maxConcurrentSearchers) { 46 | $result['max_concurrent_group_searches'] = $this->maxConcurrentSearchers; 47 | } 48 | 49 | if (false === empty($this->innerHits)) { 50 | $result['inner_hits'] = array_map(fn (InnerHit $hit) => $hit->build(), $this->innerHits); 51 | } 52 | 53 | return $result; 54 | } 55 | 56 | public function setMaxConcurrentSearchers(int $maxConcurrentSearchers): self 57 | { 58 | $this->maxConcurrentSearchers = $maxConcurrentSearchers; 59 | 60 | return $this; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Query/GeoBoundingBoxQuery.php: -------------------------------------------------------------------------------- 1 | field = $field; 24 | } 25 | 26 | public function build(): array 27 | { 28 | $filters = array_filter([ 29 | 'top_left' => $this->pointToArray($this->topLeft), 30 | 'bottom_right' => $this->pointToArray($this->bottomRight), 31 | 'top_right' => $this->pointToArray($this->topRight), 32 | 'bottom_left' => $this->pointToArray($this->bottomLeft), 33 | ]); 34 | 35 | if (count($filters) < 2) { 36 | throw new \Exception('GeoBoundingBoxQuery needs at least 2 sides set'); 37 | } 38 | 39 | return [ 40 | 'geo_bounding_box' => [ 41 | $this->field => $filters, 42 | ], 43 | ]; 44 | } 45 | 46 | protected function pointToArray(?GpsPointEntity $entity): ?array 47 | { 48 | if (null === $entity) { 49 | return null; 50 | } 51 | 52 | return [ 53 | 'lat' => $entity->lat, 54 | 'lon' => $entity->lon, 55 | ]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Options/InnerHit.php: -------------------------------------------------------------------------------- 1 | $this->from, 34 | 'size' => $this->size, 35 | 'name' => $this->name, 36 | ]); 37 | $this->buildSortTo($array); 38 | $this->buildCollapseTo($array); 39 | 40 | return $array; 41 | } 42 | 43 | public function getFrom(): ?string 44 | { 45 | return $this->from; 46 | } 47 | 48 | public function setFrom(?string $from): void 49 | { 50 | $this->from = $from; 51 | } 52 | 53 | public function getName(): string 54 | { 55 | return $this->name; 56 | } 57 | 58 | public function setName(string $name): self 59 | { 60 | $this->name = $name; 61 | 62 | return $this; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.php_cs.dist.php: -------------------------------------------------------------------------------- 1 | in(__DIR__ . '/src') 7 | ->exclude(__DIR__ . '/tests') 8 | ; 9 | 10 | return (new PhpCsFixer\Config()) 11 | ->registerCustomFixers(new PhpCsFixerCustomFixers\Fixers()) 12 | ->setRiskyAllowed(true) 13 | ->setRules([ 14 | '@PSR12' => true, 15 | '@PSR12:risky' => true, 16 | '@Symfony' => true, 17 | '@Symfony:risky' => true, 18 | 'linebreak_after_opening_tag' => true, 19 | 'mb_str_functions' => true, 20 | 'no_php4_constructor' => true, 21 | 'no_unreachable_default_argument_value' => true, 22 | 'no_useless_else' => true, 23 | 'no_useless_return' => true, 24 | 'php_unit_strict' => true, 25 | 'phpdoc_order' => true, 26 | 'strict_comparison' => true, 27 | 'strict_param' => true, 28 | 'concat_space' => false, 29 | 'declare_strict_types' => true, 30 | 'native_function_invocation' => ['include' => []], 31 | 'nullable_type_declaration_for_default_null_value' => ['use_nullable_type_declaration' => true], 32 | PhpCsFixerCustomFixers\Fixer\DeclareAfterOpeningTagFixer::name() => true, 33 | PhpCsFixerCustomFixers\Fixer\NoDoctrineMigrationsGeneratedCommentFixer::name() => true, 34 | PhpCsFixerCustomFixers\Fixer\NoImportFromGlobalNamespaceFixer::name() => true, 35 | //PhpCsFixerCustomFixers\Fixer\PromotedConstructorPropertyFixer::name() => true, 36 | PhpCsFixerCustomFixers\Fixer\ConstructorEmptyBracesFixer::name() => true, 37 | PhpCsFixerCustomFixers\Fixer\MultilinePromotedPropertiesFixer::name() => true, 38 | PhpCsFixerCustomFixers\Fixer\NoUselessDoctrineRepositoryCommentFixer::name() => true, 39 | ]) 40 | ->setFinder($finder) 41 | ; 42 | -------------------------------------------------------------------------------- /tests/Query/MatchQueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 17 | 'match' => [ 18 | 'title' => [ 19 | 'query' => 'a brown fox', 20 | 'operator' => 'AND', 21 | 'minimum_should_match' => '20%', 22 | ], 23 | ], 24 | ], $query->build()); 25 | } 26 | 27 | public function testItBuildTheQueryWithAnAnalyzer(): void 28 | { 29 | $query = new MatchQuery('title', 'a brown fox'); 30 | $query->setAnalyzer('custom_analyzer'); 31 | 32 | $this->assertEquals([ 33 | 'match' => [ 34 | 'title' => [ 35 | 'query' => 'a brown fox', 36 | 'analyzer' => 'custom_analyzer', 37 | ], 38 | ], 39 | ], $query->build()); 40 | } 41 | 42 | public function testItBuildTheQueryWithBoolean(): void 43 | { 44 | $query = new MatchQuery('is_closed', true); 45 | $this->assertSame([ 46 | 'match' => [ 47 | 'is_closed' => [ 48 | 'query' => true 49 | ], 50 | ], 51 | ], $query->build()); 52 | } 53 | 54 | 55 | public function testItBuildTheQueryWithInteger(): void 56 | { 57 | $query = new MatchQuery('count', 1); 58 | 59 | $this->assertSame([ 60 | 'match' => [ 61 | 'count' => [ 62 | 'query' => 1 63 | ], 64 | ], 65 | ], $query->build()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Aggregation/DateHistogramAggregation.php: -------------------------------------------------------------------------------- 1 | $aggregations 18 | */ 19 | public function __construct( 20 | string $nameAndField, 21 | private string $calendarInterval, 22 | ?string $field = null, 23 | array $aggregations = [], 24 | ?string $min = null, 25 | ?string $max = null, 26 | ) { 27 | parent::__construct($nameAndField, $aggregations); 28 | $this->field = $field ?? $nameAndField; 29 | $this->min = $min; 30 | $this->max = $max; 31 | } 32 | 33 | public function setCalendarInterval(string $calendarInterval): self 34 | { 35 | $this->calendarInterval = $calendarInterval; 36 | 37 | return $this; 38 | } 39 | 40 | public function getCalendarInterval(): string 41 | { 42 | return $this->calendarInterval; 43 | } 44 | 45 | protected function getType(): string 46 | { 47 | return 'date_histogram'; 48 | } 49 | 50 | protected function buildAggregation(): array 51 | { 52 | $build = [ 53 | 'field' => $this->field, 54 | 'calendar_interval' => $this->calendarInterval, 55 | ]; 56 | 57 | if (null !== $this->min && null !== $this->max) { 58 | $build['extended_bounds']['min'] = $this->min; 59 | $build['extended_bounds']['max'] = $this->max; 60 | } 61 | 62 | return $build; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ElasticSearch Query Builder 2 | =========================== 3 | 4 | ![img](https://img.shields.io/badge/phpstan-8-green) 5 | ![php](https://img.shields.io/badge/php-8.0-brightgreen) 6 | 7 | 8 | This is a PHP library which helps you build query for an ElasticSearch client by using a fluent interface. 9 | 10 | 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. 11 | 12 | Installation 13 | ------------ 14 | 15 | ```bash 16 | composer require erichard/elasticsearch-query-builder "^3.0@beta" 17 | ``` 18 | 19 | Usage 20 | ----- 21 | 22 | ```php 23 | 24 | use Erichard\ElasticQueryBuilder\QueryBuilder; 25 | use Erichard\ElasticQueryBuilder\Aggregation\Aggregation; 26 | use Erichard\ElasticQueryBuilder\Filter\Filter; 27 | 28 | $qb = new QueryBuilder(); 29 | 30 | $qb 31 | ->setIndex('app') 32 | ->setSize(10) 33 | ; 34 | 35 | // Add an aggregation 36 | $qb->addAggregation(Aggregation::terms('agg_name', 'my_field')); 37 | $qb->addAggregation(Aggregation::terms('agg_name_same_as_field')); 38 | 39 | // Set query 40 | $qb->setQuery(Query::terms('field', 'value')); 41 | 42 | // I am using a client from elasticsearch/elasticsearch here 43 | $results = $client->search($qb->build()); 44 | ``` 45 | 46 | with PHP 8.1 you can use named arguments like this: 47 | 48 | ```php 49 | $query = new BoolQuery(must: [ 50 | new RangeQuery( 51 | field: 'price', 52 | gte: 100 53 | ), 54 | new RangeQuery( 55 | field: 'stock', 56 | gte: 10 57 | ), 58 | ]); 59 | ``` 60 | 61 | or with the factory 62 | 63 | ```php 64 | $query = Query::bool(must: [ 65 | Query::range( 66 | field: 'price', 67 | gte: 100 68 | ), 69 | Query::range( 70 | field: 'stock', 71 | gte: 10 72 | ), 73 | ]); 74 | ``` 75 | 76 | Contribution 77 | ------------ 78 | 79 | - Use PHPCS fixer and PHPStan 80 | - `composer lint` 81 | - Update tests (PHPUnit) 82 | - `composer test` 83 | 84 | -------------------------------------------------------------------------------- /tests/Aggregation/AvgAggregationTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 18 | 'avg' => [ 19 | 'field' => 'avg_price', 20 | ], 21 | ], $query->build()); 22 | } 23 | 24 | public function testItBuildTheAggregationUsingAField(): void 25 | { 26 | $query = new AvgAggregation('avg_price'); 27 | $query->setField('price'); 28 | 29 | $this->assertEquals([ 30 | 'avg' => [ 31 | 'field' => 'price', 32 | ], 33 | ], $query->build()); 34 | } 35 | 36 | public function testItBuildTheAggregationUsingAScript(): void 37 | { 38 | $query = new AvgAggregation('avg_price', new SourceScript('doc.price.value')); 39 | 40 | $this->assertEquals([ 41 | 'avg' => [ 42 | 'script' => [ 43 | 'source' => 'doc.price.value', 44 | ], 45 | ], 46 | ], $query->build()); 47 | } 48 | 49 | public function testItBuildTheAggregationUsingAScriptViaSet(): void 50 | { 51 | $query = new AvgAggregation('avg_price'); 52 | $query->setScript('doc.price.value'); 53 | 54 | $this->assertEquals([ 55 | 'avg' => [ 56 | 'script' => [ 57 | 'source' => 'doc.price.value', 58 | ], 59 | ], 60 | ], $query->build()); 61 | } 62 | 63 | public function testItBuildTheAggregationWithMissingValue(): void 64 | { 65 | $query = new AvgAggregation('avg_price'); 66 | $query->setField('price'); 67 | $query->setMissing(10); 68 | 69 | $this->assertEquals([ 70 | 'avg' => [ 71 | 'field' => 'price', 72 | 'missing' => 10, 73 | ], 74 | ], $query->build()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/Aggregation/MaxAggregationTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 18 | 'max' => [ 19 | 'field' => 'max_price', 20 | ], 21 | ], $query->build()); 22 | } 23 | 24 | public function testItBuildTheAggregationUsingAField(): void 25 | { 26 | $query = new MaxAggregation('max_price'); 27 | $query->setField('price'); 28 | 29 | $this->assertEquals([ 30 | 'max' => [ 31 | 'field' => 'price', 32 | ], 33 | ], $query->build()); 34 | } 35 | 36 | public function testItBuildTheAggregationUsingAScript(): void 37 | { 38 | $query = new MaxAggregation('max_price', new SourceScript('doc.price.value')); 39 | 40 | $this->assertEquals([ 41 | 'max' => [ 42 | 'script' => [ 43 | 'source' => 'doc.price.value', 44 | ], 45 | ], 46 | ], $query->build()); 47 | } 48 | 49 | public function testItBuildTheAggregationUsingAScriptViaSet(): void 50 | { 51 | $query = new MaxAggregation('max_price'); 52 | $query->setScript('doc.price.value'); 53 | 54 | $this->assertEquals([ 55 | 'max' => [ 56 | 'script' => [ 57 | 'source' => 'doc.price.value', 58 | ], 59 | ], 60 | ], $query->build()); 61 | } 62 | 63 | public function testItBuildTheAggregationWithMissingValue(): void 64 | { 65 | $query = new MaxAggregation('max_price'); 66 | $query->setField('price'); 67 | $query->setMissing(10); 68 | 69 | $this->assertEquals([ 70 | 'max' => [ 71 | 'field' => 'price', 72 | 'missing' => 10, 73 | ], 74 | ], $query->build()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/Query/FunctionScoreQueryTest.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'query' => [ 21 | 'query_string' => [ 22 | 'query' => '(*name*) OR name', 23 | 'fields' => ['column1', 'column2'], 24 | ], 25 | ], 26 | ], 27 | ]; 28 | 29 | $this->assertEquals($response, $functionScoreQuery->build()); 30 | } 31 | 32 | public function testBuildFunctionScoreQuerySetParams(): void 33 | { 34 | $fields = ['column1', 'column2']; 35 | $query = '(*name*) OR name'; 36 | 37 | $functionScoreQuery = new FunctionScoreQuery($fields, $query); 38 | $functionScoreQuery->setBoostMode('multiply'); 39 | $functionScoreQuery->setFunctions($this->functions()); 40 | 41 | $response = [ 42 | 'function_score' => [ 43 | 'boost_mode' => 'multiply', 44 | 'functions' => [ 45 | 'filter' => [ 46 | 'term' => [ 47 | '_index' => 'column2', 48 | ], 49 | ], 50 | 'weight' => 2.50, 51 | ], 52 | 'query' => [ 53 | 'query_string' => [ 54 | 'query' => '(*name*) OR name', 55 | 'fields' => ['column1', 'column2'], 56 | ], 57 | ], 58 | ], 59 | ]; 60 | 61 | $this->assertEquals($response, $functionScoreQuery->build()); 62 | } 63 | 64 | private function functions(): array 65 | { 66 | $functions = new FunctionsQuery('column2'); 67 | $functions->setWeight(2.50); 68 | 69 | return $functions->build(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/Aggregation/DateHistogramAggregationTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 17 | 'date_histogram' => [ 18 | 'field' => 'price_evolution', 19 | 'calendar_interval' => '1d', 20 | ], 21 | ], $aggregation->build()); 22 | } 23 | 24 | public function testWithDifferentName(): void 25 | { 26 | $aggregation = new DateHistogramAggregation('price_evolution', '1d', 'price'); 27 | 28 | $this->assertEquals([ 29 | 'date_histogram' => [ 30 | 'field' => 'price', 31 | 'calendar_interval' => '1d', 32 | ], 33 | ], $aggregation->build()); 34 | } 35 | 36 | public function testItBuildTheAggregationWithSet(): void 37 | { 38 | $aggregation = new DateHistogramAggregation('price_evolution', '1d', 'price'); 39 | 40 | $aggregation->setField('price2'); 41 | $aggregation->setCalendarInterval('2d'); 42 | 43 | $this->assertEquals([ 44 | 'date_histogram' => [ 45 | 'field' => 'price2', 46 | 'calendar_interval' => '2d', 47 | ], 48 | ], $aggregation->build()); 49 | } 50 | 51 | public function testWithExtendedBounds(): void 52 | { 53 | $nameAndField = 'per_day'; 54 | $calendarInterval = 'day'; 55 | $field = 'date'; 56 | $min = '2022-01-10'; 57 | $max = '2022-01-20'; 58 | 59 | $aggregation = new DateHistogramAggregation($nameAndField, $calendarInterval, $field, [], $min, $max); 60 | 61 | $this->assertEquals([ 62 | 'date_histogram' => [ 63 | 'field' => $field, 64 | 'calendar_interval' => $calendarInterval, 65 | 'extended_bounds' => [ 66 | 'min' => $min, 67 | 'max' => $max, 68 | ], 69 | ], 70 | ], $aggregation->build()); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Query/MultiMatchQuery.php: -------------------------------------------------------------------------------- 1 | operator = $operator; 32 | $this->boost = $boost; 33 | $this->minimumShouldMatch = $minimumShouldMatch; 34 | $this->fuzziness = $fuzziness; 35 | } 36 | 37 | public function setFields(array $fields): self 38 | { 39 | $this->fields = $fields; 40 | 41 | return $this; 42 | } 43 | 44 | public function setQuery(string $query): self 45 | { 46 | $this->query = $query; 47 | 48 | return $this; 49 | } 50 | 51 | public function setType(string $type): self 52 | { 53 | $this->type = $type; 54 | 55 | return $this; 56 | } 57 | 58 | public function setParams(array $params): self 59 | { 60 | $this->params = $params; 61 | 62 | return $this; 63 | } 64 | 65 | public function build(): array 66 | { 67 | $data = [ 68 | 'query' => $this->query, 69 | 'fields' => $this->fields, 70 | ]; 71 | 72 | if (null !== $this->type) { 73 | $data['type'] = $this->type; 74 | } 75 | 76 | $this->buildOperatorTo($data); 77 | $this->buildBoostTo($data); 78 | $this->buildMinimumShouldMatchTo($data); 79 | $this->buildFuzzinessTo($data); 80 | 81 | $build = $this->params; 82 | $build['multi_match'] = $data; 83 | 84 | return $build; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/Query/GeoBoundingBoxQueryTest.php: -------------------------------------------------------------------------------- 1 | expectExceptionMessage('GeoBoundingBoxQuery needs at least 2 sides set'); 16 | (new GeoBoundingBoxQuery('test'))->build(); 17 | } 18 | 19 | public function testBuildFailsOnOneFilter(): void 20 | { 21 | $this->expectExceptionMessage('GeoBoundingBoxQuery needs at least 2 sides set'); 22 | (new GeoBoundingBoxQuery(field: 'test', topLeft: new GpsPointEntity(1.1, 2.1)))->build(); 23 | } 24 | 25 | public function testBuildTopLeftBottomRight(): void 26 | { 27 | $result = (new GeoBoundingBoxQuery( 28 | field: 'test', 29 | topLeft: new GpsPointEntity(1.1, 2.1), 30 | bottomRight: new GpsPointEntity(2.1, 3.1), 31 | ))->build(); 32 | 33 | $this->assertEquals([ 34 | 'geo_bounding_box' => [ 35 | 'test' => [ 36 | 'top_left' => [ 37 | 'lat' => 1.1, 38 | 'lon' => 2.1, 39 | ], 40 | 'bottom_right' => [ 41 | 'lat' => 2.1, 42 | 'lon' => 3.1, 43 | ], 44 | ], 45 | ], 46 | ], $result); 47 | } 48 | 49 | public function testBuildTopRightBottomLeft(): void 50 | { 51 | $result = (new GeoBoundingBoxQuery( 52 | field: 'test', 53 | topRight: new GpsPointEntity(1.1, 2.1), 54 | bottomLeft: new GpsPointEntity(2.1, 3.1), 55 | ))->build(); 56 | 57 | $this->assertEquals([ 58 | 'geo_bounding_box' => [ 59 | 'test' => [ 60 | 'top_right' => [ 61 | 'lat' => 1.1, 62 | 'lon' => 2.1, 63 | ], 64 | 'bottom_left' => [ 65 | 'lat' => 2.1, 66 | 'lon' => 3.1, 67 | ], 68 | ], 69 | ], 70 | ], $result); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Aggregation/RangesAggregation.php: -------------------------------------------------------------------------------- 1 | $ranges pass desired ranges that will be converted to 15 | * linear range 16 | * @param array $aggregations 17 | * @param bool $equalConditionOnToRange Set to true if you want to do a histogram with 0 18 | * - 10, 10 - 15, and correctly count the number 19 | * (entry with 10 will be in first and seconds 20 | * segment 21 | */ 22 | public function __construct( 23 | string $name, 24 | string $field, 25 | private array $ranges, 26 | array $aggregations = [], 27 | private bool $equalConditionOnToRange = false 28 | ) { 29 | parent::__construct($name, $aggregations); 30 | $this->field = $field; 31 | } 32 | 33 | public function getRanges(): array 34 | { 35 | return $this->ranges; 36 | } 37 | 38 | public function setRanges(array $ranges): self 39 | { 40 | $this->ranges = $ranges; 41 | 42 | return $this; 43 | } 44 | 45 | protected function getType(): string 46 | { 47 | return 'range'; 48 | } 49 | 50 | protected function buildAggregation(): array 51 | { 52 | $ranges = []; 53 | $prevValue = 0; 54 | foreach ($this->ranges as $range) { 55 | $to = $range + ($this->equalConditionOnToRange ? 1 : 0); // To value is not included - increase it by 1 56 | $ranges[] = [ 57 | 'from' => $prevValue, 58 | 'to' => $to, 59 | 'key' => $range, 60 | ]; 61 | $prevValue = $range; // Do not use increased value 62 | } 63 | 64 | // Append "others" 65 | $ranges[] = [ 66 | 'key' => $prevValue.'-*', 67 | 'from' => $prevValue, 68 | ]; 69 | 70 | return [ 71 | 'field' => $this->field, 72 | 'ranges' => $ranges, 73 | ]; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Aggregation/MetricAggregation.php: -------------------------------------------------------------------------------- 1 | $aggregations 19 | */ 20 | public function __construct( 21 | string $nameAndField, 22 | string|SourceScript|Field|null $fieldOrSource = null, 23 | array $aggregations = [] 24 | ) { 25 | parent::__construct($nameAndField, $aggregations); 26 | 27 | if (null === $fieldOrSource) { 28 | $fieldOrSource = $nameAndField; 29 | } 30 | 31 | if (is_string($fieldOrSource)) { 32 | $this->field = new Field($fieldOrSource); 33 | } elseif ($fieldOrSource instanceof Field) { 34 | $this->field = $fieldOrSource; 35 | } elseif ($fieldOrSource instanceof SourceScript) { 36 | $this->script = $fieldOrSource; 37 | } else { 38 | throw new QueryException('Invalid field or source argument in metric aggregation'); 39 | } 40 | } 41 | 42 | public function setField(string|Field $field): self 43 | { 44 | $this->script = null; 45 | $this->field = is_string($field) ? new Field($field) : $field; 46 | 47 | return $this; 48 | } 49 | 50 | public function setScript(string|SourceScript $script): self 51 | { 52 | $this->field = null; 53 | $this->script = is_string($script) ? new SourceScript($script) : $script; 54 | 55 | return $this; 56 | } 57 | 58 | public function getField(): ?Field 59 | { 60 | return $this->field; 61 | } 62 | 63 | public function setMissing(?int $missing): self 64 | { 65 | $this->missing = $missing; 66 | 67 | return $this; 68 | } 69 | 70 | protected function buildAggregation(): array 71 | { 72 | $term = []; 73 | if (null !== $this->script) { 74 | $term = $this->script->build(); 75 | } elseif (null !== $this->field) { 76 | $term = $this->field->build(); 77 | } 78 | 79 | if (null !== $this->missing) { 80 | $term['missing'] = $this->missing; 81 | } 82 | 83 | return $term; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Query/RangeQuery.php: -------------------------------------------------------------------------------- 1 | field = $field; 28 | $this->format = $format; 29 | $this->boost = $boost; 30 | } 31 | 32 | public function gt(int|float|string|null $value): self 33 | { 34 | $this->gt = $value; 35 | 36 | return $this; 37 | } 38 | 39 | public function lt(int|float|string|null $value): self 40 | { 41 | $this->lt = $value; 42 | 43 | return $this; 44 | } 45 | 46 | public function gte(int|float|string|null $value): self 47 | { 48 | $this->gte = $value; 49 | 50 | return $this; 51 | } 52 | 53 | public function lte(int|float|string|null $value): self 54 | { 55 | $this->lte = $value; 56 | 57 | return $this; 58 | } 59 | 60 | public function setParams(array $params): self 61 | { 62 | $this->params = $params; 63 | 64 | return $this; 65 | } 66 | 67 | public function build(): array 68 | { 69 | $query = $this->params; 70 | 71 | if (null !== $this->gt) { 72 | $query['gt'] = $this->gt; 73 | } 74 | 75 | if (null !== $this->lt) { 76 | $query['lt'] = $this->lt; 77 | } 78 | 79 | if (null !== $this->gte) { 80 | $query['gte'] = $this->gte; 81 | } 82 | 83 | if (null !== $this->lte) { 84 | $query['lte'] = $this->lte; 85 | } 86 | 87 | if (empty($query)) { 88 | throw new QueryException('Empty RangeQuery'); 89 | } 90 | 91 | $this->buildFormatTo($query); 92 | $this->buildBoostTo($query); 93 | 94 | return [ 95 | 'range' => [ 96 | $this->field => $query, 97 | ], 98 | ]; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Query/QueryStringQuery.php: -------------------------------------------------------------------------------- 1 | boost = $boost; 30 | $this->minimumShouldMatch = $minimumShouldMatch; 31 | $this->rewrite = $rewrite; 32 | $this->fuzziness = $fuzziness; 33 | } 34 | 35 | public function setQuery(string $query): self 36 | { 37 | $this->query = $query; 38 | 39 | return $this; 40 | } 41 | 42 | public function setDefaultField(?string $defaultField): self 43 | { 44 | $this->defaultField = $defaultField; 45 | 46 | return $this; 47 | } 48 | 49 | public function setDefaultOperator(?string $defaultOperator): self 50 | { 51 | $this->defaultOperator = $defaultOperator; 52 | 53 | return $this; 54 | } 55 | 56 | public function setFields(?array $fields): self 57 | { 58 | $this->fields = $fields; 59 | 60 | return $this; 61 | } 62 | 63 | public function setParams(array $params): self 64 | { 65 | $this->params = $params; 66 | 67 | return $this; 68 | } 69 | 70 | public function build(): array 71 | { 72 | $build = $this->params; 73 | $build['query'] = $this->query; 74 | 75 | if (null !== $this->defaultField) { 76 | $build['default_field'] = $this->defaultField; 77 | } 78 | 79 | if (null !== $this->defaultOperator) { 80 | $build['default_operator'] = $this->defaultOperator; 81 | } 82 | 83 | if (null !== $this->fields) { 84 | $build['fields'] = $this->fields; 85 | } 86 | 87 | $this->buildBoostTo($build); 88 | $this->buildMinimumShouldMatchTo($build); 89 | $this->buildRewriteTo($build); 90 | $this->buildFuzzinessTo($build); 91 | 92 | return [ 93 | 'query_string' => $build, 94 | ]; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/QueryBuilderTest.php: -------------------------------------------------------------------------------- 1 | setSource(false); 17 | 18 | $query = $queryBuilder->build(); 19 | 20 | $this->assertFalse($query['_source']); 21 | } 22 | 23 | public function testItSetTheSource(): void 24 | { 25 | $queryBuilder = new QueryBuilder(); 26 | 27 | $queryBuilder->setSource('obj.*'); 28 | 29 | $query = $queryBuilder->build(); 30 | 31 | $this->assertEquals('obj.*', $query['_source']); 32 | } 33 | 34 | public function testItSetTheIndex(): void 35 | { 36 | $queryBuilder = new QueryBuilder(); 37 | 38 | $queryBuilder->setIndex('index1'); 39 | 40 | $query = $queryBuilder->build(); 41 | 42 | $this->assertEquals('index1', $query['index']); 43 | } 44 | 45 | public function testItSetTheSize(): void 46 | { 47 | $queryBuilder = new QueryBuilder(); 48 | 49 | $queryBuilder->setSize(50); 50 | 51 | $query = $queryBuilder->build(); 52 | 53 | $this->assertEquals(50, $query['size']); 54 | } 55 | 56 | public function testItSetTheFrom(): void 57 | { 58 | $queryBuilder = new QueryBuilder(); 59 | 60 | $queryBuilder->setFrom(50); 61 | 62 | $query = $queryBuilder->build(); 63 | 64 | $this->assertEquals(50, $query['from']); 65 | } 66 | 67 | public function testItSetThePitAsString(): void 68 | { 69 | $queryBuilder = new QueryBuilder(); 70 | 71 | $queryBuilder->setPit('pit-as-string'); 72 | 73 | $query = $queryBuilder->build(); 74 | 75 | $this->assertEquals(['id' => 'pit-as-string'], $query['body']['pit']); 76 | } 77 | 78 | public function testItSetThePitAsArray(): void 79 | { 80 | $queryBuilder = new QueryBuilder(); 81 | 82 | $queryBuilder->setPit(['id' => 'pit-as-array', 'keep_alive' => '1m']); 83 | 84 | $query = $queryBuilder->build(); 85 | 86 | $this->assertEquals(['id' => 'pit-as-array', 'keep_alive' => '1m'], $query['body']['pit']); 87 | } 88 | 89 | public function testItAllowToSort(): void 90 | { 91 | $queryBuilder = new QueryBuilder(); 92 | 93 | $queryBuilder->addSort('field', [ 94 | 'order' => 'desc', 95 | ]); 96 | $queryBuilder->addSort('field2', [ 97 | 'order' => 'asc', 98 | ]); 99 | 100 | $query = $queryBuilder->build(); 101 | 102 | $this->assertEquals([ 103 | 'field' => [ 104 | 'order' => 'desc', 105 | ], 106 | 'field2' => [ 107 | 'order' => 'asc', 108 | ], 109 | ], $query['body']['sort']); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tests/Aggregation/CardinalityAggregationTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 18 | 'cardinality' => [ 19 | 'field' => 'city', 20 | ], 21 | ], $query->build()); 22 | } 23 | 24 | public function testItBuildTheAggregationUsingAFieldViaSet(): void 25 | { 26 | $query = new CardinalityAggregation('city'); 27 | $query->setScript('test2'); 28 | $query->setField('test'); 29 | 30 | $this->assertEquals([ 31 | 'cardinality' => [ 32 | 'field' => 'test', 33 | ], 34 | ], $query->build()); 35 | } 36 | 37 | public function testItBuildTheAggregationUsingAScriptViaSet(): void 38 | { 39 | $query = new CardinalityAggregation('city'); 40 | $query->setScript('doc.city.value'); 41 | 42 | $this->assertEquals([ 43 | 'cardinality' => [ 44 | 'script' => [ 45 | 'source' => 'doc.city.value', 46 | ], 47 | ], 48 | ], $query->build()); 49 | } 50 | 51 | public function testItBuildTheAggregationUsingAScript(): void 52 | { 53 | $query = new CardinalityAggregation('city', new SourceScript('asd')); 54 | 55 | $this->assertEquals([ 56 | 'cardinality' => [ 57 | 'script' => [ 58 | 'source' => 'asd', 59 | ], 60 | ], 61 | ], $query->build()); 62 | } 63 | 64 | public function testItBuildTheAggregationUsingAFieldAndDifferentName(): void 65 | { 66 | $query = new CardinalityAggregation('city', 'test'); 67 | 68 | $this->assertEquals([ 69 | 'cardinality' => [ 70 | 'field' => 'test', 71 | ], 72 | ], $query->build()); 73 | } 74 | 75 | public function testItBuildTheAggregationWithMissingValue(): void 76 | { 77 | $query = new CardinalityAggregation('city'); 78 | $query->setField('city'); 79 | $query->setMissing(10); 80 | 81 | $this->assertEquals([ 82 | 'cardinality' => [ 83 | 'field' => 'city', 84 | 'missing' => 10, 85 | ], 86 | ], $query->build()); 87 | } 88 | 89 | public function testItBuildTheAggregationWithAPrecisionThreshold(): void 90 | { 91 | $query = new CardinalityAggregation('city'); 92 | $query->setField('city'); 93 | $query->setPrecisionThreshold(100); 94 | 95 | $this->assertEquals([ 96 | 'cardinality' => [ 97 | 'field' => 'city', 98 | 'precision_threshold' => 100, 99 | ], 100 | ], $query->build()); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Aggregation/TermsAggregation.php: -------------------------------------------------------------------------------- 1 | field = new Field($fieldOrSource); 36 | } elseif ($fieldOrSource instanceof Field) { 37 | $this->field = $fieldOrSource; 38 | } elseif ($fieldOrSource instanceof InlineScript) { 39 | $this->script = $fieldOrSource; 40 | } else { 41 | throw new QueryException('Invalid field or source argument in metric aggregation'); 42 | } 43 | } 44 | 45 | public function setSize(int $size): self 46 | { 47 | $this->size = $size; 48 | 49 | return $this; 50 | } 51 | 52 | public function setOrder(string|null $orderField, string $orderValue = SortDirections::ASC): self 53 | { 54 | $this->orderField = $orderField; 55 | $this->orderValue = $orderValue; 56 | 57 | return $this; 58 | } 59 | 60 | public function setInclude(array|string|null $include): self 61 | { 62 | $this->include = $include; 63 | 64 | return $this; 65 | } 66 | 67 | public function setExclude(array|string|null $exclude): self 68 | { 69 | $this->exclude = $exclude; 70 | 71 | return $this; 72 | } 73 | 74 | protected function buildAggregation(): array 75 | { 76 | $build = []; 77 | if (null !== $this->script) { 78 | $build = [ 79 | 'script' => $this->script->build(), 80 | ]; 81 | } elseif (null !== $this->field) { 82 | $build = $this->field->build() + [ 83 | 'size' => $this->size, 84 | ]; 85 | } 86 | 87 | if (null !== $this->orderField) { 88 | $build['order'] = [ 89 | $this->orderField => $this->orderValue, 90 | ]; 91 | } 92 | 93 | if (null !== $this->include) { 94 | $build['include'] = $this->include; 95 | } 96 | 97 | if (null !== $this->exclude) { 98 | $build['exclude'] = $this->exclude; 99 | } 100 | 101 | return $build; 102 | } 103 | 104 | protected function getType(): string 105 | { 106 | return 'terms'; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Query/BoolQuery.php: -------------------------------------------------------------------------------- 1 | $must 12 | * @param array $mustNot 13 | * @param array $should 14 | * @param array $filter 15 | */ 16 | public function __construct( 17 | private array $must = [], 18 | private array $mustNot = [], 19 | private array $should = [], 20 | private array $filter = [], 21 | private array $params = [], 22 | ) {} 23 | 24 | public function addMust(QueryInterface $query): self 25 | { 26 | if ($query === $this) { 27 | throw new QueryException('You are trying to add self to a bool query'); 28 | } 29 | 30 | $this->must[] = $query; 31 | 32 | return $this; 33 | } 34 | 35 | public function addMustNot(QueryInterface $query): self 36 | { 37 | if ($query === $this) { 38 | throw new QueryException('You are trying to add self to a bool query'); 39 | } 40 | 41 | $this->mustNot[] = $query; 42 | 43 | return $this; 44 | } 45 | 46 | public function addShould(QueryInterface $query): self 47 | { 48 | if ($query === $this) { 49 | throw new QueryException('You are trying to add self to a bool query'); 50 | } 51 | 52 | $this->should[] = $query; 53 | 54 | return $this; 55 | } 56 | 57 | public function addFilter(QueryInterface $query): self 58 | { 59 | if ($query === $this) { 60 | throw new QueryException('You are trying to add self to a bool query'); 61 | } 62 | 63 | $this->filter[] = $query; 64 | 65 | return $this; 66 | } 67 | 68 | public function isEmpty(): bool 69 | { 70 | return empty($this->must) 71 | && empty($this->mustNot) 72 | && empty($this->should) 73 | && empty($this->filter); 74 | } 75 | 76 | public function setParams(array $params): self 77 | { 78 | $this->params = $params; 79 | 80 | return $this; 81 | } 82 | 83 | public function build(): array 84 | { 85 | $query = $this->params; 86 | 87 | $this 88 | ->buildQueries($query, 'should', $this->should) 89 | ->buildQueries($query, 'filter', $this->filter) 90 | ->buildQueries($query, 'must_not', $this->mustNot) 91 | ->buildQueries($query, 'must', $this->must); 92 | 93 | if ((is_countable($query) ? count($query) : 0) === 0) { 94 | throw new QueryException('Empty BoolQuery'); 95 | } 96 | 97 | return [ 98 | 'bool' => $query, 99 | ]; 100 | } 101 | 102 | /** 103 | * @param array $queries 104 | * 105 | * @return $this 106 | */ 107 | protected function buildQueries(array &$query, string $name, array $queries): self 108 | { 109 | if ([] === $queries) { 110 | return $this; 111 | } 112 | 113 | $query[$name] = []; 114 | 115 | foreach ($queries as $filter) { 116 | $query[$name][] = $filter->build(); 117 | } 118 | 119 | return $this; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Query/Query.php: -------------------------------------------------------------------------------- 1 | $values 11 | */ 12 | public static function terms(string $field, array $values): TermsQuery 13 | { 14 | return new TermsQuery($field, $values); 15 | } 16 | 17 | public static function term(string $field, string|int|float|bool $value): TermQuery 18 | { 19 | return new TermQuery($field, $value); 20 | } 21 | 22 | public static function wildcard(string $field, string $value): WildcardQuery 23 | { 24 | return new WildcardQuery($field, $value); 25 | } 26 | 27 | /** 28 | * @param array $must 29 | * @param array $mustNot 30 | * @param array $should 31 | * @param array $filter 32 | */ 33 | public static function bool( 34 | array $must = [], 35 | array $mustNot = [], 36 | array $should = [], 37 | array $filter = [], 38 | ): BoolQuery { 39 | return new BoolQuery($must, $mustNot, $should, $filter); 40 | } 41 | 42 | public static function range( 43 | string $field, 44 | int|float|string|null $lt = null, 45 | int|float|string|null $gt = null, 46 | int|float|string|null $lte = null, 47 | int|float|string|null $gte = null, 48 | ): RangeQuery { 49 | return new RangeQuery($field, $lt, $gt, $lte, $gte); 50 | } 51 | 52 | public static function nested(?string $path, QueryInterface $query): NestedQuery 53 | { 54 | return new NestedQuery($path, $query); 55 | } 56 | 57 | public static function match(string $field, string $query): MatchQuery 58 | { 59 | return new MatchQuery($field, $query); 60 | } 61 | 62 | public static function matchPhrase(string $field, string $query): MatchPhraseQuery 63 | { 64 | return new MatchPhraseQuery($field, $query); 65 | } 66 | 67 | public static function matchPhrasePrefix(string $field, string $query): MatchPhrasePrefixQuery 68 | { 69 | return new MatchPhrasePrefixQuery($field, $query); 70 | } 71 | 72 | public static function multiMatch(array $fields, string $query): MultiMatchQuery 73 | { 74 | return new MultiMatchQuery($fields, $query); 75 | } 76 | 77 | public static function functionScoreQuery(array $fields, string $query): FunctionScoreQuery 78 | { 79 | return new FunctionScoreQuery($fields, $query); 80 | } 81 | 82 | public static function functionsQuery(string $field): FunctionsQuery 83 | { 84 | return new FunctionsQuery($field); 85 | } 86 | 87 | public static function GeoBoundingBoxQuery(string $field): GeoBoundingBoxQuery 88 | { 89 | return new GeoBoundingBoxQuery($field); 90 | } 91 | 92 | /** 93 | * @param float[]|int[] $position 94 | */ 95 | public static function geoDistance(string $field, string $distance, array $position): GeoDistanceQuery 96 | { 97 | return new GeoDistanceQuery($distance, $field, $position); 98 | } 99 | 100 | /** 101 | * @param mixed[]|float[]|int[] $coordinates 102 | */ 103 | public static function geoShape(string $field, string $type, array $coordinates): GeoShapeQuery 104 | { 105 | return new GeoShapeQuery($field, $type, $coordinates); 106 | } 107 | 108 | public static function prefix(string $field, string $value): PrefixQuery 109 | { 110 | return new PrefixQuery($field, $value); 111 | } 112 | 113 | public static function queryString(string $query, ?string $defaultField = null): QueryStringQuery 114 | { 115 | return new QueryStringQuery($query, $defaultField); 116 | } 117 | 118 | public static function rankFeature(string $field): RankFeatureQuery 119 | { 120 | return new RankFeatureQuery($field); 121 | } 122 | 123 | public static function exists(string $field): ExistsQuery 124 | { 125 | return new ExistsQuery($field); 126 | } 127 | 128 | /** 129 | * @param mixed[]|string[] $fields 130 | */ 131 | public static function simpleQueryString(array $fields, string $query): SimpleQueryStringQuery 132 | { 133 | return new SimpleQueryStringQuery($fields, $query); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /tests/Query/BoolQueryTest.php: -------------------------------------------------------------------------------- 1 | expectException(QueryException::class); 19 | 20 | $boolQuery->build(); 21 | } 22 | 23 | public function testAddFilterWithSameObject(): void 24 | { 25 | $this->expectExceptionMessage('You are trying to add self to a bool query'); 26 | $boolQuery = new BoolQuery(); 27 | 28 | $boolQuery->addFilter($boolQuery); 29 | $boolQuery->build(); 30 | } 31 | 32 | public function testAddShouldWithSameObject(): void 33 | { 34 | $this->expectExceptionMessage('You are trying to add self to a bool query'); 35 | $boolQuery = new BoolQuery(); 36 | 37 | $boolQuery->addShould($boolQuery); 38 | $boolQuery->build(); 39 | } 40 | 41 | public function testAddMustNotWithSameObject(): void 42 | { 43 | $this->expectExceptionMessage('You are trying to add self to a bool query'); 44 | $boolQuery = new BoolQuery(); 45 | 46 | $boolQuery->addMustNot($boolQuery); 47 | $boolQuery->build(); 48 | } 49 | 50 | public function testAddMustWithSameObject(): void 51 | { 52 | $this->expectExceptionMessage('You are trying to add self to a bool query'); 53 | $boolQuery = new BoolQuery(); 54 | 55 | $boolQuery->addMust($boolQuery); 56 | $boolQuery->build(); 57 | } 58 | 59 | public function testItAddAMustClause(): void 60 | { 61 | $boolQuery = new BoolQuery(); 62 | 63 | $boolQuery->addMust(Query::term('field', 'value')); 64 | 65 | $query = $boolQuery->build(); 66 | 67 | $this->assertEquals([ 68 | 'bool' => [ 69 | 'must' => [ 70 | [ 71 | 'term' => [ 72 | 'field' => [ 73 | 'value' => 'value', 74 | ], 75 | ], 76 | ], 77 | ], 78 | ], 79 | ], $query); 80 | } 81 | 82 | public function testItAddAMustNotClause(): void 83 | { 84 | $boolQuery = new BoolQuery(); 85 | 86 | $boolQuery->addMustNot(Query::term('field', 'value')); 87 | 88 | $query = $boolQuery->build(); 89 | 90 | $this->assertEquals([ 91 | 'bool' => [ 92 | 'must_not' => [ 93 | [ 94 | 'term' => [ 95 | 'field' => [ 96 | 'value' => 'value', 97 | ], 98 | ], 99 | ], 100 | ], 101 | ], 102 | ], $query); 103 | } 104 | 105 | public function testItAddAShouldClause(): void 106 | { 107 | $boolQuery = new BoolQuery(); 108 | 109 | $boolQuery->addShould(Query::term('field', 'value')); 110 | 111 | $query = $boolQuery->build(); 112 | 113 | $this->assertEquals([ 114 | 'bool' => [ 115 | 'should' => [ 116 | [ 117 | 'term' => [ 118 | 'field' => [ 119 | 'value' => 'value', 120 | ], 121 | ], 122 | ], 123 | ], 124 | ], 125 | ], $query); 126 | } 127 | 128 | public function testItAddAFilterClause(): void 129 | { 130 | $boolQuery = new BoolQuery(); 131 | 132 | $boolQuery->addFilter(Query::term('field', 'value')); 133 | 134 | $query = $boolQuery->build(); 135 | 136 | $this->assertEquals([ 137 | 'bool' => [ 138 | 'filter' => [ 139 | [ 140 | 'term' => [ 141 | 'field' => [ 142 | 'value' => 'value', 143 | ], 144 | ], 145 | ], 146 | ], 147 | ], 148 | ], $query); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /tests/Query/SimpleQueryStringQueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([ 31 | 'simple_query_string' => 32 | [ 33 | 'query' => '~brown fox', 34 | 'fields' => 35 | [ 36 | 'subject', 37 | 'body', 38 | ], 39 | 'flags' => 'ALL', 40 | 'fuzzy_transpositions' => true, 41 | 'fuzzy_max_expansions' => 50, 42 | 'fuzzy_prefix_length' => 0, 43 | 'default_operator' => 'or', 44 | 'analyzer' => 'standard', 45 | 'lenient' => false, 46 | 'quote_field_suffix' => '', 47 | 'analyze_wildcard' => false, 48 | 'auto_generate_synonyms_phrase_query' => true, 49 | 'minimum_should_match' => '1%', 50 | ], 51 | ], $query->build()); 52 | } 53 | 54 | public function testItBuildTheQueryWithAFuzziness(): void 55 | { 56 | $query = new SimpleQueryStringQuery( 57 | ['subject', 'body'], 58 | '~brown fox', 59 | 'ALL', 60 | true, 61 | 50, 62 | 0, 63 | "1%", 64 | "or", 65 | "standard", 66 | false, 67 | "", 68 | false, 69 | true 70 | 71 | ); 72 | $query->setDefaultOperator('and'); 73 | $this->assertEquals([ 74 | 'simple_query_string' => 75 | [ 76 | 'query' => '~brown fox', 77 | 'fields' => 78 | [ 79 | 'subject', 80 | 'body', 81 | ], 82 | 'flags' => 'ALL', 83 | 'fuzzy_transpositions' => true, 84 | 'fuzzy_max_expansions' => 50, 85 | 'fuzzy_prefix_length' => 0, 86 | 'default_operator' => 'and', 87 | 'analyzer' => 'standard', 88 | 'lenient' => false, 89 | 'quote_field_suffix' => '', 90 | 'analyze_wildcard' => false, 91 | 'auto_generate_synonyms_phrase_query' => true, 92 | 'minimum_should_match' => '1%', 93 | ], 94 | ], $query->build()); 95 | } 96 | 97 | public function testItBuildTheQueryWithBoost(): void 98 | { 99 | $query = new SimpleQueryStringQuery( 100 | ['subject', 'body'], 101 | '~brown fox', 102 | 'ALL', 103 | true, 104 | 50, 105 | 0, 106 | "1%", 107 | "or", 108 | "standard", 109 | false, 110 | "", 111 | false, 112 | true 113 | 114 | ); 115 | $query->setBoost(3); 116 | 117 | $this->assertEquals([ 118 | 'simple_query_string' => 119 | [ 120 | 'query' => '~brown fox', 121 | 'fields' => 122 | [ 123 | 'subject', 124 | 'body', 125 | ], 126 | 'flags' => 'ALL', 127 | 'fuzzy_transpositions' => true, 128 | 'fuzzy_max_expansions' => 50, 129 | 'fuzzy_prefix_length' => 0, 130 | 'default_operator' => 'or', 131 | 'analyzer' => 'standard', 132 | 'lenient' => false, 133 | 'quote_field_suffix' => '', 134 | 'analyze_wildcard' => false, 135 | 'auto_generate_synonyms_phrase_query' => true, 136 | 'minimum_should_match' => '1%', 137 | 'boost' => 3, 138 | ], 139 | ], $query->build()); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Aggregation/Aggregation.php: -------------------------------------------------------------------------------- 1 | $aggregations 14 | */ 15 | public static function terms( 16 | string $name, 17 | string|Field|InlineScript $fieldOrSource, 18 | array $aggregations = [], 19 | ): TermsAggregation { 20 | return new TermsAggregation($name, $fieldOrSource, $aggregations); 21 | } 22 | 23 | /** 24 | * @param array $aggregations 25 | */ 26 | public static function dateHistogram( 27 | string $nameAndField, 28 | string $calendarInterval, 29 | ?string $field = null, 30 | array $aggregations = [], 31 | ): DateHistogramAggregation { 32 | return new DateHistogramAggregation($nameAndField, $calendarInterval, $field, $aggregations); 33 | } 34 | 35 | /** 36 | * @param array $aggregations 37 | */ 38 | public static function nested(string $name, string $path, array $aggregations = []): NestedAggregation 39 | { 40 | return new NestedAggregation($name, $path, $aggregations); 41 | } 42 | 43 | /** 44 | * @param array $aggregations 45 | */ 46 | public static function reverseNested(string $name, ?string $path = null, array $aggregations = []): ReverseNestedAggregation 47 | { 48 | return new ReverseNestedAggregation($name, $path, $aggregations); 49 | } 50 | 51 | /** 52 | * @param array $aggregations 53 | */ 54 | public static function filter(string $name, QueryInterface $query, array $aggregations = []): FilterAggregation 55 | { 56 | return new FilterAggregation($name, $query, $aggregations); 57 | } 58 | 59 | /** 60 | * @param array $aggregations 61 | */ 62 | public static function cardinality( 63 | string $nameAndField, 64 | string|SourceScript|Field|null $fieldOrSource = null, 65 | array $aggregations = [], 66 | ): CardinalityAggregation { 67 | return new CardinalityAggregation($nameAndField, $fieldOrSource, $aggregations); 68 | } 69 | 70 | /** 71 | * @param array $aggregations 72 | */ 73 | public static function max( 74 | string $nameAndField, 75 | string|SourceScript|Field|null $fieldOrSource = null, 76 | array $aggregations = [], 77 | ): MaxAggregation { 78 | return new MaxAggregation($nameAndField, $fieldOrSource, $aggregations); 79 | } 80 | 81 | /** 82 | * @param array $aggregations 83 | */ 84 | public static function min( 85 | string $nameAndField, 86 | string|SourceScript|Field|null $fieldOrSource = null, 87 | array $aggregations = [], 88 | ): MinAggregation { 89 | return new MinAggregation($nameAndField, $fieldOrSource, $aggregations); 90 | } 91 | 92 | /** 93 | * @param array $aggregations 94 | */ 95 | public static function sum( 96 | string $nameAndField, 97 | string|SourceScript|Field|null $fieldOrSource = null, 98 | array $aggregations = [], 99 | ): SumAggregation { 100 | return new SumAggregation($nameAndField, $fieldOrSource, $aggregations); 101 | } 102 | 103 | /** 104 | * @param array $aggregations 105 | */ 106 | public static function topHits(string $name, array $aggregations = []): TopHitsAggregation 107 | { 108 | return new TopHitsAggregation($name, $aggregations); 109 | } 110 | 111 | public static function histogram(string $name, string $field, int $interval): HistogramAggregation 112 | { 113 | return new HistogramAggregation($name, $field, $interval); 114 | } 115 | 116 | /** 117 | * @param array $ranges pass desired ranges that will be converted to linear range 118 | */ 119 | public static function ranges(string $name, string $field, array $ranges): RangesAggregation 120 | { 121 | return new RangesAggregation($name, $field, $ranges); 122 | } 123 | 124 | public static function widthHistogram(string $name, string $field, int $buckets): WidthHistogramAggregation 125 | { 126 | return new WidthHistogramAggregation($name, $field, $buckets); 127 | } 128 | 129 | public static function stats(string $name): StatsAggregation 130 | { 131 | return new StatsAggregation($name); 132 | } 133 | 134 | public static function valueCount(string $name): ValueCountAggregation 135 | { 136 | return new ValueCountAggregation($name); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/QueryBuilder.php: -------------------------------------------------------------------------------- 1 | source = $source; 41 | 42 | return $this; 43 | } 44 | 45 | public function setIndex(string $index): self 46 | { 47 | $this->index = $index; 48 | 49 | return $this; 50 | } 51 | 52 | public function setFrom(int $from): self 53 | { 54 | $this->from = $from; 55 | 56 | return $this; 57 | } 58 | 59 | public function setSize(int $size): self 60 | { 61 | $this->size = $size; 62 | 63 | return $this; 64 | } 65 | 66 | public function setPit(string|array|null $pit): self 67 | { 68 | if (is_array($pit) && !isset($pit['id'])) { 69 | throw new \InvalidArgumentException('Parameter "pit" is not valid. Value with key "id" is not set.'); 70 | } 71 | 72 | $this->pit = $pit; 73 | 74 | return $this; 75 | } 76 | 77 | public function setSearchAfter(?array $searchAfter): self 78 | { 79 | $this->searchAfter = $searchAfter; 80 | 81 | return $this; 82 | } 83 | 84 | public function setQuery(QueryInterface $query): self 85 | { 86 | $this->query = $query; 87 | 88 | return $this; 89 | } 90 | 91 | public function setPostFilter(QueryInterface $query): self 92 | { 93 | $this->postFilter = $query; 94 | 95 | return $this; 96 | } 97 | 98 | public function setFields(?array $fields): self 99 | { 100 | $this->fields = $fields; 101 | 102 | return $this; 103 | } 104 | 105 | public function setHighlight(?array $highlight): self 106 | { 107 | $this->highlight = $highlight; 108 | 109 | return $this; 110 | } 111 | 112 | public function setParams(array $params): self 113 | { 114 | $this->params = $params; 115 | 116 | return $this; 117 | } 118 | 119 | public function build(): array 120 | { 121 | $query = $this->params; 122 | if (false === isset($query['body'])) { 123 | $query['body'] = []; 124 | } 125 | 126 | if (null !== $this->index) { 127 | $query['index'] = $this->index; 128 | } 129 | 130 | if (null !== $this->from) { 131 | $query['from'] = $this->from; 132 | } 133 | 134 | if (null !== $this->size) { 135 | $query['size'] = $this->size; 136 | } 137 | 138 | if (null !== $this->pit) { 139 | if (is_string($this->pit)) { 140 | $query['body']['pit'] = ['id' => $this->pit]; 141 | } else { 142 | $query['body']['pit'] = $this->pit; 143 | } 144 | } 145 | 146 | if (null !== $this->searchAfter) { 147 | $query['body']['search_after'] = $this->searchAfter; 148 | } 149 | 150 | if (null !== $this->source) { 151 | $query['_source'] = $this->source; 152 | } 153 | 154 | if (null !== $this->query) { 155 | $query['body']['query'] = $this->query->build(); 156 | } 157 | 158 | if (null !== $this->postFilter) { 159 | $query['body']['post_filter'] = $this->postFilter->build(); 160 | } 161 | 162 | if (null !== $this->fields) { 163 | $query['body']['fields'] = $this->fields; 164 | } 165 | 166 | if (null !== $this->highlight) { 167 | $query['body']['highlight'] = $this->highlight; 168 | } 169 | 170 | $this->buildSortTo($query['body']) 171 | ->buildAggregationsTo($query['body']) 172 | ->buildCollapseTo($query['body']); 173 | 174 | return $query; 175 | } 176 | 177 | public function getSource(): bool|array|string|null 178 | { 179 | return $this->source; 180 | } 181 | 182 | public function getFrom(): ?int 183 | { 184 | return $this->from; 185 | } 186 | 187 | public function getSize(): ?int 188 | { 189 | return $this->size; 190 | } 191 | 192 | public function getPit(): string|array|null 193 | { 194 | return $this->pit; 195 | } 196 | 197 | public function getSearchAfter(): ?array 198 | { 199 | return $this->searchAfter; 200 | } 201 | 202 | public function getQuery(): ?QueryInterface 203 | { 204 | return $this->query; 205 | } 206 | 207 | public function getPostFilter(): ?QueryInterface 208 | { 209 | return $this->postFilter; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/Query/SimpleQueryStringQuery.php: -------------------------------------------------------------------------------- 1 | minimumShouldMatch = $minimumShouldMatch; 34 | } 35 | 36 | public function setFlags(string|null $flags): self 37 | { 38 | $this->flags = $flags; 39 | 40 | return $this; 41 | } 42 | 43 | public function setFuzzyTranspositions(bool|null $fuzzyTranspositions): self 44 | { 45 | $this->fuzzyTranspositions = $fuzzyTranspositions; 46 | 47 | return $this; 48 | } 49 | 50 | public function setFuzzyMaxExpansions(int|null $fuzzyMaxExpansions): self 51 | { 52 | $this->fuzzyMaxExpansions = $fuzzyMaxExpansions; 53 | 54 | return $this; 55 | } 56 | 57 | public function setFuzzyPrefixLength(int|null $fuzzyPrefixLength): self 58 | { 59 | $this->fuzzyPrefixLength = $fuzzyPrefixLength; 60 | 61 | return $this; 62 | } 63 | 64 | public function setDefaultOperator(string|null $defaultOperator): self 65 | { 66 | $this->defaultOperator = $defaultOperator; 67 | 68 | return $this; 69 | } 70 | 71 | public function setAnalyzer(string|null $analyzer): self 72 | { 73 | $this->analyzer = $analyzer; 74 | 75 | return $this; 76 | } 77 | 78 | public function setLenient(bool|null $lenient): self 79 | { 80 | $this->lenient = $lenient; 81 | 82 | return $this; 83 | } 84 | 85 | public function setQuoteFieldSuffix(string|null $quoteFieldSuffix): self 86 | { 87 | $this->quoteFieldSuffix = $quoteFieldSuffix; 88 | 89 | return $this; 90 | } 91 | 92 | public function setAnalyzeWildCard(bool|null $analyzeWildCard): self 93 | { 94 | $this->analyzeWildCard = $analyzeWildCard; 95 | 96 | return $this; 97 | } 98 | 99 | public function setAutoGenerateSynonymsPhraseQuery(bool|null $autoGenerateSynonymsPhraseQuery): self 100 | { 101 | $this->autoGenerateSynonymsPhraseQuery = $autoGenerateSynonymsPhraseQuery; 102 | 103 | return $this; 104 | } 105 | 106 | public function setFields(array $fields): self 107 | { 108 | $this->fields = $fields; 109 | 110 | return $this; 111 | } 112 | 113 | public function setQuery(string $query): self 114 | { 115 | $this->query = $query; 116 | 117 | return $this; 118 | } 119 | 120 | public function setParams(array $params): self 121 | { 122 | $this->params = $params; 123 | 124 | return $this; 125 | } 126 | 127 | public function build(): array 128 | { 129 | $data = [ 130 | 'query' => $this->query, 131 | 'fields' => $this->fields, 132 | ]; 133 | if (null !== $this->flags) { 134 | $data['flags'] = $this->flags; 135 | } 136 | if (null !== $this->fuzzyTranspositions) { 137 | $data['fuzzy_transpositions'] = $this->fuzzyTranspositions; 138 | } 139 | 140 | if (null !== $this->fuzzyMaxExpansions) { 141 | $data['fuzzy_max_expansions'] = $this->fuzzyMaxExpansions; 142 | } 143 | 144 | if (null !== $this->fuzzyPrefixLength) { 145 | $data['fuzzy_prefix_length'] = $this->fuzzyPrefixLength; 146 | } 147 | 148 | if (null !== $this->defaultOperator) { 149 | $data['default_operator'] = $this->defaultOperator; 150 | } 151 | 152 | if (null !== $this->analyzer) { 153 | $data['analyzer'] = $this->analyzer; 154 | } 155 | 156 | if (null !== $this->lenient) { 157 | $data['lenient'] = $this->lenient; 158 | } 159 | 160 | if (null !== $this->quoteFieldSuffix) { 161 | $data['quote_field_suffix'] = $this->quoteFieldSuffix; 162 | } 163 | 164 | if (null !== $this->analyzeWildCard) { 165 | $data['analyze_wildcard'] = $this->analyzeWildCard; 166 | } 167 | if (null !== $this->autoGenerateSynonymsPhraseQuery) { 168 | $data['auto_generate_synonyms_phrase_query'] = $this->autoGenerateSynonymsPhraseQuery; 169 | } 170 | 171 | $this->buildMinimumShouldMatchTo($data); 172 | $this->buildBoostTo($data); 173 | 174 | $build = $this->params; 175 | $build['simple_query_string'] = $data; 176 | 177 | return $build; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /tests/Aggregation/RangesAggregationTest.php: -------------------------------------------------------------------------------- 1 | [ 22 | 'field' => 'option_236', 23 | 'ranges' => [ 24 | 0 => [ 25 | 'from' => 0, 26 | 'to' => 10, 27 | 'key' => 10, 28 | ], 29 | 1 => [ 30 | 'from' => 10, 31 | 'to' => 50, 32 | 'key' => 50, 33 | ], 34 | 2 => [ 35 | 'from' => 50, 36 | 'to' => 100, 37 | 'key' => 100, 38 | ], 39 | 3 => [ 40 | 'from' => 100, 41 | 'to' => 200, 42 | 'key' => 200, 43 | ], 44 | 4 => [ 45 | 'from' => 200, 46 | 'to' => 350, 47 | 'key' => 350, 48 | ], 49 | 5 => [ 50 | 'from' => 350, 51 | 'to' => 500, 52 | 'key' => 500, 53 | ], 54 | 6 => [ 55 | 'from' => 500, 56 | 'to' => 750, 57 | 'key' => 750, 58 | ], 59 | 7 => [ 60 | 'from' => 750, 61 | 'to' => 1000, 62 | 'key' => 1000, 63 | ], 64 | 8 => [ 65 | 'from' => 1000, 66 | 'to' => 1500, 67 | 'key' => 1500, 68 | ], 69 | 9 => [ 70 | 'from' => 1500, 71 | 'to' => 2500, 72 | 'key' => 2500, 73 | ], 74 | 10 => [ 75 | 'key' => '2500-*', 76 | 'from' => 2500, 77 | ], 78 | ], 79 | ], 80 | ]; 81 | $built = $instance->build(); 82 | $this->assertEquals($expected, $built); 83 | } 84 | 85 | public function testGetBeachDistancesRangesAggregationWithEqualRanges(): void 86 | { 87 | $instance = new RangesAggregation( 88 | 'beach', 89 | 'option_236', 90 | [10, 50, 100, 200, 350, 500, 750, 1000, 1500, 2500], 91 | [], 92 | true 93 | ); 94 | 95 | $expected = [ 96 | 'range' => [ 97 | 'field' => 'option_236', 98 | 'ranges' => [ 99 | 0 => [ 100 | 'from' => 0, 101 | 'to' => 11, 102 | 'key' => 10, 103 | ], 104 | 1 => [ 105 | 'from' => 10, 106 | 'to' => 51, 107 | 'key' => 50, 108 | ], 109 | 2 => [ 110 | 'from' => 50, 111 | 'to' => 101, 112 | 'key' => 100, 113 | ], 114 | 3 => [ 115 | 'from' => 100, 116 | 'to' => 201, 117 | 'key' => 200, 118 | ], 119 | 4 => [ 120 | 'from' => 200, 121 | 'to' => 351, 122 | 'key' => 350, 123 | ], 124 | 5 => [ 125 | 'from' => 350, 126 | 'to' => 501, 127 | 'key' => 500, 128 | ], 129 | 6 => [ 130 | 'from' => 500, 131 | 'to' => 751, 132 | 'key' => 750, 133 | ], 134 | 7 => [ 135 | 'from' => 750, 136 | 'to' => 1001, 137 | 'key' => 1000, 138 | ], 139 | 8 => [ 140 | 'from' => 1000, 141 | 'to' => 1501, 142 | 'key' => 1500, 143 | ], 144 | 9 => [ 145 | 'from' => 1500, 146 | 'to' => 2501, 147 | 'key' => 2500, 148 | ], 149 | 10 => [ 150 | 'key' => '2500-*', 151 | 'from' => 2500, 152 | ], 153 | ], 154 | ], 155 | ]; 156 | $built = $instance->build(); 157 | $this->assertEquals($expected, $built); 158 | } 159 | } 160 | --------------------------------------------------------------------------------