定制 ensi/laravel-elastic-query 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

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

ensi/laravel-elastic-query

Composer 安装命令:

composer require ensi/laravel-elastic-query

包简介

laravel elastic query

README 文档

README

Latest Version on Packagist Tests Total Downloads

Working with Elasticsearch in an Eloquent-like fashion.

Installation

You can install the package via composer:

composer require ensi/laravel-elastic-query

Attention: Synonyms API methods require Elasticsearch 8.10+

Publish config file like this:

php artisan vendor:publish --provider="Ensi\LaravelElasticQuery\ElasticQueryServiceProvider"

Set ELASTICSEARCH_HOSTS in your .env file. , can be used as a delimeter.

Version Compatibility

Laravel Elastic Query Laravel PHP Elasticsearch
^0.1.0 ^8.0 ^8.0 7.*
^0.2.0 ^8.0 ^8.0 7.*
^0.3.0 ^8.0 ^8.0 7.*
^0.3.2 ^8.0 || ^9.0 ^8.0 7.*
^7.x (see details) ^9.0 || ^10.0 || ^11.0 ^8.1 7.*
^8.0.0 ^8.0 || ^9.0 ^8.0 8.*
^8.0.13 ^8.0 || ^9.0 || ^10.0 ^8.0 8.*
^8.0.23 ^8.0 || ^9.0 || ^10.0 || ^11.0 ^8.0 8.*
^8.1.0 ^9.0 || ^10.0 || ^11.0 ^8.1 8.*
^8.2.3 ^9.0 || ^10.0 || ^11.0 || ^12.0 ^8.1 8.*
^8.2.13 ^9.0 || ^10.0 || ^11.0 || ^12.0 ^8.1 8.10+

Basic usage

Let's create and index class. It's someting like Eloquent model.

use Ensi\LaravelElasticQuery\ElasticIndex;

class ProductsIndex extends ElasticIndex
{
    protected string $name = 'test_products';
    protected string $tiebreaker = 'product_id';
}

You should set a unique in document attribute name in $tiebreaker. It is used as an additional sort in search_after

Now we can get some documents

$searchQuery = ProductsIndex::query();

$hits = $searchQuery
             ->where('rating', '>=', 5)
             ->whereDoesntHave('offers', fn(BoolQuery $query) => $query->where('seller_id', 10)->where('active', false))
             ->sortBy('rating', 'desc')
             ->sortByNested('offers', fn(SortableQuery $query) => $query->where('active', true)->sortBy('price', mode: 'min'))
             ->take(25)
             ->get();

Filtering

$searchQuery->where('field', 'value');
$searchQuery->where('field', '>', 'value'); // supported operators: `=` `!=` `>` `<` `>=` `<=`
$searchQuery->whereNot('field', 'value'); // equals `where('field', '!=', 'value')`
$searchQuery->whereIn('field', ['value1', 'value2']);
$searchQuery->whereNotIn('field', ['value1', 'value2']);
$searchQuery->whereNull('field');
$searchQuery->whereNotNull('field');
$searchQuery->whereHas('nested_field', fn(BoolQuery $subQuery) => $subQuery->where('field_in_nested', 'value'));
$searchQuery->whereDoesntHave(
    'nested_field',
    function (BoolQuery $subQuery) {
        $subQuery->whereHas('nested_field', fn(BoolQuery $subQuery2) => $subQuery2->whereNot('field', 'value'));
    }
);

nested_field must have nested type. Subqueries cannot use fields of main document only subdocument.

Full text search

$searchQuery->whereMatch('field_one', 'query string');
$searchQuery->whereMultiMatch(['field_one^3', 'field_two'], 'query string', MatchType::MOST_FIELDS);
$searchQuery->whereMultiMatch([], 'query string');  // search by all text fields

field_one and field_two must be of text type. If no type is given, the MatchType::BEST_FIELDS is used.

Sorting

$searchQuery->sortBy('field', SortOrder::DESC, SortMode::MAX, MissingValuesMode::FIRST); // field is from main document
$searchQuery->sortByNested(
    'nested_field',
    fn(SortableQuery $subQuery) => $subQuery->where('field_in_nested', 'value')->sortBy('field')
);

Second attribute is a direction. It supports asc and desc values. Defaults to asc.
Third attribute - sorting type. List of supporting types: min, max, avg, sum, median. Defaults to min.

There are also dedicated sort methods for each sort type.

$searchQuery->minSortBy('field', 'asc');
$searchQuery->maxSortBy('field', 'asc');
$searchQuery->avgSortBy('field', 'asc');
$searchQuery->sumSortBy('field', 'asc');
$searchQuery->medianSortBy('field', 'asc');

Pinned query

Promotes selected documents to rank higher than those matching a given query. This feature is typically used to guide searchers to curated documents that are promoted over and above any "organic" matches for a search. The promoted or "pinned" documents are identified using the document IDs stored in the _id field.

$searchQuery->pinned(['doc-3', 'doc-1', 'doc-2']);

Pagination

Offset Pagination

$page = $searchQuery->paginate(15, 45);

Offset pagination returns total documents count as total and current position as size/offset.

Cursor pagination

$page = $searchQuery->cursorPaginate(10);
$pageNext = $searchQuery->cursorPaginate(10, $page->next);

current, next, previous is returned in this case instead of total, size and offset. You can check Laravel docs for more info about cursor pagination.

Aggregation

Aggregaction queries can be created like this

$aggQuery = ProductsIndex::aggregate();

/** @var \Illuminate\Support\Collection $aggs */
$aggs = $aggQuery
            ->where('active', true)
            ->terms('codes', 'code')
            ->count('product_count', 'product_id')
            ->nested(
                'offers',
                fn(AggregationsBuilder $builder) => $builder->where('seller_id', 10)->minmax('price', 'price')
            );
            

Type of $aggs->price is MinMax. Type of $aggs->codes is BucketCollection. Aggregate names must be unique for whole query.

Aggregate types

Get all variants of attribute values:

$aggQuery->terms('agg_name', 'field', 25);

Get min and max attribute values. E.g for date:

$aggQuery->minmax('agg_name', 'field');

Get count unique attribute values:

$aggQuery->count('agg_name', 'field');

Aggregation plays nice with nested documents.

$aggQuery->nested('nested_field', function (AggregationsBuilder $builder) {
    $builder->terms('name', 'field_in_nested');
});

There is also a special virtual composite aggregate on the root level. You can set special conditions using it.

$aggQuery->composite(function (AggregationsBuilder $builder) {
    $builder->where('field', 'value')
        ->whereHas('nested_field', fn(BoolQuery $query) => $query->where('field_in_nested', 'value2'))
        ->terms('field1', 'agg_name1')
        ->minmax('field2', 'agg_name2');
});

Suggesting

Suggest queries can be created like this

$sugQuery = ProductsIndex::suggest();

/** @var \Illuminate\Support\Collection $suggests */
$suggests = $sugQuery->phrase('suggestName', 'name.trigram')
    ->text('glves')
    ->size(1)
    ->shardSize(3)
    ->get();
            

Global suggest text

User can set global text like this

$sugQuery = ProductsIndex::suggest()->text('glves');

$sugQuery->phrase('suggestName1', 'name.trigram')->size(1)->shardSize(3);
    
$sugQuery->phrase('suggestName2', 'name.trigram');
    
/** @var \Illuminate\Support\Collection $suggests */
$suggests = $sugQuery->get();
            

Suggester types

Term suggester:

$aggQuery->term('suggestName', 'name.trigram')->text('glves')->...->get();

Phrase Suggester:

$aggQuery->phrase('suggestName', 'name.trigram')->text('glves')->...->get();

Additional methods

$index = new ProductsIndex();

$index->isCreated(); // Check if index are created 
$index->create(); // Create index with structure from settings() method
$index->bulk(); // Send bulk request
$index->get(); // Send get request
$index->documentDelete(); // Send documentDelete request
$index->deleteByQuery(); // Send deleteByQuery request
$index->termvectors(); // Send termvectors request

$index->catIndices();
$index->indicesDelete();
$index->indicesRefresh();
$index->indicesReloadSearchAnalyzers();

Synonyms API

Synonyms API methods are available through the ElasticQuery facade.

These methods require Elasticsearch 8.10+.

Get all synonym sets

$sets = ElasticQuery::getSynonymsSets();

$sets = ElasticQuery::getSynonymsSets(from: 0, size: 20);

Get a synonym set

$set = ElasticQuery::getSynonymSet('products-synonyms');

Create or update a synonym set

$result = ElasticQuery::putSynonymSet('products-synonyms', [
    [
        'id' => 'brand-rule',
        'synonyms' => 'iphone, i-phone',
    ],
    [
        'id' => 'tv-rule',
        'synonyms' => 'tv, television',
    ],
]);

Delete a synonym set

$result = ElasticQuery::deleteSynonymSet('products-synonyms');

Get a synonym rule

$rule = ElasticQuery::getSynonymRule('products-synonyms', 'brand-rule');

Create or update a synonym rule

$result = ElasticQuery::putSynonymRule(
    'products-synonyms',
    'brand-rule',
    'iphone, i-phone'
);

Delete a synonym rule

$result = ElasticQuery::deleteSynonymRule('products-synonyms', 'brand-rule');

Query Log

Just like Eloquent ElasticQuery has its own query log, but you need to enable it manually Each message contains indexName, query and timestamp

ElasticQuery::enableQueryLog();

/** @var \Illuminate\Support\Collection|Ensi\LaravelElasticQuery\Debug\QueryLogRecord[] $records */
$records = ElasticQuery::getQueryLog();

ElasticQuery::disableQueryLog();

Environment Variables

Below see the environment variables that you can configure with the default values, Hosts should be comma seperated string of hosts with protocol prefix and port suffix, e.g. http://localhost:9200,http://localhost:9201

 ELASTICSEARCH_HOSTS=https://localhost:9200'
 ELASTICSEARCH_RETRIES=2
 ELASTICSEARCH_USERNAME=admin
 ELASTICSEARCH_PASSWORD=admin
 ELASTICSEARCH_SSL_VERIFICATION=true,

Async Usage

All methods can return a Promise.
To enable this, you will need to add http_async_client to your config and then execute ElasticSearch::getClient()->setAsync(true).
To disable: ElasticQuery::getClient()->setAsync(false).

For example:

laravel-elastic-query.php:

return [
    'connection' => [
    
        // ..

        'http_async_client' => [HttpClientOptionsBuilder::class, 'getAsyncClient'],
    ],
];

HttpClientOptionsBuilder:

use Http\Adapter\Guzzle7\Client as GuzzleAdapter;
use Http\Client\HttpAsyncClient;

class HttpClientOptionsBuilder
{
    public static function getAsyncClient(): HttpAsyncClient
    {
        return GuzzleAdapter::createWithConfig([]);
    }
}

Action:

use Ensi\LaravelElasticQuery\ElasticQuery;

ElasticQuery::getClient()->setAsync(true);

// With async
$promises = [
    'key1' => FirstIndex::query()->get(),
    'key2' => FirstIndex::suggest()->paginate(/* ... */),
];

$results = [];
foreach ($promises as $key => $promise) {
    $results[$key] = $promise->wait();
}

$firstResponse = $results['key1'];

ElasticQuery::getClient()->setAsync(false);

// Without async
$firstResponse = FirstIndex::query()->get()

Elasticsearch 7 and 8 support.

Due to the incompatibility of clients for Elasticsearch 7 and 8, separate releases will be created for these versions. Development for each version is carried out in the corresponding branch.

To make changes to version 7, you need to create a task branch based on v7 and make a pull request to it. For version 8 it is similar, but based on the v8 branch.

Contributing

Please see CONTRIBUTING for details.

Testing

  1. composer install
  2. start Elasticsearch in your preferred way
  3. if you need change ELASTICSEARCH_HOSTS, copy phpunit.xml.dist to phpunit.xml and fill value
  4. composer test

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

License

The MIT License (MIT). Please see License File for more information.

ensi/laravel-elastic-query 适用场景与选型建议

ensi/laravel-elastic-query 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 20.12k 次下载、GitHub Stars 达 9, 最近一次更新时间为 2021 年 10 月 05 日, 在 PHP 生态内属于活跃度较高的组件。

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

围绕 ensi/laravel-elastic-query 我们能提供哪些服务?
定制开发 / 二次开发

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

BUG 修复 & 性能优化

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

项目外包 & 长期维护

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

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

统计信息

  • 总下载量: 20.12k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 9
  • 点击次数: 6
  • 依赖项目数: 1
  • 推荐数: 0

GitHub 信息

  • Stars: 9
  • Watchers: 2
  • Forks: 7
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2021-10-05