alirzaj / laravel-elasticsearch-builder
elasticsearch数据库的查询构建器
Requires
- php: ^8.1|^8.2
- elasticsearch/elasticsearch: ^8.0
- illuminate/contracts: ^10.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.1
- nunomaduro/collision: ^6
- nunomaduro/mock-final-classes: ^1.2
- orchestra/testbench: ^8
- pestphp/pest: ^1.18
- pestphp/pest-plugin-laravel: ^1.1
- vimeo/psalm: ^4.8
This package is auto-updated.
Last update: 2024-09-10 16:41:07 UTC
README
此包可以为elasticsearch数据库构建查询,并提供了一种简单的方式来将Eloquent模型添加到elasticsearch索引中。
它并不试图提供类似于Eloquent的API来与elasticsearch交互。相反,它将提供与在elasticsearch中编写查询类似的方法,但以一种更面向对象和更优雅的方式。
此包包含我在项目中需要的查询。所以如果你觉得需要某种方法或功能,请提出问题,我将尽快实现。
- elasticsearch数据库的查询构建器
- 安装
- 定义索引
- 创建索引
- 删除索引
- 配置
- 使模型可搜索
- 不使用Eloquent模型和可搜索特性索引文档
- 批量索引文档
- 根据条件更新文档
- 向嵌套字段添加项目
- 向数组字段添加项目
- 根据条件更新文档的嵌套字段项目
- 根据条件更新所有文档的嵌套字段项目
- 从特定文档的嵌套字段中删除项目
- 根据条件从嵌套字段中删除项目
- 删除文档
- 删除满足某些条件的所有文档
- 查询索引
- 将索引包含在查询中
- 提高某些索引的得分
- 确定搜索类型
- 通过其ID查找文档
- 匹配
- 匹配全部
- 多匹配
- 嵌套
- 存在
- 布尔
- 项
- 项集
- 范围
- 最大值
- 聚合(aggs)
- 处理数组字段
- 获取结果
- 确定结果的大小限制
- 确定获取结果时的from选项(分页)
- 选择特定字段
- 根据条件构建查询
- 调试
- 使用低级elasticsearch客户端
- 记录
- 测试助手
- 致谢
- 许可
安装
要安装此包,请通过composer要求
composer require alirzaj/laravel-elasticsearch-builder
使用
定义索引
首先,您需要定义索引类。索引类必须扩展 Alirzaj\ElasticsearchBuilder\Index 类。以下是一个示例
<?php namespace Alirzaj\ElasticsearchBuilder\Tests\Indices; use Alirzaj\ElasticsearchBuilder\Index; class Users extends Index { public string $name = 'users_index'; public array $propertyTypes = [ 'text' => 'text', 'user_id' => 'keyword', 'ip' => 'ip', 'created_at' => 'date', ]; public array $fields = [ 'text' => [ 'hashtags' => [ 'type' => 'text', 'analyzer' => 'hashtag', ], ], ]; public array $dateFormats = [ 'created_at' => 'strict_date_optional_time||strict_date_optional_time_nanos||yyyy-MM-dd HH:mm:ss', ]; public array $analyzers = [ 'hashtag' => [ 'type' => 'custom', 'tokenizer' => 'hashtag_tokenizer', 'filter' => ['lowercase'], ], 'hashtag_2' => [ 'type' => 'custom', 'tokenizer' => 'hashtag_tokenizer', 'filter' => ['lowercase'], ], ]; public array $tokenizers = [ 'hashtag_tokenizer' => [ 'type' => 'pattern', 'pattern' => '#\S+', 'group' => 0, ], ]; public array $propertyAnalyzers = [ 'text' => 'hashtag', ]; public array $searchAnalyzers = [ 'text' => 'hashtag_2', ]; public array $normalizers = [ 'my_normalizer' => [ 'type' => 'custom', 'char_filter' => ['special_character_strip'], 'filter' => ['lowercase',] ], ]; public array $propertyNormalizers = [ 'ip' => 'my_normalizer' ]; public array $characterFilters = [ 'special_character_strip' => [ 'type' => 'pattern_replace', 'pattern' => '[._]', ], ]; public array $tokenFilters = [ '4_7_edge_ngram' => [ 'min_gram' => '4', 'max_gram' => '7', 'type' => 'edge_ngram', 'preserve_original' => 'true', ], ]; public array $staticIndexSettings = [ 'number_of_shards' => 1, ]; public array $dynamicIndexSettings = [ 'number_of_replicas' => 1, ]; }
如果您没有定义 $name
索引名称将与类名称相同。
$propertyTypes
数组是属性名称及其数据类型的映射。键是字段名称,值是数据类型。
$fields
是字段的其它定义。例如,您想要保存文本字段的另一个版本以匹配标签(使用另一个分析器)。
$analyzers
是索引的自定义定义分析器。
$tokenizers
包含分词器的配置。要了解 $staticIndexSettings
和 $dynamicIndexSettings
,您可以在此处查看: https://elastic.ac.cn/guide/en/elasticsearch/reference/current/index-modules.html
您必须定义的唯一属性是 $properties
。
如果您不知道其他属性的功能,请参阅每个属性的文档注释来了解更多信息。
创建索引
要创建前一步定义的索引,运行 php artisan elastic:create-indices
删除索引
要删除通过此包定义的所有索引,运行 php artisan elastic:delete-indices
配置
使用 php artisan vendor:publish --provider="Alirzaj\\ElasticsearchBuilder\\ElasticsearchBuilderServiceProvider"
命令发布包的配置文件。所有选项都有文件中的描述。
使模型可搜索
您可以在 Eloquent 模型中使用 Alirzaj\ElasticsearchBuilder\Searchable
特性。此特性将自动在相应的索引中添加和更新文档。您可以在模型上重写 toIndex
方法来控制将保存到 Elasticsearch 的属性。默认行为是模型的数组表示形式(toArray)。
不使用Eloquent模型和可搜索特性索引文档
在某些情况下,您可能需要在 Eloquent 模型上不使用可搜索特性来索引或更新文档。此包提供了两个用于索引和更新的作业。
IndexDocument::dispatch( 'name_of_index', 'id', ['name' => 'alirzaj'] //an array that you want indexed in elasticsearch ); UpdateDocument::dispatch( 'name_of_index', 'id', ['name' => 'alirzaj'] //an array that you want to add in your existing elasticsearch document );
批量索引文档
BulkIndexDocuments::dispatchSync( 'blogs', [ ['id' => 1, 'title' => 'abcd'], ['id' => 2, 'title' => 'efgh'], ] );
根据条件更新文档
UpdateDocumentsByCondition::dispatchSync( 'blogs', [ 'condition-field' => 'condition-value', 'condition-field-2' => null, ], ['my-field' => 'new-value'], );
如果您要更新满足某些条件的 大字段 文档,请务必使用 UpdateDocumentsByCondition
作业的第四个参数。
UpdateDocumentsByCondition::dispatchSync( 'blogs', [ 'condition-field' => 'condition-value', 'condition-field-2' => null, ], ['text' => 'large-value'], ['text'] );
向嵌套字段添加项目
AddItemToNestedField::dispatchSync( 'blogs', 10, 'tags', ['id' => 20, 'name' => 'php'], );
向数组字段添加项目
AddItemToArrayField::dispatchSync( 'blogs', 10, 'tags', 'php', );
根据条件更新文档的嵌套字段项目
UpdateNestedItemByCondition::dispatchSync( 'blogs', 10, 'tags', ['id' => 20], // in document, we have a [nested] tags field. now we are looking for the ones with id of 20 /** * we want all of those items having above condition to be updated to this item * note that if you have id key in conditions, and id key in document parameter, the values must be the same * in other words condition's value must not change in update. * in this example we find the tag via id and update its name. we couldn't find it via old name and set a new name */ ['id' => 20, 'name' => 'new-php'] );
根据条件更新所有文档的嵌套字段项目
UpdateNestedItemByQuery::dispatchSync( 'blogs', 'tags', ['id' => 20], // in documents, we have a [nested] tags field. now we are looking for all documents with this criteria /** * we want all of those items having above condition to be updated to this item * note that if you have id key in conditions, and id key in document parameter, the values must be the same * in other words condition's value must not change in update. * in this example we find the tag via id and update its name. we couldn't find it via old name and set a new name */ ['id' => 20, 'name' => 'new-php'] );
从特定文档的嵌套字段中删除项目
/** * In tags field, remove all sub-fields with the key of id and value of 20 */ RemoveItemFromNestedField::dispatch('blogs', 10, 'tags', 'id', 20);
根据条件从嵌套字段中删除项目
/** * find documents that have id:20 in their tags field and delete id:20 from them */ DeleteNestedFieldByCondition::dispatch( 'blogs', 'tags', ['id' => 20] );
删除文档
DeleteDocument::dispatchSync('blogs',10);
删除满足某些条件的所有文档
DeleteDocumentsByCondition::dispatchSync( 'blogs', [ 'condition-field' => 'condition-value', 'condition-field-2' => null, ], );
查询索引
如果您有可搜索的模型,可以开始像这样查询相应的索引
Model::elasticsearchQuery()
您也可以通过实例化 Query
类来开始查询索引
new \Alirzaj\ElasticsearchBuilder\Query();
将索引包含在查询中
您可以将索引添加到正在查询的索引中
Blog::elasticsearchQuery()->addIndex(Users::class)->addIndex('blogs');
提高某些索引的得分
Blog::elasticsearchQuery()->addIndex('blogs')->addIndex('posts')->boost(['blogs' => 2]);
确定搜索类型
Blog::elasticsearchQuery()->addIndex('blogs')->searchType('dfs_query_then_fetch');
有关更多信息,请访问 https://elastic.ac.cn/guide/en/elasticsearch/reference/current/search-search.html
通过其ID查找文档
Blog::elasticsearchQuery()->find(150);
匹配
您可以使用命名参数来仅传递所需的选项。
Blog::elasticsearchQuery()->match('field', 'value', 'analyzer', 'fuzziness');
匹配全部
Blog::elasticsearchQuery()->matchAll(1.7);
多匹配
Blog::elasticsearchQuery()->multiMatch(['field1', 'field2'], 'value', 'analyzer', 'fuzziness', 'type');
嵌套
Blog::elasticsearchQuery()->nested( fn (Query $query) => $query->match('field', 'value'), //query 'driver.vehicle', //path 'sum',//score mode true //ignore_unmapped );
存在
Blog::elasticsearchQuery()->exists('title');
布尔
您可以将闭包传递给布尔方法,并指定您想要的查询类型
Blog::elasticsearchQuery() ->boolean( fn (Must $must) => $must ->match('a', 'b') ->exists('description'), fn (MustNot $mustNot) => $mustNot ->match('a', 'b') ->exists('description'), fn (Filter $filter) => $filter ->match('a', 'b') ->exists('z'), fn (Should $should) => $should ->match('a', 'b') ->match('z', 'x', analyzer: 'custom-analyzer') ->multiMatch(['c', 'd'], 'e') fn(BooleanOptions $booleanOptions) => $booleanOptions->minimumShouldMatch(1) );
项
Blog::elasticsearchQuery()->term('field', 'value', 1.5);
项集
Blog::elasticsearchQuery()->terms('field', ['value-1', 'value-2']);
范围
Blog::elasticsearchQuery()->range(field: 'field', gte: 10, lte: 20);
最大值
Blog::elasticsearchQuery() ->disjunctionMax( fn (Query $query) => $query->match('a', 'b'), fn (Query $query) => $query->boolean( fn (Should $should) => $should ->exists('f') ->term('g', 'h') ), );
聚合(aggs)
您可以像这样构建聚合查询:(请注意,目前,当您使用聚合时,您将获得原始的 Elasticsearch 结果)
(new Query()) ->addIndex('posts') ->addIndex('users') ->size(0) ->boolean( fn(Must $must) => $must->term('published', true), fn(Should $should) => $should ->term('title', 'aaaa') ->term('title', 'bbbb'), ) ->aggregation('types', (new Terms('morph_type')) ->aggregation('latest', (new TopHits(source: ['title', 'morph_type', 'created_at'], size: 3)) ->sort(field: 'created_at', direction: 'desc') ) ) ->get();
处理数组字段
此包提供了两个用于更新/从数组字段中删除项的作业
RemoveArrayItem::dispatch('index_name', 'array_field_name', 'value_to_remove'); UpdateArrayItem::dispatch('index_name', 'array_field_name', 'old_value', 'new_value');
获取结果
在编写查询后,您可以调用 get()
来获取结果集合。
Blog::elasticsearchQuery()->match('title', 'ttt')->get(keyResultsBy: '_id'); //a collection including _source of the resulting documents
您还可以将结果转换为 Eloquent 模型
Blog::elasticsearchQuery()->match('title', 'ttt')->hydrate(); //an Eloquent collection containing models filled with attributes from elasticsearch documents
请注意,结果集合的键是文档的 _id。
确定结果的大小限制
Blog::elasticsearchQuery()->match('title', 'ttt')->size(15)->get();
确定获取结果时的from选项(分页)
Blog::elasticsearchQuery()->match('title', 'ttt')->from(10)->get();
有关更多信息,请访问 https://elastic.ac.cn/guide/en/elasticsearch/reference/current/paginate-search-results.html
选择特定字段
only()
方法将在您的查询中添加 "_source"。
Blog::elasticsearchQuery()->match('title', 'ttt')->only(['title'])->get();
根据条件构建查询
查询构建器在底层使用 Laravel 的 Conditionable 特性,这意味着您可以这样做
use Alirzaj\ElasticsearchBuilder\Query\Query; Blog::elasticsearchQuery() ->match('title', 'ttt') ->when(isset($select), fn(Query $query) => $query->only(['title'])) ->get();
调试
您可以转储或终止查询
Blog::elasticsearchQuery()->match('title', 'ttt')->dump()->exists('field')->dump(); Blog::elasticsearchQuery()->match('title', 'ttt')->dd();
使用低级elasticsearch客户端
此包将 Elastic\Elasticsearch\Client
类绑定到服务容器作为单例,因此您可以在需要直接使用它时从容器中解析它。
记录
当环境为测试或本地时,此包将在 storage/logs/elasticsearch.log
文件中记录执行的查询。
测试助手
刷新索引状态
此包提供了一个 RefreshElasticsearchDatabase
特性,您可以在每个测试后使用它来清理 Elasticsearch 索引。
首先,您必须在测试用例中使用此特性。
abstract class TestCase extends BaseTestCase { use RefreshElasticsearchDatabase; }
然后,您应该调用两个方法。一个在 setUp()
方法中,一个在 tearDown()
中
public function setUp(): void { parent::setUp(); $this->createIndices(); }
public function tearDown(): void { $this->clearElasticsearchData(); parent::tearDown(); }
断言
此包提供了一个 InteractsWithElasticsearch
特性,您可以在测试用例中使用它来对 Elasticsearch 索引中的数据进行断言。
abstract class TestCase extends BaseTestCase { use InteractsWithElasticsearch; }
您可以在 Elasticsearch 索引中断言某个文档是否存在
$this->assertElasticsearchHas( 'blogs', 15, ['title' => 'my title'] );
或确保某个文档未在 Elasticsearch 中索引
$this->assertElasticsearchMissing( 'blogs', 15, ['title' => 'my title'] );
致谢
许可
MIT 许可证(MIT)。有关更多信息,请参阅 许可文件。