Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Elasticsearch Query Builder Laravel Package

spatie/elasticsearch-query-builder

Lightweight fluent PHP query builder for Elasticsearch. Build searches, filters, and aggregations with a clean API, then execute via the official client. Designed to pair with Spatie’s search-string parser; covers common use cases and is easy to extend.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:
    composer require spatie/elasticsearch-query-builder
    
  2. 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: Simple Search

$builder = new Builder($client)
    ->index('articles')
    ->addQuery(MatchQuery::create('title', 'laravel'))
    ->addQuery(RangeQuery::create('published_at')->gte('2023-01-01'))
    ->search();

Key Entry Points

  • Builder: Core class for constructing queries
  • Queries: Predefined query types (e.g., MatchQuery, RangeQuery)
  • Aggregations: For analytics (e.g., TermsAggregation, AvgAggregation)
  • Sorts: For ordering results

Implementation Patterns

Common Workflows

1. Search with Filtering

$builder = new Builder($client)
    ->index('users')
    ->addQuery(MatchQuery::create('name', 'john'), 'must') // Full-text search
    ->addQuery(TermQuery::create('status', 'active'), 'filter') // Non-scoring filter
    ->search();

2. Aggregations for Analytics

$builder = new Builder($client)
    ->index('orders')
    ->addAggregation(TermsAggregation::create('by_status', 'status'))
    ->addAggregation(AvgAggregation::create('avg_value', 'total'))
    ->search();

3. Nested Queries

$nestedQuery = NestedQuery::create('comments', MatchQuery::create('comments.text', 'great'))
    ->innerHits(InnerHits::create('top_comments')->size(2));

$builder = new Builder($client)
    ->index('posts')
    ->addQuery($nestedQuery)
    ->search();

4. Pagination

$builder = new Builder($client)
    ->index('products')
    ->from(10) // Skip first 10 results
    ->size(5)  // Limit to 5 results
    ->search();

5. Multi-Search Queries

use Spatie\ElasticsearchQueryBuilder\MultiBuilder;

$multiBuilder = new MultiBuilder($client);
$multiBuilder->addSearch(
    (new Builder($client))->index('users')->addQuery(MatchQuery::create('name', 'john'))
);
$multiBuilder->addSearch(
    (new Builder($client))->index('products')->addQuery(MatchQuery::create('name', 'laptop'))
);
$results = $multiBuilder->search();

Integration Tips

  • Laravel Service Provider: Register the Elasticsearch client as a singleton:
    $this->app->singleton(ElasticsearchClient::class, function () {
        return Elastic\Elasticsearch\ClientBuilder::create()->build();
    });
    
  • Repository Pattern: Wrap the builder in a repository for cleaner usage:
    class ProductRepository {
        public function __construct(private Builder $builder) {}
    
        public function search(string $query): array {
            return $this->builder
                ->index('products')
                ->addQuery(MatchQuery::create('name', $query))
                ->search();
        }
    }
    
  • Dynamic Field Mapping: Use fields() to control returned fields:
    $builder->fields(['name', 'price', 'description']);
    

Gotchas and Tips

Pitfalls

  1. Boolean Query Logic:

    • must (AND), must_not (NOT), should (OR), filter (non-scoring).
    • Forgetting to specify the occurrence type defaults to must, which may not be intended.
    // ❌ Silent failure: defaults to 'must'
    $builder->addQuery(MatchQuery::create('name', 'john'));
    
    // ✅ Explicit
    $builder->addQuery(MatchQuery::create('name', 'john'), 'must');
    
  2. Aggregation Naming Conflicts:

    • Aggregation names must be unique. Reusing names will overwrite previous aggregations.
    // ❌ Overwrites previous 'genres' aggregation
    $builder->addAggregation(TermsAggregation::create('genres', 'genre'));
    $builder->addAggregation(TermsAggregation::create('genres', 'category'));
    
  3. Nested Query Requirements:

    • The mapped field must be of type nested. Elasticsearch will throw an error otherwise.
    // ❌ Fails if 'comments' is not a nested field
    $builder->addQuery(NestedQuery::create('comments', MatchQuery::create('text', 'great')));
    
  4. Pagination Limits:

    • Elasticsearch has a default from/size limit (typically 10,000). Exceeding this requires search_after for deep pagination.
    // ❌ May fail with "Search Too Large" error
    $builder->from(10000)->size(10000)->search();
    
  5. Fuzzy Matching Performance:

    • High fuzziness values (e.g., fuzziness: 3) can significantly slow down queries.
    // ⚠️ Expensive for large datasets
    $builder->addQuery(MatchQuery::create('name', 'john', fuzziness: 3));
    

Debugging Tips

  1. Inspect Raw Payload:
    $payload = $builder->getPayload();
    // Log or dump $payload to verify the query structure
    
  2. Enable Query Logging: Configure the Elasticsearch client to log queries:
    $client = Elastic\Elasticsearch\ClientBuilder::create()
        ->setLogger(new \Monolog\Logger('elasticsearch'))
        ->build();
    
  3. Use explain for Debugging:
    $builder->addQuery(MatchQuery::create('name', 'john'))->explain();
    

Extension Points

  1. Custom Queries: Extend the Query abstract class to create custom query types:
    class CustomQuery extends Query {
        public static function create(string $field, mixed $value): self {
            return new self($field, $value);
        }
    
        public function build(): array {
            return ['custom' => ['field' => $this->field, 'value' => $this->value]];
        }
    }
    
  2. Dynamic Aggregations: Use closures to build aggregations dynamically:
    $builder->addAggregation(function (AggregationBuilder $aggregationBuilder) {
        return $aggregationBuilder->terms('dynamic_agg', 'field_' . $this->dynamicField);
    });
    
  3. Override Default Behavior: Extend the Builder class to modify default settings:
    class CustomBuilder extends Builder {
        public function __construct(Client $client) {
            parent::__construct($client);
            $this->defaultSize = 20; // Override default size
        }
    }
    

Configuration Quirks

  1. Index Aliases: The builder does not resolve index aliases by default. Use the indices option if needed:
    $builder->indices(['alias_name']);
    
  2. Routing: Specify routing parameters for sharded indices:
    $builder->routing('shard_key_value');
    
  3. Scroll API: For large datasets, use the scroll method instead of search:
    $scroll = $builder->scroll('1m')->search();
    

Performance Tips

  1. Use filter for Non-Scoring Queries:
    // ✅ Faster for filtering (non-scoring)
    $builder->addQuery(TermQuery::create('status', 'active'), 'filter');
    
  2. Limit Fields: Reduce payload size by specifying only needed fields:
    $builder->fields(['id', 'name', 'price']);
    
  3. Avoid wildcard Queries: Wildcard queries (*) are resource-intensive. Use keyword subfields or ngram analyzers instead.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope
anil/file-picker
broqit/fields-ai