rennokki / elasticscout
Requires
- aws/aws-sdk-php: ^3.132
- elasticsearch/elasticsearch: >=7.1
- laravel/scout: ^8.0
- rennokki/laravel-eloquent-query-cache: ^1.3|^2.0
Requires (Dev)
- orchestra/database: ^5.0
- orchestra/testbench: ^5.0
README
ElasticScout 是一个 Laravel Scout 驱动器,它可以与任何 Elasticsearch 服务器交互,在模型中提供全文搜索的全部功能。
此包源自Babenko Ivan 的 Elasticscout Driver 仓库。
🤝 支持
Renoki Co. 在 GitHub 上致力于将许多开源项目和有用的项目带给世界。每天开发和维护项目是一项艰巨的工作,尽管如此,我们仍然热爱它。
如果您在日常工作中使用您的应用程序,在演示演示中,爱好项目,甚至是学校项目中,请分享一些关于我们工作的好评或赞助我们的工作。好评会触动我们的心灵和情绪,而赞助将使开源项目保持活力。
🚀 安装
使用 Composer CLI 安装包
$ composer require rennokki/elasticscout
如果您的 Laravel 包不支持自动发现,请将以下内容添加到您的 config/app.php
文件中
'providers' => [ ... Rennokki\ElasticScout\ElasticScoutServiceProvider::class, ... ];
发布配置文件
$ php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider" $ php artisan vendor:publish --provider="Rennokki\ElasticScout\ElasticScoutServiceProvider"
如果您希望直接访问已配置为具有您自己的配置的 Elasticsearch 客户端,您可以通过将外观添加到 config/app.php
来这样做
'ElasticScout' => Rennokki\ElasticScout\Facades\ElasticClient::class,
然后您可以像平时一样访问它
// Get all indexes ElasticScout::indices()->get(['index' => '*']);
配置 Scout
在您的 .env
文件中,将您的 SCOUT_DRIVER
设置为 elasticscout
,并附带 Elasticsearch 配置
SCOUT_DRIVER=elasticscout SCOUT_ELASTICSEARCH_HOST=localhost SCOUT_ELASTICSEARCH_PORT=9200
AWS Elasticsearch 服务
Amazon Elasticsearch 服务无需任何额外设置即可在 VPC 集群中完美运行。然而,对于公共集群,它有点奇怪,因为它需要 IAM 认证。
您首先确保已经安装了正确的 elasticsearch/elasticsearch
包版本。例如,对于 7.4
集群,您应该安装 elasticsearch/elasticsearch:"7.4.*"
,否则您的应用程序将收到错误。
$ composer require elasticsearch/elasticsearch:"7.4.*"
要找到正确的包大小,请检查Elasticsearch 的版本矩阵。
当从 AWS Elasticsearch 集群请求数据时,ElasticScout 确保通过附加处理程序将您的身份验证传递给后续请求。您只需启用设置,通过设置 SCOUT_ELASTICSCOUT_AWS_ENABLED
环境变量即可完成此操作
AWS_ACCESS_KEY_ID=my_key AWS_SECRET_ACCESS_KEY=my_secret SCOUT_ELASTICSCOUT_AWS_ENABLED=true SCOUT_ELASTICSCOUT_AWS_REGION=us-east-1 SCOUT_ELASTICSEARCH_HOST=search-xxxxxx.es.amazonaws.com SCOUT_ELASTICSEARCH_PORT=443 SCOUT_ELASTICSEARCH_SCHEME=https
请注意:您不需要用户名和密码即可访问 AWS Elasticsearch 服务集群。
📄 索引
创建索引
在 Elasticsearch 中,索引是 MySQL 中的表,或 MongoDB 中的集合的等效项。您可以使用 artisan 创建索引类
$ php artisan make:elasticscout:index PostIndex
你会在 app/Indexes/PostIndex.php
中看到类似的内容
namespace App\Indexes; use Rennokki\ElasticScout\Index; use Rennokki\ElasticScout\Migratable; class PostIndex extends Index { use Migratable; /** * The settings applied to this index. * * @var array */ protected $settings = [ // ]; /** * The mapping for this index. * * @var array */ protected $mapping = [ // ]; }
这里的关键是你可以为每个索引设置设置和映射。你可以在Elasticsearch的文档网站上找到更多关于 映射 和 设置 的信息。
以下是一个为地理点数据类型创建映射的示例。
class RestaurantIndex extends Index { ... protected $mapping = [ 'properties' => [ 'location' => [ 'type' => 'geo_point', ], ], ]; }
以下是在 $settings
变量中为空白分词器创建新分析器的示例。
class PostIndex extends Index { ... protected settings = [ 'analysis' => [ 'analyzer' => [ 'content' => [ 'type' => 'custom', 'tokenizer' => 'whitespace', ], ], ], ]; }
如果你希望更改索引的名称,可以通过覆盖 $name
变量来实现。
class PostIndex extends Index { protected $name = 'posts_index_2'; }
将索引附加到模型
所有可以搜索的模型都应该使用 Rennokki\ElasticScout\Searchable
特性和实现 Rennokki\ElasticScout\Index\HasElasticScoutIndex
接口。
use Rennokki\ElasticScout\Contracts\HasElasticScoutIndex; use Rennokki\ElasticScout\Searchable; class Post extends Model implements HasElasticScoutIndex { use Searchable; }
此外,模型还应指定索引类。
use App\Indexes\PostIndex; use Rennokki\ElasticScout\Contracts\HasElasticScoutIndex; use Rennokki\ElasticScout\Index; use Rennokki\ElasticScout\Searchable; class Post extends Model implements HasElasticScoutIndex { use Searchable; /** * Get the index instance class for Elasticsearch. * * @return \Rennokki\ElasticScout\Index */ public function getElasticScoutIndex(): Index { return new PostIndex($this); } }
将索引发布到Elasticsearch
要将索引发布到Elasticsearch,你应该同步索引。
$ php artisan elasticscout:index:sync App\\Post
现在,每次你的模型创建、更新或删除新记录时,它们都将自动同步到Elasticsearch。
如果你想导入已存在的数据,请使用Scout文档中描述的 scout:import 命令。
你可以在代码中同步索引。
$restaurant = Restaurant::first(); $restaurant->getIndex()->sync(); // returns true/false
🔍 搜索查询
要查询Elasticsearch中的数据,你可以使用 search()
方法。
Post::search('Laravel') ->take(30) ->from(10) ->get();
如果你只想获取文档的数量,你也可以这样做。
$posts = Post::search('Lumen')->count();
🔺 过滤查询
ElasticScout 允许你通过通过 elasticsearch()
方法使用内置方法创建自定义查询。
必须、必须不、应该、过滤
你可以在构建器中直接使用Elasticsearch的必须、必须不、应该和过滤键。请注意,你可以按需链式调用。
Post::elasticsearch() ->must(['term' => ['tag' => 'wow']]) ->should(['term' => ['tag' => 'yay']]) ->shouldNot(['term' => ['tag' => 'nah']]) ->filter(['term' => ['tag' => 'wow']]) ->get();
⚗️ 查询定制
你可以向body或query键追加数据。
// apend to the body payload Post::elasticsearch() ->appendToBody('minimum_should_match', 1) ->appendToBody('some_field', ['array' => 'yes']) ->get();
// append to the query payload Post::elasticsearch() ->appendToQuery('some_field', 'value') ->appendToQuery('some_other_field', ['array' => 'yes']) ->get();
Wheres
Post::elasticsearch() ->where('title.keyword', 'Elasticsearch') ->first();
Book::elasticsearch() ->whereBetween('price', [100, 200]) ->first();
Book::elasticsearch() ->whereNotBetween('price', [100, 200]) ->first();
Whens、Unless、动态Wheres
Book::elasticsearch() ->when(true, function ($query) { return $query->where('price', 100); })->get();
Book::elasticsearch() ->unless(false, function ($query) { return $query->where('price', 100); })->get();
Book::elasticsearch() ->wherePrice(100) ->get(); // This is the equivalent. Book::elasticsearch() ->where('price', 100) ->get();
如果动态Where包含多个单词,它们将被 snake_case
分割。
Book::elasticsearch() ->whereFanVotes(10) ->get(); // This is the same. Book::elasticsearch() ->where('fan_votes', 10) ->get();
正则表达式过滤器
Post::elasticsearch() ->whereRegexp('title.raw', 'A.+') ->get();
存在性检查
由于Elasticsearch具有NoSQL结构,你应该能够检查字段是否存在。
Post::elasticsearch() ->whereExists('meta') ->whereNotExists('new_meta') ->get();
地理类型搜索
Restaurant::whereGeoDistance('location', [-70, 40], '1000m') ->get();
Restaurant::whereGeoBoundingBox( 'location', [ 'top_left' => [-74.1, 40.73], 'bottom_right' => [-71.12, 40.01], ] )->get();
Restaurant::whereGeoPolygon( 'location', [ [-70, 40], [-80, 30], [-90, 20], ] )->get();
Restaurant::whereGeoShape( 'shape', [ 'type' => 'circle', 'radius' => '1km', 'coordinates' => [4, 52], ], 'WITHIN' )->get();
使用作用域
Elasticscout 还与在主模型中定义的作用域一起工作。
class Restaurant extends Model { public function scopeNearby($query, $lat, $lon, $meters) { return $query->whereGeoDistance('location', [$lat, $lon], $meters.'m'); } } $nearbyRestaurants = Restaurant::search('Dominos')->nearby(45, 35, 1000)->get();
查询缓存
使用 rennokki/laravel-eloquent-query-cache 可以实现查询缓存。您只需要检查代码库了解如何使用它。
基本上,您可以这样缓存请求
$booksByJohnGreen = Book::elasticsearch() ->cacheFor(now()->addMinutes(60)) ->where('author', 'John Green') ->get();
Elasticsearch 规则
搜索规则是一个可以在多个查询中使用的类,帮助您一次性定义自定义的有效负载。这仅适用于搜索查询构建器。
要创建规则,使用 artisan 命令
$ php artisan make:elasticscout:rule NameRule
您会得到类似这样的结果
namespace App\SearchRules; use Rennokki\ElasticScout\Builders\SearchQueryBuilder; use Rennokki\ElasticScout\SearchRule; class NameRule extends SearchRule { /** * Initialize the rule. * * @return void */ public function __construct() { // } /** * Build the highlight payload. * * @param SearchQueryBuilder $builder * @return array */ public function buildHighlightPayload(SearchQueryBuilder $builder) { return [ // ]; } /** * Build the query payload. * * @param SearchQueryBuilder $builder * @return array */ public function buildQueryPayload(SearchQueryBuilder $builder) { return [ // ]; } }
查询有效负载
在 buildQueryPayload()
中,您应该定义查询过程中将发生的查询有效负载。
例如,您可以从一些 bool 查询开始。有关 bool 查询的详细信息,您可以在Elasticsearch 文档中找到。
class NameRule extends SearchRule { public function buildQueryPayload(SearchQueryBuilder $builder) { return [ 'must' => [ 'match' => [ // access the search phrase from the $builder 'name' => $builder->query, ], ], ]; } }
要默认应用于所有搜索查询,在您的模型中定义一个 getElasticScoutSearchRules()
方法
use App\SearchRules\NameRule; class Restaurant extends Model { /** * Get the search rules for Elasticsearch. * * @return array */ public function getElasticScoutSearchRules(): array { return [ new NameRule, ]; } }
要查询级别应用规则,您可以调用 ->addRule()
方法
use App\SearchRules\NameRule; Restaurant::search('Dominos') ->addRule(new NameRule) ->get();
您可以添加多个规则或将规则设置为特定值
use App\SearchRules\NameRule; use App\SearchRules\LocationRule; Restaurant::search('Dominos') ->addRules([ new NameRule, new LocationRule($lat, $lon), ])->get(); // The rule that will be aplied will be only LocationRule Restaurant::search('Dominos') ->addRule(new NameRule) ->setRules([ new LocationRule($lat, $lon), ])->get();
高亮有效负载
在构建高亮有效负载时,您可以将数组传递给 buildHighlightPayload()
方法。有关高亮的更多详细信息,您可以在Elasticsearch 文档中找到。
class NameRule extends SearchRule { public function buildHighlightPayload(SearchQueryBuilder $builder) { return [ 'fields' => [ 'name' => [ 'type' => 'plain', ], ], ]; } public function buildQueryPayload(SearchQueryBuilder $builder) { return [ 'should' => [ 'match' => [ 'name' => $builder->query, ], ], ]; } }
要访问有效负载,您可以使用模型中的 $highlight
属性(或最终集合中的每个模型的属性)。
use App\SearchRules\NameRule; $restaurant = Restaurant::search('Dominos')->addRule(new NameRule)->first(); $name = $restaurant->elasticsearch_highlights->name; $nameAsString = $restaurant->elasticsearch_highlights->nameAsString;
如果您需要向规则传递参数,可以通过添加您的构造方法来实现。
class NameRule extends SearchRule { protected $name; public function __construct($name = null) { $this->name = $name; } public function buildQueryPayload(SearchQueryBuilder $builder) { // Override the name from the rule construct. $name = $this->name ?: $builder->query; return [ 'must' => [ 'match' => [ 'name' => $name, ], ], ]; } } Restaurant::search('Dominos') ->addRule(new NameRule('Pizza Hut')) ->get();
🐛 查询调试
您可以通过解释查询来进行调试。
Restaurant::search('Dominos')->explain();
您可以通过调用 getPayload()
来查看有效负载的外观。
Restaurant::search('Dominos')->getPayload();
🐛 测试
vendor/bin/phpunit
🤝 贡献
请参阅CONTRIBUTING 获取详细信息。
🔒 安全性
如果您发现任何与安全相关的问题,请通过电子邮件 alex@renoki.org 而不是使用问题跟踪器。