bonu/php-elasticsearch-builder 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

bonu/php-elasticsearch-builder

Composer 安装命令:

composer require bonu/php-elasticsearch-builder

包简介

Elasticsearch query builder for PHP

README 文档

README

PHPUnit Latest Stable Version License PHP Version

A clean, fluent, immutable, and type-safe query builder for Elasticsearch - built from the ground up to work seamlessly with the official Elasticsearch PHP client.

No extra dependencies. No magic. Just expressive, readable, and maintainable Elasticsearch queries in PHP.

use Elastic\Elasticsearch\ClientBuilder;
use Bonu\ElasticsearchBuilder\QueryBuilder;
use Bonu\ElasticsearchBuilder\Query\TermQuery;
use Bonu\ElasticsearchBuilder\Query\BoolQuery;
use Bonu\ElasticsearchBuilder\Query\MatchQuery;

$builder = new QueryBuilder('products')
    ->query(new TermQuery('ean', 'foo_bar_123')->boost(12))
    ->query(new BoolQuery()
        ->should(new MatchQuery('name', 'foo'))
        ->should(new MatchQuery('description', 'bar'))
        ->boost(5)
    )
    ->size(20);

$client = ClientBuilder::create()->build();
$products = $client->search($builder->build());

Features

  • Fully fluent & chainable API
  • Zero dependencies beyond the official Elasticsearch PHP SDK
  • Easy creation of reusable composite queries
  • 100% type-hinted and IDE-friendly

Requirements

  • PHP ≥ 8.4

Installation

composer require bonu/php-elasticsearch-builder

Using query builder

The Bonu\ElasticsearchBuilder\QueryBuilder class provides a fluent interface for building Elasticsearch queries. Constructor of this class accepts a single argument - name of the index to query, which may be used for searching in specific index.

Out of box it supports:

  • Chaining queries using query() method
  • Chaining aggregations using aggregation() method
  • Configuring pagination of results using from() and size() methods

Queries

Caution

Queries are immutable.

This package comes with a set of ready-to-use queries which is documented below.

It is also possible to create reusable composite queries using abstract Bonu\ElasticsearchBuilder\Query\CompositeQuery class.

Example of composite query:

use Bonu\ElasticsearchBuilder\Query\BoolQuery;
use Bonu\ElasticsearchBuilder\Query\TermQuery;
use Bonu\ElasticsearchBuilder\Query\CompositeQuery;

class PubliclyVisibleProductsQuery extends CompositeQuery
{
    /**
     * @inheritDoc
     */
    public function query(): QueryInterface
    {
        return new BoolQuery()
            ->must(new TermQuery('is_active', true))
            ->mustNot(new TermQuery('is_out_of_stock', false));
    }
}

$builder = new QueryBuilder('products')
    ->query(new PubliclyVisibleProductsQuery());

TermQuery

https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-term-query

use Bonu\ElasticsearchBuilder\Query\TermQuery;

new TermQuery('field', 'value')->boost(10)

TermsQuery

https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-terms-query

use Bonu\ElasticsearchBuilder\Query\TermsQuery;

new TermsQuery('status', ['active', 'pending'])->boost(5)

ExistsQuery

https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-exists-query

use Bonu\ElasticsearchBuilder\Query\ExistsQuery;

new ExistsQuery('field')

MatchQuery

https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-match-query

use Bonu\ElasticsearchBuilder\Query\MatchQuery;

// Default operator is OR
new MatchQuery('field', 'some text')
    ->boost(2)
    ->analyzer('standard')

// With AND operator
new MatchQuery('field', 'some text', MatchQuery::OPERATOR_AND)

MatchPhraseQuery

https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-match-query-phrase

use Bonu\ElasticsearchBuilder\Query\MatchPhraseQuery;

// Optional third argument is slop
new MatchPhraseQuery('field', 'exact phrase', 2)
    ->boost(1.5)
    ->analyzer('standard')

BoolQuery

https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-bool-query

use Bonu\ElasticsearchBuilder\Query\BoolQuery;
use Bonu\ElasticsearchBuilder\Query\TermQuery;
use Bonu\ElasticsearchBuilder\Query\MatchQuery;

new BoolQuery()
    ->must(new TermQuery('status', 'active'))
    ->filter(new TermQuery('stock', 1))
    ->should(new MatchQuery('title', 'awesome product'))
    ->mustNot(new TermQuery('blocked', true))
    ->boost(3)

NestedQuery

https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-nested-query

use Bonu\ElasticsearchBuilder\Query\NestedQuery;
use Bonu\ElasticsearchBuilder\Query\MatchQuery;

// Query nested field path, inner query must be provided via ->query()
new NestedQuery('variants')
    ->query(new MatchQuery('variants.name', 'red'))

RangeQuery

https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-range-query

Range queries can be used for filtering by multiple data types. For this reason, each data type has its own query class to fully support type-hinting.

use Bonu\ElasticsearchBuilder\Query\NumericRangeQuery;
use Bonu\ElasticsearchBuilder\Query\DatetimeRangeQuery;

new NumericRangeQuery('price', gte: 100)
    ->boost(10);

new DatetimeRangeQuery('created_at', lt: date('Y-m-d'), format: 'yyyy-MM-dd', timeZone: 'Europe/Prague')
    ->boost(20);

Aggregations

Caution

Aggregations are immutable.

Similar to queries, it is also possible to create reusable composite aggregations using abstract Bonu\ElasticsearchBuilder\Aggregation\CompositeAggregation class.

Example of composite aggregation:

use Bonu\ElasticsearchBuilder\Aggregation\TermsAggregation;
use Bonu\ElasticsearchBuilder\Aggregation\NestedAggregation;
use Bonu\ElasticsearchBuilder\Aggregation\CompositeAggregation;

class CategoryBrandAggregation extends CompositeAggregation
{
    /**
     * @param string|\Stringable $name
     */
    public function __construct(
        private readonly string | Stringable $name,
    ) {}

    /**
     * @inheritDoc
     */
    public function aggregation(): AggregationInterface
    {
        return new NestedAggregation($this->name, 'products')
            ->aggregation(new TermsAggregation('by_brand', 'products.brand_id'));
    }
}

ContainerAggregation

Container aggregation is used to group sub-aggregations. It requires either global or at least one filter (query) to be set, but not both.

use Bonu\ElasticsearchBuilder\Query\TermQuery;
use Bonu\ElasticsearchBuilder\Aggregation\TermsAggregation;
use Bonu\ElasticsearchBuilder\Aggregation\ContainerAggregation;

// Container with a filter
new ContainerAggregation('my_container')
    ->query(new TermQuery('status', 'active'))
    ->aggregation(new TermsAggregation('by_brand', 'brand.keyword'));

// Global container
new ContainerAggregation('global_container')
    ->asGlobal()
    ->aggregation(new TermsAggregation('all_brands', 'brand.keyword'));

TermsAggregation

https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-terms-aggregation

use Bonu\ElasticsearchBuilder\Aggregation\TermsAggregation;
use Bonu\ElasticsearchBuilder\Query\TermQuery;

// Top 10 brands, filtered to active products
new TermsAggregation('by_brand', 'brand.keyword')
    ->size(10)
    ->query(new TermQuery('status', 'active'));

// Make the aggregation global (ignores the top-level query)
new TermsAggregation('all_categories', 'category.keyword')
    ->asGlobal();

StatsAggregation

https://www.elastic.co/docs/reference/aggregations/search-aggregations-metrics-stats-aggregation

use Bonu\ElasticsearchBuilder\Aggregation\StatsAggregation;
use Bonu\ElasticsearchBuilder\Query\TermQuery;

// Basic stats for the price field, filtered by currency
new StatsAggregation('price_stats', 'price')
    ->query(new TermQuery('currency', 'USD'));

// Make the aggregation global (ignores the top-level query)
new StatsAggregation('global_price_stats', 'price')
    ->asGlobal();

NestedAggregation

https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-nested-aggregation

use Bonu\ElasticsearchBuilder\Aggregation\NestedAggregation;

new NestedAggregation('categories', 'products')
    ->aggregation(new StatsAggregation('product_price', 'products.price'))

MultiTermsAggregation

https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-multi-terms-aggregation

use Bonu\ElasticsearchBuilder\Aggregation\MultiTermsAggregation;

new MultiTermsAggregation('foo', ['product', 'category'])
    ->size(10)
    ->query(new TermQuery('status', 'active'));

HistogramAggregation

https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-histogram-aggregation

use Bonu\ElasticsearchBuilder\Aggregation\HistogramAggregation;

// Prices in $10 intervals
new HistogramAggregation('price_histogram', 'price', 10);

// With custom interval and min_doc_count
new HistogramAggregation('price_histogram', 'price', 50, 1);

<<<<<<< HEAD

SumAggregation

https://www.elastic.co/docs/reference/aggregations/search-aggregations-metrics-sum-aggregation

use Bonu\ElasticsearchBuilder\Aggregation\SumAggregation;
use Bonu\ElasticsearchBuilder\Query\TermQuery;

// Sum of all prices
new SumAggregation('total_price', 'price');

// Filtered sum
new SumAggregation('active_total', 'price')
    ->query(new TermQuery('status', 'active'));

// Global sum (ignores the top-level query)
new SumAggregation('global_total', 'price')
    ->asGlobal();

CardinalityAggregation

https://www.elastic.co/docs/reference/aggregations/search-aggregations-metrics-cardinality-aggregation

use Bonu\ElasticsearchBuilder\Aggregation\CardinalityAggregation;

// Count unique values
new CardinalityAggregation('unique_brands', 'brand.keyword');

// With precision threshold for better accuracy on high-cardinality fields
new CardinalityAggregation('unique_brands', 'brand.keyword', 1000);

// Filtered cardinality
new CardinalityAggregation('active_unique_brands', 'brand.keyword')
    ->query(new TermQuery('status', 'active'));

DateHistogramAggregation

https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-datehistogram-aggregation

use Bonu\ElasticsearchBuilder\Aggregation\DateHistogramAggregation;

// Monthly buckets using calendar interval
new DateHistogramAggregation('sales_over_time', 'date', calendarInterval: 'month');

// Fixed 30-day intervals with date format
new DateHistogramAggregation('monthly_sales', 'date', fixedInterval: '30d', format: 'yyyy-MM-dd');

// With all options: calendar interval, min_doc_count, format, time zone, offset
new DateHistogramAggregation(
    'hourly_activity',
    'timestamp',
    calendarInterval: 'hour',
    minDocCount: 1,
    format: 'yyyy-MM-dd HH:mm',
    timeZone: 'Europe/Prague',
    offset: '+6h',
);

Sorts

FieldSort

use Bonu\ElasticsearchBuilder\Sort\FieldSort;
use Bonu\ElasticsearchBuilder\Sort\SortDirectionEnum;

new FieldSort('my_field', SortDirectionEnum::ASC)

ScoreSort

use Bonu\ElasticsearchBuilder\Sort\ScoreSort;
use Bonu\ElasticsearchBuilder\Sort\SortDirectionEnum;

new ScoreSort(SortDirectionEnum::DESC)

License

This package is licensed under the MIT License.

bonu/php-elasticsearch-builder 适用场景与选型建议

bonu/php-elasticsearch-builder 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 470 次下载、GitHub Stars 达 65, 最近一次更新时间为 2025 年 11 月 30 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 bonu/php-elasticsearch-builder 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 bonu/php-elasticsearch-builder 我们能提供哪些服务?
定制开发 / 二次开发

基于 bonu/php-elasticsearch-builder 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 470
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 65
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 65
  • Watchers: 0
  • Forks: 2
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-11-30