hilsonxhero / elasticvision
Laravel Scout 的 Elasticsearch 驱动程序。
Requires
- php: 8.0.*||8.1.*||8.2.*
- elasticsearch/elasticsearch: ^8.6
- illuminate/support: ^9.0||^10.0
- laravel/scout: ^9.0||^10.0
- webmozart/assert: ^1.10
Requires (Dev)
- phpunit/phpunit: ~9.0
README
Elasticsearch 驱动程序,结合了 Laravel Scout 和 Elasticsearch 查询的力量。
安装
通过 Composer
composer require hilsonxhero/elasticvision
您需要配置文件来定义您的索引
php artisan vendor:publish --tag=elasticvision-config
同时,请不要忘记遵循Laravel Scout 的安装说明,并在您的 Laravel Scout 配置中,将驱动程序设置为 elastic
。
配置
您可以在 elasticvision 配置文件中定义您索引的映射
return [ 'indexes' => [ \App\Models\Product::class, ], ];
php artisan scout:index products
php artisan scout:import "\App\Models\Product"
用法
在最后一种情况下,您可以实现 Explored
接口,并使用 mappableAs()
函数覆盖映射。
📕 文档
这意味着它取决于您是否喜欢在模型中将它们全部放在一起,还是在配置文件中分别存放。
词项查询
返回包含提供字段中确切词项的文档。
您可以使用词项查询根据精确值(如价格、产品 ID 或用户名)查找文档。
use App\Models\Post; $posts = Post::search('lorem') ->filter(new Term('published', true)) ->get();
词项查询
返回包含提供字段中一个或多个确切词项的文档。
词项查询与词项查询相同,但您可以搜索多个值。文档将匹配如果它包含至少一个词项。要搜索包含多个匹配词项的文档。
use App\Models\Post; $posts = Post::search('lorem') ->should(new Terms('tags', ['featured'], 2)) ->get();
词项的高级参数
字段
(可选,对象) 您希望搜索的字段。
提升
(可选,浮点数) 用于减少或增加查询相关性的浮点数。默认为 1.0。
排序
默认情况下,您的搜索结果将根据 Elasticsearch 的得分进行排序。如果您想介入并影响排序,可以使用 Laravel Scout 的默认 orderBy()
函数。
use App\Models\Post; $results = Post::search('Self-steering') ->orderBy('published_at', 'desc') ->get();
范围
返回包含提供范围内词项的文档。
use App\Models\User; $results = User::search('fugiat')->must(new Range('age',['gte' => 18, 'lte' => 35]))->get();
正则表达式查询
返回包含匹配正则表达式的词项的文档。
正则表达式是通过使用占位符字符(称为运算符)在数据中匹配模式的方法。有关正则表达式查询支持的运算符列表,请参阅正则表达式语法。
use App\Models\User; $results = User::search('fugiat')->must(new RegExp('username', 'k.*y','ALL',false))->get();
正则表达式的高级参数
字段
(可选,对象) 您希望搜索的字段。
值
(必需,字符串) 您希望在提供的 <field>
中查找的词项的正则表达式。有关支持的运算符列表,请参阅正则表达式语法。
标志
(可选,字符串) 启用正则表达式的可选运算符。有关有效值和更多信息,请参阅正则表达式语法。
不区分大小写
(可选,布尔值) 当设置为 true 时,允许不区分大小写的正则表达式值与索引字段值匹配。默认为 false,这意味着匹配的大小写取决于底层字段的映射。
提升
(可选,浮点数) 用于减少或增加查询相关性的浮点数。默认为 1.0。
通配符查询
返回匹配通配符模式的词项的文档。
通配符运算符是一个占位符,可以匹配一个或多个字符。例如,* 通配符运算符可以匹配零个或多个字符。您可以将通配符运算符与其他字符结合,创建通配符模式。
use App\Models\User; $users = User::search() ->should(new Wildcard('username','ki*y')) ->get();
匹配查询
返回与提供的文本、数字、日期或布尔值匹配的文档。在匹配之前会分析提供的文本。
匹配查询是执行全文搜索的标准查询,包括模糊匹配的选项。
use App\Models\Article; $articles = Article::search() ->must(new Matching('title','ipsum')) ->get();
匹配短语前缀查询
返回包含提供文本单词的文档,单词的顺序与提供的一致。提供文本的最后一个术语被视为前缀,匹配以该术语开头的任何单词。
use App\Models\User; $users = User::search() ->should(new MatchPhrasePrefix('message','quick brown f')) ->get();
匹配短语查询
match_phrase
查询分析文本,并将分析后的文本创建为 phrase
查询。例如
use App\Models\User; $users = User::search() ->should(new MatchPhrase('message','this is a test')) ->get();
嵌套查询
包装另一个查询以搜索嵌套字段。
nested
查询将嵌套的字段对象作为单独的文档索引进行搜索。如果对象匹配搜索,则 nested
查询返回根父文档。
use App\Models\Product; $products = Product::search() ->must(new Nested('category', new Term('category.id', 2))) ->get();
use App\Models\Product; $search = Product::search("lorem"); // $feature_ids = array([4 => [1,2], 5 => [1,2]]) foreach (request()->feature_id as $key => $value) { $query = new BoolQuery(); $query->must(new Term('features.feature_id', $key)); $query->must(new Terms('features.feature_value_id', $value)); $boolQuery->add('must', new Nested('features', $query)); } $search->newCompound($boolQuery); $products = $search->paginate(15);
短语查询匹配以可配置的“slop”(默认为 0)为上限的术语,顺序不限。转置术语的 slop 为 2。
分析器可以设置以控制哪个分析器将对文本执行分析过程。默认为字段显式映射定义,或默认搜索分析器,例如
索引设置
您将通过您的索引的 映射 执行大部分配置。但是,如果您想要定义更高级的 Elasticsearch 设置,例如 分析器 或 分词器,则需要使用索引设置来完成。
请注意,每次您更改索引设置时,都需要 重新创建 索引。
要开始使用索引设置,我们将在 Post 模型中扩展一个 indexSettings
函数来设置分析器。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Hilsonxhero\ElasticVision\Application\Explored; use Hilsonxhero\ElasticVision\Application\IndexSettings; use Laravel\Scout\Searchable; class Post extends Model implements Explored, IndexSettings { use HasFactory; use Searchable; protected $fillable = ['title', 'published']; public function mappableAs(): array { return [ 'id' => 'keyword', 'title' => 'text', 'published' => 'boolean', 'created_at' => 'date', ]; } public function indexSettings(): array { return [ 'analysis' => [ 'analyzer' => [ 'standard_lowercase' => [ 'type' => 'custom', 'tokenizer' => 'standard', 'filter' => ['lowercase'], ], ], ], ]; } }
文本分析
文本分析是作为 索引设置 部分设置的。
以下示例创建了一个同义词分析器,最终结果是当您搜索 'Vue' 时,您也会获得 'React' 的搜索结果。为了确保同义词匹配所有情况,还会运行 lowercase
过滤器。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Hilsonxhero\ElasticVision\Application\Explored; use Hilsonxhero\ElasticVision\Application\IndexSettings; use Hilsonxhero\ElasticVision\Domain\Analysis\Analysis; use Hilsonxhero\ElasticVision\Domain\Analysis\Analyzer\StandardAnalyzer; use Hilsonxhero\ElasticVision\Domain\Analysis\Filter\SynonymFilter; use Laravel\Scout\Searchable; class Post extends Model implements Explored, IndexSettings { use HasFactory; use Searchable; protected $fillable = ['title', 'published']; public function mappableAs(): array { return [ 'id' => 'keyword', 'title' => [ 'type' => 'text', 'analyzer' => 'frameworks', ], 'published' => 'boolean', 'created_at' => 'date', ]; } public function indexSettings(): array { $synonymFilter = new SynonymFilter(); $synonymFilter->setSynonyms(['vue => react']); $synonymAnalyzer = new StandardAnalyzer('frameworks'); $synonymAnalyzer->setFilters(['lowercase', $synonymFilter]); return (new Analysis()) ->addAnalyzer($synonymAnalyzer) ->addFilter($synonymFilter) ->build(); } }
聚合
聚合是您的搜索查询的一部分,可以对数据进行汇总。您可以在 Elasticsearch 的官方文档中阅读更多有关聚合的信息 官方文档。目前,并非所有聚合类型都内置,但创建缺失的类型应该是可行的(并且对这些包的补充非常受欢迎)。
添加聚合可以使您的搜索查询更复杂。以下是从演示应用程序中的一个示例
$search = Cartographer::search(); $search->aggregation('places', new TermsAggregation('place')); $results = $search->raw(); $aggregations = $results->aggregations();
这将返回一个数组,显示每个地点在 Elasticsearch 索引中出现的次数。
创建同义词过滤器和分析器非常简单,但请注意,对于 Elasticsearch 来说它们“昂贵”的运行。在转向同义词之前,看看是否可以使用通配符或模糊查询。
⚡ 高级查询
Laravel Scout 的文档指出:“目前不支持更高级的 'where' 子句”。除了标准的模糊术语搜索之外,只能进行简单的 ID 检查。
$categories = Category::search('lorem ipsum')->filter(new MatchPhrase('status', 'enable'))->take(15)->get();
ElasticVision 使用查询构建器扩展了您的可能性,可以编写更复杂的查询。
class Product extends Model implements Explored { public function mappableAs(): array { return [ 'id' => 'keyword', 'title_fa' => [ 'type' => 'text', 'analyzer' => 'my_analyzer', ], 'title_en' => [ 'type' => 'text', ], 'status' => [ 'type' => 'text', ], 'category' => 'nested', 'features' => 'nested', 'variants' => 'nested', 'has_stock' => 'boolean', ]; } public function toSearchableArray(): array { return [ 'id' => $this->id, 'title' => $this->title, 'status' => $this->status, 'category' => $this->category, 'features' => $this->features, 'variants' => ProductVariantResource::collection($this->variants)->toArray(true), 'has_stock' => $this->has_stock, ]; } public function indexSettings(): array { return [ "analysis" => [ "analyzer" => [ "my_analyzer" => [ "type" => "custom", "tokenizer" => "standard", "filter" => ["lowercase", "my_filter"] ] ], "filter" => [ "my_filter" => [ "type" => "ngram", "min_gram" => 2, ] ] ], "index" => [ "max_ngram_diff" => 13 ] ]; } /** * Get the name of the index associated with the model. * * @return string */ public function searchableAs() { return 'products'; } public function category() { return $this->belongsTo(Category::class); } public function variants() { return $this->hasMany(ProductVariant::class); } public function features() { return $this->hasMany(ProductFeature::class); } /** * check inventory of product variations * * @return \Illuminate\Database\Eloquent\Casts\Attribute */ protected function hasStock(): Attribute { return Attribute::make( get: fn ($value) => $this->variants()->sum('stock') > 0 ? true : false ); } }
例如,要获取所有帖子,
- 启用了状态
- 有一个 ID 为 2 的分类
- 标题和描述中包含“ipsum”
- 有任意功能
您可以执行此搜索查询
$boolQuery = new BoolQuery(); $search = Product::search("ipsum") ->field('title') ->field('description') ->filter(new MatchPhrase('status', 'enable')) ->must(new Nested('category', new MatchPhrase('category.id',2))); if (request()->filled('available_stock')) { $search->filter(new Term('has_stock', true)); } // request feature_ids value // $feature_ids = array([4 => [1,2], 5 => [1,2]]) if (request()->filled('feature_ids')) { foreach (request()->feature_ids as $key => $value) { $query = new BoolQuery(); $query->must(new MatchPhrase('features.feature_id', $key)); $query->must(new Terms('features.feature_value_id', $value)); $boolQuery->add('must', new Nested('features', $query)); } } if (request()->filled('max_price') && request()->filled('min_price')) { $boolQuery->add('must', new Nested('variants', new Range( 'variants.selling_price', ['gte' => request()->min_price] ))); $boolQuery->add('must', new Nested('variants', new Range( 'variants.selling_price', ['lte' => request()->max_price] ))); $boolQuery->add('must_not', new Nested('variants', new Range( 'variants.selling_price', ['lt' => request()->min_price] ))); $boolQuery->add('must_not', new Nested('variants', new Range( 'variants.selling_price', ['gt' => request()->max_price] ))); } $search->newCompound($boolQuery); $products = $search->paginate(15); return $products;
调试
有时您可能会想知道为什么某些结果被返回或未被返回。
以下是从ElasticVision演示应用程序中的示例,尽管不是复杂的查询
class SearchController { public function __invoke(SearchFormRequest $request) { $people = Cartographer::search($request->get('keywords'))->get(); return view('search', [ 'people' => $people, ]); } }
要调试此搜索查询,您可以在Laravel Scout的Elastic Engine上调用静态debug
方法
use Hilsonxhero\ElasticVision\Infrastructure\Scout\ElasticEngine; $debug = ElasticEngine::debug();
此方法返回的调试类可以以数组或JSON形式提供最后执行的查询。您应该能够将JSON复制粘贴为直接查询到Elasticsearch。
$lastQueryAsArray = ElasticEngine::debug()->array(); $lastQueryAsJson = ElasticEngine::debug()->json();
控制台命令
创建
使用Laravel Scout导入命令创建和更新索引。
php artisan scout:import <model>
例如,如果您的模型是"App\Models\Post",则命令如下
php artisan scout:import "App\Models\Post"
如果您想重新创建索引,请首先确保它已被删除,然后创建它。接着用scout导入命令来重新填充索引。
更新别名索引
如果您正在使用别名索引,则应使用此命令而不是scout:import
php artisan elastic:update <index?>
您可以选择指定索引或选择省略它,命令将更新所有索引。例如,如果您的模型是"App\Model\Post",索引是"posts",则
php artisan elastic:update posts
删除
php artisan scout:delete-index <model>
使用Laravel Scount删除索引命令来删除索引。
命令
请确保您已首先在config/elasticvision.php
中配置索引并运行Scout命令。