Scout 全文搜索
Laravel Scout
介绍
Laravel Scout为 Eloquent 模型 的全文搜索提供了一个简单的基于驱动程序的解决方案,通过使用模型观察者,Scout将自动同步 Eloquent 记录的搜索索引。
目前,Scout自带了一个 Algolia 驱动; 不过,编写自定义驱动程序也很简单,您可以轻松的通过自己的搜索实现来扩展 Scout。
安装
首先,通过 Composer 软件包管理器安装 Scout:
composer require laravel/scout
Scout 安装完成后,使用 vendor:publish
Artisan 命令生成 Scout 配置文件。此命令将会在你的 config
目录下 生成一个 scout.php
配置文件:
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
最后,在你要做搜索的模型中添加 Laravel\Scout\Searchable
trait 。这个 trait 会注册一个模型观察者来保持模型和搜索驱动的同步:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Post extends Model
{
use Searchable;
}
驱动的先决条件
Algolia
使用 Algolia 驱动时,需要在 config/scout.php
配置文件配置你的 Algolia id
和 secret
凭证。配置好凭证之后,还需要使用 Composer 安装 Algolia PHP SDK:
composer require algolia/algoliasearch-client-php
MeiliSearch
MeiliSearch 是一个强大的开源搜索引擎,可以使用 Laravel Sail 在本地运行。 MeiliSearch 提供并维护了一个 Laravel Scout 官方 MeiliSearch 驱动。 请参阅此包的文档以了解如何将 MeiliSearch 与 Laravel Scout 一起使用。
Queueing
虽然不是严格要求使用 Scout,但在使用库之前,您应该强烈考虑配置 队列驱动。 运行队列 worker 将允许 Scout 将所有将您的模型信息同步到您的搜索索引的操作进行排队,从而为您的应用的 Web 界面提供更好的响应时间。
配置好队列驱动后,将 config/scout.php
配置文件中的 queue
选项的值设置为 true
:
'queue' => true,
配置
配置模型索引
每个 Eloquent 模型都与给定的搜索 「索引」同步,该索引包含该模型的所有可搜索记录。 换句话说,您可以将每个索引视为一个 MySQL 表。 默认情况下,每个模型都将持久化到与模型的典型 「表」名称匹配的索引。 通常,是模型名称的复数形式; 但是,您可以通过重写模型上的 searchableAs
方法来自由地自定义模型的索引:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Post extends Model
{
use Searchable;
/**
* 获取与模型关联的索引的名称。
*
* @return string
*/
public function searchableAs()
{
return 'posts_index';
}
}
配置可搜索数据
默认情况下,模型以完整的 toArray
格式持久化到搜索索引。如果要自定义同步到搜索索引的数据,可以覆盖模型上的 toSearchableArray
方法:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Post extends Model
{
use Searchable;
/**
* 获取模型的可索引的数据。
*
* @return array
*/
public function toSearchableArray()
{
$array = $this->toArray();
// 自定义数据数组...
return $array;
}
}
配置模型 ID
默认情况下,Scout 将使用模型的主键作为搜索索引中存储的唯一 ID / key。 可以通过模型上的 getScoutKey
和 getScoutKeyName
方法自定义:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class User extends Model
{
use Searchable;
/**
* 获取用于索引模型的值
*
* @return mixed
*/
public function getScoutKey()
{
return $this->email;
}
/**
* 获取用于索引模型的键名
*
* @return mixed
*/
public function getScoutKeyName()
{
return 'email';
}
}
识别用户
Scout 还允许您在使用 Algolia 时自动识别用户。在 Algolia 的仪表板中查看搜索分析时,将经过身份验证的用户与搜索操作相关联可能会有所帮助。您可以通过在应用程序的 .env
文件中将 SCOUT_IDENTIFY
环境变量定义为 true
来启用用户标识:
SCOUT_IDENTIFY=true
启用此功能还会将请求的IP地址和经过身份验证的用户的主标识符传递给 Algolia,以便将此数据与用户发出的任何搜索请求相关联。
索引
批量导入
如果要将 Scout 安装到现有项目中,则可能已有需要导入索引的数据库记录。Scout 提供 scout:import
Artisan 命令,可用于将所有现有记录导入搜索索引:
php artisan scout:import "App\Models\Post"
flush
命令可用于从搜索索引中删除模型的所有记录:
php artisan scout:flush "App\Models\Post"
修改导入查询
如果要修改用于检索所有模型以进行批量导入的查询,可以在模型上定义 makeAllSearchableUsing
方法。这是一个很好的地方,可以在导入模型之前添加任何可能需要的即时关系加载:
/**
* 在使所有模型都可搜索时,修改用于检索模型的查询。
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function makeAllSearchableUsing($query)
{
return $query->with('author');
}
添加记录
一旦将 Laravel\Scout\Searchable
trait 添加到模型中,您只需 save
或 create
模型实例,它就会自动添加到搜索索引中。如果已将 Scout 配置为 使用队列,此操作将由队列 worker 进程在后台执行:
use App\Models\Order;
$order = new Order;
// ...
$order->save();
通过查询添加
如果您希望通过 Eloquent 查询将模型集合添加到搜索索引中,你也可以在 Eloquent 查询构造器上链式调用 searchable
方法。searchable
会把构造器的查询 结果分块 并将记录添加到搜索索引中。同样,如果您已将 Scout 配置为使用队列,则队列 worker 将在后台导入所有块:
use App\Models\Order;
Order::where('price', '>', 100)->searchable();
您还可以在 Eloquent 关联实例上调用 searchable
方法:
$user->orders()->searchable();
或者,如果内存中已经有一组 Eloquent 模型,可以调用集合实例上的 searchable
方法,将模型实例添加到相应的索引中:
$orders->searchable();
技巧:
searchable
方法可被视为 「upsert」操作。换句话说,如果模型记录已经在索引中,它将被更新。如果它不存在于搜索索引中,则将其添加到索引中。
更新记录
要更新可搜索的模型,只需要更新模型实例的属性并将模型 save
到数据库。Scout 会自动将更新同步到你的搜索索引中:
use App\Models\Order;
$order = Order::find(1);
// 更新订单...
$order->save();
你也可以在 Eloquent 查询语句上使用 searchable
方法来更新一个模型的集合。如果这个模型不存在你检索的索引里,就会被创建:
Order::where('price', '>', 100)->searchable();
如果要更新关系中所有模型的搜索索引记录,可以在关系实例上调用 searchable
:
$user->orders()->searchable();
或者,如果内存中已经有 Eloquent 模型集合,则可以调用集合实例上的 searchable
方法来更新相应索引中的模型实例:
$orders->searchable();
移除记录
要从索引中删除记录,只需从数据库中 delete
模型即可。即使您正在使用 软删除 模型,也可以这样做:
use App\Models\Order;
$order = Order::find(1);
$order->delete();
如果你不希望记录在删除之前被检索到,可以在 Eloquent 查询实例或集合上使用 unsearchable
方法:
Order::where('price', '>', 100)->unsearchable();
如果要删除关系中所有模型的搜索索引记录,可以在关系实例上调用 unsearchable
:
$user->orders()->unsearchable();
或者,如果内存中已经有 Eloquent 模型集合,则可以调用集合实例上的 unsearchable
方法,从相应的索引中删除模型实例:
$orders->unsearchable();
暂停索引
你可能需要在执行一批 Eloquent 操作的时候,不同步模型数据到搜索索引。此时你可以使用 withoutSyncingToSearch
方法来执行此操作。这个方法接受一个立即执行的回调。该回调中所有的操作都不会同步到模型的索引:
use App\Models\Order;
Order::withoutSyncingToSearch(function () {
// 执行模型操作...
});
有条件的搜索模型实例
有时你可能只需要在某些条件下使模型可搜索。例如,假设你有 App\Models\Post
模型可能是两种状态之一:「草稿」和「已发布」。你可能只允许搜索 「已发布」的帖子。为了实现这一点,你需要在模型中定义一个 shouldBeSearchable
方法:
/**
* 确定模型是否可搜索
*
* @return bool
*/
public function shouldBeSearchable()
{
return $this->isPublished();
}
仅当通过 save
和 create
方法、查询或关联模型操作时,才应使用 shouldBeSearchable
方法。直接使用 searchable
方法将使模型或集合的可搜索结果覆盖 shouldBeSearchable
方法的结果:
搜索
你可以使用 search
方法来搜索模型。search 方法接受一个用于搜索模型的字符串。你还需要在搜索查询上链式调用 get
方法,才能用给定的搜索语句查询与之匹配的 Eloquent 模型:
use App\Models\Order;
$orders = Order::search('Star Trek')->get();
由于Scout 搜索返回 Eloquent 模型的集合,您甚至可以直接从路由或控制器返回结果,结果将自动转换为 JSON :
use App\Models\Order;
use Illuminate\Http\Request;
Route::get('/search', function (Request $request) {
return Order::search($request->search)->get();
});
如果你想在它们转换成 Eloquent 模型前得到原始结果,你应该使用 raw
方法:
$orders = Order::search('Star Trek')->raw();
自定义索引
搜索查询通常会在模型的 searchableAs
方法指定的索引上执行。但是,您可以使用 within
方法指定应搜索的自定义索引:
$orders = Order::search('Star Trek')
->within('tv_shows_popularity_desc')
->get();
Where 子句
Scout 允许您在搜索查询中添加简单的「where」子句。目前,这些子句仅支持基本的数值相等性检查,主要用于按所有者 ID 确定搜索查询的范围。由于搜索索引不是关系数据库,因此目前不支持更高级的「where」子句:
use App\Models\Order;
$orders = Order::search('Star Trek')->where('user_id', 1)->get();
分页
除了检索模型的集合,你也可以使用 paginate
方法对搜索结果进行分页。这个方法会返回一个就像 传统的 Eloquent 查询分页 一样的 Illuminate\Pagination\LengthAwarePaginator
实例:
use App\Models\Order;
$orders = Order::search('Star Trek')->paginate();
通过将数量作为第一个参数传递给 paginate
方法,可以指定每页要检索多少个模型:
$orders = Order::search('Star Trek')->paginate(15);
检索结果后,您可以使用 Blade 显示结果并呈现页面链接,就像对传统的 Eloquent 查询进行分页一样:
<div class="container">
@foreach ($orders as $order)
{{ $order->price }}
@endforeach
</div>
{{ $orders->links() }}
当然,如果希望以 JSON 形式检索分页结果,可以直接从路由或控制器返回分页器实例:
use App\Models\Order;
use Illuminate\Http\Request;
Route::get('/orders', function (Request $request) {
return Order::search($request->input('query'))->paginate(15);
});
软删除
如果你索引的模型是 软删除,并且您需要搜索已删除的模型,请将 config/scout.php
配置文件中的 soft_delete
选项设置为 true
:
'soft_delete' => true,
当此配置选项为 true
时,Scout 不会从搜索索引中删除软删除的模型。相反,它将在索引记录上设置一个隐藏的__soft_deleted
属性。然后,您可以在搜索时使用 withTrashed
或 onlyTrashed
方法检索软删除记录:
use App\Models\Order;
// 检索结果包括已删除记录
$orders = Order::search('Star Trek')->withTrashed()->get();
// 仅检索已删除记录...
$orders = Order::search('Star Trek')->onlyTrashed()->get();
技巧:当使用
forceDelete
永久删除软删除模型时,Scout 会自动将其从搜索索引中删除。
自定义搜索引擎
如果需要对引擎的搜索行为执行高级自定义,可以将闭包作为第二个参数传递给 search
方法。例如,在将搜索查询传递给 Algolia 之前,可以使用此回调将地理位置数据添加到搜索选项中:
use Algolia\AlgoliaSearch\SearchIndex;
use App\Models\Order;
Order::search(
'Star Trek',
function (SearchIndex $algolia, string $query, array $options) {
$options['body']['query']['bool']['filter']['geo_distance'] = [
'distance' => '1000km',
'location' => ['lat' => 36, 'lon' => 111],
];
return $algolia->search($query, $options);
}
)->get();
自定义引擎
写引擎
如果内置的 Scout 搜索引擎不能满足你的需求,你可以编写自定义的引擎并且将它注册到 Scout。 你的引擎需要继承 Laravel\Scout\Engines\Engine
抽象类,这个抽象类包含了你自定义的引擎必须要实现的八个方法:
use Laravel\Scout\Builder;
abstract public function update($models);
abstract public function delete($models);
abstract public function search(Builder $builder);
abstract public function paginate(Builder $builder, $perPage, $page);
abstract public function mapIds($results);
abstract public function map(Builder $builder, $results, $model);
abstract public function getTotalCount($results);
abstract public function flush($model);
在Laravel\Scout\Engines\AlgoliaEngine
类里查看这些方法的实现会对你有较大的帮助。这个类会为你在学习如何在自定义引擎中实现这些方法提供一个好的起点。
注册引擎
一旦你写好了自定义引擎,你可以用 Scout 引擎管理的extend
方法将它注册到 Scout。你只需要从App\Providers\AppServiceProvider
下的boot
方法或者应用中使用的任何一个服务提供器中调用 extend
方法。举个例子,如果你写好了一个MySqlSearchEngine
,你可以像这样去注册它:
use App\ScoutExtensions\MySqlSearchEngine
use Laravel\Scout\EngineManager;
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
resolve(EngineManager::class)->extend('mysql', function () {
return new MySqlSearchEngine;
});
}
引擎注册后,你可以在 config/scout.php
,配置文件中指定它为默认的 Scout driver
:
'driver' => 'mysql',
生成宏命令
如果你想要自定义生成器方法,你可以使用Laravel\Scout\Builder
类下的”macro” 方法。 通常,定义「macros」时,需要实现 service provider’s boot
方法:
use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;
use Laravel\Scout\Builder;
/**
* 注册应用的Scout宏命令
*
* @return void
*/
public function boot()
{
Builder::macro('count', function () {
return $this->engine()->getTotalCount(
$this->engine()->search($this)
);
});
}
macro
函数接受一个名字作为第一个参数,第二个参数为一个闭包函数。当调用 Laravel\Scout\Builder
宏命令时,调用这个函数.
use App\Models\Order;
Order::search('Star Trek')->count();