spatie/elasticsearch-query-builder
Lightweight fluent PHP query builder for Elasticsearch. Build and execute searches with an ergonomic Builder API: set index, add queries, aggregations, sorting, and filters, then run search() against the official Elasticsearch PHP client.
Installation:
composer require spatie/elasticsearch-query-builder
Ensure you have the official Elasticsearch PHP client (elasticsearch/elasticsearch) installed.
Basic Query:
use Spatie\ElasticsearchQueryBuilder\Builder;
use Spatie\ElasticsearchQueryBuilder\Queries\MatchQuery;
$client = Elastic\Elasticsearch\ClientBuilder::create()->build();
$results = (new Builder($client))
->index('products')
->addQuery(MatchQuery::create('name', 'laptop'))
->search();
First Use Case:
title).$posts = (new Builder($client))
->index('posts')
->addQuery(MatchQuery::create('title', 'Laravel'))
->search();
Builder class: Central class for constructing queries.Queries namespace: Predefined query types (e.g., MatchQuery, BoolQuery).Aggregations namespace: Aggregation functions (e.g., MaxAggregation, AvgAggregation).$results = (new Builder($client))
->index('users')
->addQuery(MatchQuery::create('name', 'John'))
->addFilter(TermQuery::create('status', 'active')) // Filter (not scored)
->search();
Combine multiple conditions with logical operators:
$boolQuery = BoolQuery::create()
->should(MatchQuery::create('title', 'Laravel'))
->must(TermQuery::create('category', 'tutorial'))
->mustNot(TermQuery::create('published_at', null));
$results = (new Builder($client))
->index('posts')
->addQuery($boolQuery)
->search();
Group and analyze data:
$results = (new Builder($client))
->index('orders')
->addAggregation(AvgAggregation::create('amount'))
->addAggregation(TermsAggregation::create('status'))
->search();
$results = (new Builder($client))
->index('products')
->addQuery(MatchAllQuery::create())
->sort('price', 'asc')
->from(0)
->size(10)
->search();
Use keyword or text fields dynamically:
$query = MatchQuery::create('name.keyword', 'John Doe'); // Exact match
Service Provider:
Bind the Builder to the container for dependency injection:
$this->app->bind(Builder::class, function ($app) {
return new Builder($app->make(Elastic\Elasticsearch\Client::class));
});
Eloquent-like Usage: Create a facade or helper to abstract the builder:
class Elasticsearch {
public static function search(string $index, array $queries = []): array {
return (new Builder(app('elasticsearch')))->index($index)
->addQueries($queries)
->search();
}
}
$mockClient = Mockery::mock(Elastic\Elasticsearch\Client::class);
$builder = new Builder($mockClient);
Bulk Operations:
Use the bulk method for large datasets:
$builder->bulk($operations);
Scroll API:
For deep pagination, use the scroll method:
$scroll = $builder->scroll('1m')->search();
Field Mapping Mismatches:
exists queries to check:
$builder->addFilter(ExistsQuery::create('field_name'));
Fuzziness Overuse:
fuzziness) can degrade performance. Limit to 2 or 3 for most use cases.Aggregation Limits:
TermsAggregation::create('field')->size(1000);
Boolean Query Logic:
must = AND, should = OR, mustNot = NOT. Misuse can lead to unexpected results.Scroll vs. Search After:
scroll for large datasets (but consumes cluster resources). Prefer search_after for pagination:
$builder->searchAfter($lastSortValues);
Raw Query Inspection: Access the underlying query before execution:
$query = $builder->getQuery();
dd($query); // Inspect the raw Elasticsearch query
Error Handling: Wrap searches in try-catch:
try {
$results = $builder->search();
} catch (\Elastic\Elasticsearch\Exception\ClientResponseException $e) {
report($e);
return back()->withError('Search failed');
}
Logging: Enable Elasticsearch client logging:
$client = Elastic\Elasticsearch\ClientBuilder::create()
->setHosts(['http://localhost:9200'])
->setLogger(new \Monolog\Logger('elasticsearch'))
->build();
Reusable Query Builders: Create static methods for common queries:
class PostQueryBuilder {
public static function searchByTitle(string $title): Builder {
return (new Builder(app('elasticsearch')))
->index('posts')
->addQuery(MatchQuery::create('title', $title));
}
}
Dynamic Index Selection: Use environment variables or config for index names:
$builder->index(config('elasticsearch.indices.posts'));
Highlighting Results: Enable highlighting for snippets:
$builder->highlight('title', ['pre_tags' => ['<em>'], 'post_tags' => ['</em>']]);
Custom Analyzers: Pass analyzer parameters directly:
MatchQuery::create('description', 'search term')->analyzer('custom_analyzer');
Versioning:
Use the _version parameter for optimistic concurrency:
$builder->version(2)->update();
Extending the Builder:
Add custom methods to the Builder class:
class CustomBuilder extends Builder {
public function searchByTags(array $tags): self {
return $this->addQuery(TermsQuery::create('tags', $tags));
}
}
Handling Nulls: Explicitly filter out null values:
$builder->addFilter(BoolQuery::create()->mustNot(ExistsQuery::create('field')));
Performance Tuning:
filter instead of query for non-scored filters._source fields to reduce payload size:
$builder->source(['title', 'price']);
How can I help you explore Laravel packages today?