https://github.com/spatie/elasticsearch-query-builder
Build and execute an Elasticsearch search query using a fluent PHP API
https://github.com/spatie/elasticsearch-query-builder
elasticsearch php
Last synced: about 1 year ago
JSON representation
Build and execute an Elasticsearch search query using a fluent PHP API
- Host: GitHub
- URL: https://github.com/spatie/elasticsearch-query-builder
- Owner: spatie
- License: mit
- Created: 2021-06-07T10:36:25.000Z (almost 5 years ago)
- Default Branch: main
- Last Pushed: 2025-04-14T09:31:34.000Z (about 1 year ago)
- Last Synced: 2025-05-13T14:19:11.534Z (about 1 year ago)
- Topics: elasticsearch, php
- Language: PHP
- Homepage: https://spatie.be/open-source
- Size: 130 KB
- Stars: 172
- Watchers: 9
- Forks: 45
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Funding: .github/FUNDING.yml
- License: LICENSE.md
Awesome Lists containing this project
README
# Build and execute ElasticSearch queries using a fluent PHP API
[](https://packagist.org/packages/spatie/elasticsearch-query-builder)
[](https://github.com/spatie/elasticsearch-query-builder/actions/workflows/run-tests.yml)
[](https://packagist.org/packages/spatie/elasticsearch-query-builder)
---
This package is a _lightweight_ query builder for ElasticSearch. It was specifically built for our [elasticsearch-search-string-parser](https://github.com/spatie/elasticsearch-search-string-parser) so it covers most use-cases but might lack certain features. We're always open for PRs if you need anything specific!
```php
use Spatie\ElasticsearchQueryBuilder\Aggregations\MaxAggregation;
use Spatie\ElasticsearchQueryBuilder\Builder;
use Spatie\ElasticsearchQueryBuilder\Queries\MatchQuery;
$client = Elastic\Elasticsearch\ClientBuilder::create()->build();
$companies = (new Builder($client))
->index('companies')
->addQuery(MatchQuery::create('name', 'spatie', fuzziness: 3))
->addAggregation(MaxAggregation::create('score'))
->search();
```
## Support us
[
](https://spatie.be/github-ad-click/elasticsearch-query-builder)
We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).
We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards).
## Installation
You can install the package via composer:
```bash
composer require spatie/elasticsearch-query-builder
```
> **Note**
> If you're using `elasticsearch/elasticsearch` v7 you need to use [v1](https://github.com/spatie/elasticsearch-query-builder/tree/v1) of this package.
## Basic usage
The only class you really need to interact with is the `Spatie\ElasticsearchQueryBuilder\Builder` class. It requires an `\Elastic\Elasticsearch\Client` passed in the constructor. Take a look at the [ElasticSearch SDK docs](https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/installation.html) to learn more about connecting to your ElasticSearch cluster.
The `Builder` class contains some methods to [add queries](#adding-queries), [aggregations](#adding-aggregations), [sorts](#adding-sorts), [fields](#retrieve-specific-fields) and some extras for [pagination](#pagination). You can read more about these methods below. Once you've fully built-up the query you can use `$builder->search()` to execute the query or `$builder->getPayload()` to get the raw payload for ElasticSearch.
```php
use Spatie\ElasticsearchQueryBuilder\Queries\RangeQuery;
use Spatie\ElasticsearchQueryBuilder\Builder;
$client = Elastic\Elasticsearch\ClientBuilder::create()->build();
$builder = new Builder($client);
$builder->addQuery(RangeQuery::create('age')->gte(18));
$results = $builder->search(); // raw response from ElasticSearch
```
#### Multi-Search Queries
Multi-Search queries are also available using the [`MultiBuilder` class](#multi-search-query-builder).
## Adding queries
The `$builder->addQuery()` method can be used to add any of the available `Query` types to the builder. The available query types can be found below or in the `src/Queries` directory of this repo. Every `Query` has a static `create()` method to pass its most important parameters.
The following query types are available:
#### `ExistsQuery`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-exists-query.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-exists-query.html)
```php
\Spatie\ElasticsearchQueryBuilder\Queries\ExistsQuery::create('terms_and_conditions');
```
#### `GeoshapeQuery`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-query.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-shape-query.html);
```php
\Spatie\ElasticsearchQueryBuilder\Queries\GeoshapeQuery::create(
'location',
\Spatie\ElasticsearchQueryBuilder\Queries\GeoshapeQuery::TYPE_POLYGON,
[[1.0, 2.0]],
\Spatie\ElasticsearchQueryBuilder\Queries\GeoShapeQuery::RELATION_INTERSECTS,
);
```
#### `MatchQuery`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html)
```php
\Spatie\ElasticsearchQueryBuilder\Queries\MatchQuery::create('name', 'john doe', fuzziness: 2, boost: 5.0);
```
#### `MatchPhraseQuery`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query-phrase.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query-phrase.html)
```php
\Spatie\ElasticsearchQueryBuilder\Queries\MatchPhraseQuery::create('name', 'john doe', slop: 2,zeroTermsQuery: "none",analyzer: "my_analyzer");
```
#### `MultiMatchQuery`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html)
```php
\Spatie\ElasticsearchQueryBuilder\Queries\MultiMatchQuery::create('john', ['email', 'email'], fuzziness: 'auto');
```
#### `NestedQuery`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-nested-query.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-nested-query.html)
```php
\Spatie\ElasticsearchQueryBuilder\Queries\NestedQuery::create(
'user',
new \Spatie\ElasticsearchQueryBuilder\Queries\MatchQuery('name', 'john')
);
```
##### `NestedQuery` `InnerHits`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/inner-hits.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/inner-hits.html)
```php
$nestedQuery = \Spatie\ElasticsearchQueryBuilder\Queries\NestedQuery::create(
'comments',
\Spatie\ElasticsearchQueryBuilder\Queries\TermsQuery::create('comments.published', true)
);
$nestedQuery->innerHits(
\Spatie\ElasticsearchQueryBuilder\Queries\NestedQuery\InnerHits::create('top_three_liked_comments')
->size(3)
->addSort(
\Spatie\ElasticsearchQueryBuilder\Sorts\Sort::create(
'comments.likes',
\Spatie\ElasticsearchQueryBuilder\Sorts\Sort::DESC
)
)
->fields(['comments.content', 'comments.author', 'comments.likes'])
);
```
#### `RangeQuery`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html)
```php
\Spatie\ElasticsearchQueryBuilder\Queries\RangeQuery::create('age')
->gte(18)
->lte(1337);
```
#### `TermQuery`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html)
```php
\Spatie\ElasticsearchQueryBuilder\Queries\TermQuery::create('user.id', 'flx');
```
#### `TermsQuery`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html)
```php
\Spatie\ElasticsearchQueryBuilder\Queries\TermsQuery::create('user.id', ['flx', 'fly'], boost: 5.0);
```
#### `WildcardQuery`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-wildcard-query.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-wildcard-query.html)
```php
\Spatie\ElasticsearchQueryBuilder\Queries\WildcardQuery::create('user.id', '*doe');
```
#### `PercolateQuery`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-percolate-query.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-percolate-query.html)
```php
\Spatie\ElasticsearchQueryBuilder\Queries\PercolateQuery::create('query', ['title' => 'foo', 'body' => 'bar']);
```
#### `BoolQuery`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html)
```php
\Spatie\ElasticsearchQueryBuilder\Queries\BoolQuery::create()
->add($matchQuery, 'must_not')
->add($existsQuery, 'must_not');
```
#### `collapse`
The `collapse` feature allows grouping search results by a specific field while retrieving top documents from each group using `inner_hits`. This is useful for avoiding duplicate entities in search results while still accessing grouped data.
[https://www.elastic.co/guide/en/elasticsearch/reference/current/collapse-search-results.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/collapse-search-results.html)
```php
use Spatie\ElasticsearchQueryBuilder\Sorts\Sort;
use Spatie\ElasticsearchQueryBuilder\Builder;
// Initialize ExtendedBuilder with an Elasticsearch client
$builder = new Builder($client);
// Apply collapse to group by 'user_id'
$builder->collapse(
'user_id', // Field to collapse on
[
'name' => 'top_three_liked_posts',
'size' => 3, // Retrieve top 3 posts per user
'sort' => [
Sort::create('post.likes', Sort::DESC), // Sort posts by likes (descending)
],
'fields' => ['post.title', 'post.content', 'post.likes'], // Select specific fields
],
10, // Max concurrent group searches
);
// Execute the search
$response = $builder->search();
```
### Chaining multiple queries
Multiple `addQuery()` calls can be chained on one `Builder`. Under the hood they'll be added to a `BoolQuery` with occurrence type `must`. By passing a second argument to the `addQuery()` method you can select a different occurrence type:
```php
$builder
->addQuery(
MatchQuery::create('name', 'billie'),
'must_not' // available types: must, must_not, should, filter
)
->addQuery(
MatchQuery::create('team', 'eillish')
);
```
More information on the boolean query and its occurrence types can be found [in the ElasticSearch docs](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html).
## Adding aggregations
The `$builder->addAggregation()` method can be used to add any of the available `Aggregation`s to the builder. The available aggregation types can be found below or in the `src/Aggregations` directory of this repo. Every `Aggregation` has a static `create()` method to pass its most important parameters and sometimes some extra methods.
```php
use Spatie\ElasticsearchQueryBuilder\Aggregations\TermsAggregation;
use Spatie\ElasticsearchQueryBuilder\Builder;
$results = (new Builder(Elastic\Elasticsearch\ClientBuilder::create()->build()))
->addAggregation(TermsAggregation::create('genres', 'genre'))
->search();
$genres = $results['aggregations']['genres']['buckets'];
```
The following query types are available:
#### `CardinalityAggregation`
```php
\Spatie\ElasticsearchQueryBuilder\Aggregations\CardinalityAggregation::create('team_agg', 'team_name');
```
#### `FilterAggregation`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filter-aggregation.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filter-aggregation.html)
```php
\Spatie\ElasticsearchQueryBuilder\Aggregations\FilterAggregation::create(
'tshirts',
\Spatie\ElasticsearchQueryBuilder\Queries\TermQuery::create('type', 'tshirt'),
\Spatie\ElasticsearchQueryBuilder\Aggregations\MaxAggregation::create('max_price', 'price')
);
```
#### `MaxAggregation`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-max-aggregation.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-max-aggregation.html)
```php
\Spatie\ElasticsearchQueryBuilder\Aggregations\MaxAggregation::create('max_price', 'price');
```
#### `MinAggregation`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-min-aggregation.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-min-aggregation.html)
```php
\Spatie\ElasticsearchQueryBuilder\Aggregations\MinAggregation::create('min_price', 'price');
```
#### `SumAggregation`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-sum-aggregation.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-sum-aggregation.html)
```php
\Spatie\ElasticsearchQueryBuilder\Aggregations\SumAggregation::create('sum_price', 'price');
```
#### `NestedAggregation`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-nested-aggregation.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-nested-aggregation.html)
```php
\Spatie\ElasticsearchQueryBuilder\Aggregations\NestedAggregation::create(
'resellers',
'resellers',
\Spatie\ElasticsearchQueryBuilder\Aggregations\MinAggregation::create('min_price', 'resellers.price'),
\Spatie\ElasticsearchQueryBuilder\Aggregations\MaxAggregation::create('max_price', 'resellers.price'),
);
```
#### `ReverseNestedAggregation`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-reverse-nested-aggregation.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-reverse-nested-aggregation.html)
```php
\Spatie\ElasticsearchQueryBuilder\Aggregations\ReverseNestedAggregation::create(
'name',
...$aggregations
);
```
#### `TermsAggregation`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html)
```php
\Spatie\ElasticsearchQueryBuilder\Aggregations\TermsAggregation::create(
'genres',
'genre'
)
->size(10)
->order(['_count' => 'asc'])
->missing('N/A')
->aggregation(/* $subAggregation */);
```
#### `TopHitsAggregation`
[https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-top-hits-aggregation.html](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-top-hits-aggregation.html)
```php
\Spatie\ElasticsearchQueryBuilder\Aggregations\TopHitsAggregation::create(
'top_sales_hits',
size: 10,
);
```
## Adding sorts
The `Builder` (and some aggregations) has a `addSort()` method that takes a `Sort` instance to sort the results. You can read more about how sorting works in [the ElasticSearch docs](https://www.elastic.co/guide/en/elasticsearch/reference/current/sort-search-results.html).
```php
use Spatie\ElasticsearchQueryBuilder\Sorts\Sort;
$builder
->addSort(Sort::create('age', Sort::DESC))
->addSort(
Sort::create('score', Sort::ASC)
->unmappedType('long')
->missing(0)
);
```
### Nested sort
[https://www.elastic.co/guide/en/elasticsearch/reference/current/sort-search-results.html#\_nested_sorting_examples](https://www.elastic.co/guide/en/elasticsearch/reference/current/sort-search-results.html#_nested_sorting_examples)
```php
use Spatie\ElasticsearchQueryBuilder\Sorts\NestedSort;
$builder
->addSort(
NestedSort::create('books', 'books.rating', NestedSort::ASC)
);
```
#### Nested sort with filter
```php
use Spatie\ElasticsearchQueryBuilder\Sorts\NestedSort;
use Spatie\ElasticsearchQueryBuilder\Queries\BoolQuery;
use Spatie\ElasticsearchQueryBuilder\Queries\TermQuery;
$builder
->addSort(
NestedSort::create(
'books',
'books.rating',
NestedSort::ASC
)->filter(BoolQuery::create()->add(TermQuery::create('books.category', 'comedy'))
);
```
## Retrieve specific fields
The `fields()` method can be used to request specific fields from the resulting documents without returning the entire `_source` entry. You can read more about the specifics of the fields parameter in [the ElasticSearch docs](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-fields.html).
```php
$builder->fields('user.id', 'http.*.status');
```
## Highlighting
The `highlight()` method can be used to add a highlight section to your query along the rules in [the ElasticSearch docs](https://www.elastic.co/guide/en/elasticsearch/reference/current/highlighting.html).
```php
$highlightSettings = [
'pre_tags' => [''],
'post_tags' => [''],
'fields' => [
'*' => (object) []
]
];
$builder->highlight($highlightSettings);
```
## Post filter
The `addPostFilterQuery()` method can be used to add a post_filter BoolQuery to your query along the rules in [the ElasticSearch docs](https://www.elastic.co/guide/en/elasticsearch/reference/current/filter-search-results.html#post-filter).
```php
use Spatie\ElasticsearchQueryBuilder\Queries\TermsQuery;
$builder->addPostFilterQuery(TermsQuery::create('user.id', ['flx', 'fly']));
```
## Pagination
Finally the `Builder` also features a `size()` and `from()` method for the corresponding ElasticSearch search parameters. These can be used to build a paginated search. Take a look the following example to get a rough idea:
```php
use Spatie\ElasticsearchQueryBuilder\Builder;
$pageSize = 100;
$pageNumber = $_GET['page'] ?? 1;
$pageResults = (new Builder(Elastic\Elasticsearch\ClientBuilder::create()))
->size($pageSize)
->from(($pageNumber - 1) * $pageSize)
->search();
```
## Multi-Search Query Builder
Elasticsearch provides a ["multi-search" API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-multi-search.html) that allows for multiple query bodies to be included in a single request.
Use the `MultiBuilder` class and [add builders](#add-builders) to add builders to your query request. The response will include a `responses` array of the query results, in the same order the requests are added. Use the `$multiBuilder->search()` to execute the queries, or `$multiBuilder->getPayload()` for the raw request payload.
```php
use Spatie\ElasticsearchQueryBuilder\MultiBuilder;
use Spatie\ElasticsearchQueryBuilder\Builder;
$client = Elastic\Elasticsearch\ClientBuilder::create();
$multiBuilder = (new MultiBuilder($client));
$multiBuilder->addBuilder(
(new Builder($client))->index('custom_index')->size(10)
);
// you can pass the index name to the addBuilder method second param
$multiBuilder->addBuilder(
(new Builder($client))->size(10)
'different_index'
);
$multiResults = $multiBuilder->search();
```
Returns the following response JSON shape:
```
{
"took": 2,
"responses": [
{... first query result ...},
{... second query result ...},
]
}
```
## Testing
```bash
composer test
```
## Changelog
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
## Contributing
Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details.
## Security Vulnerabilities
Please review [our security policy](../../security/policy) on how to report security vulnerabilities.
## Credits
- [Alex Vanderbist](https://github.com/alexvanderbist)
- [Ruben Van Assche](https://github.com/rubenvanassche)
- [All Contributors](../../contributors)
## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.