Laravel Scout 是一个基于驱动的简洁方案,用于给 Eloquent 模型 添加全文检索能力。它通过模型观察者,自动同步 Eloquent 记录与搜索索引。
Scout 内置了 database 引擎,使用 MySQL / PostgreSQL 的全文索引和 LIKE 子句直接搜索数据库,无需外部服务。大规模生产环境中若需要拼写容错、分面搜索、地理搜索,则可以选择外部引擎。
支持的引擎一览
引擎 特点 适用场景 database使用 MySQL / PostgreSQL 全文索引 大多数应用 collection在 PHP 内过滤(含 SQLite 也可) 本地开发、测试 Meilisearch 开源,支持拼写容错,速度快 生产环境 Algolia 云端 SaaS,功能丰富 生产环境 Typesense 开源,支持向量检索 生产环境
通过 Composer 安装。
composer require laravel/scout
安装后,使用 vendor:publish 命令发布配置文件,会生成 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 ;
}
配置队列
使用 database 或 collection 之外的引擎时,强烈建议先配置好队列驱动 。启动队列 worker 后,索引同步会在后台执行,Web 界面的响应速度将大幅提升。
将 config/scout.php 中的 queue 设为 true。
也可以指定连接和队列名。
'queue' => [
'connection' => 'redis' ,
'queue' => 'scout'
],
配置好后启动专用的队列 worker。
php artisan queue:work redis --queue=scout
使用唯一任务
在写入密集的应用中,你可能希望避免同一模型记录的重复索引任务进入队列。在 config/scout.php 中注册 MakeSearchableUniquely 和 RemoveFromSearchUniquely 任务类,就能使用唯一化的索引任务。通常在服务提供者的 boot 方法中设置。
use Laravel\Scout\Jobs\ MakeSearchableUniquely ;
use Laravel\Scout\Jobs\ RemoveFromSearchUniquely ;
use Laravel\Scout\ Scout ;
Scout :: makeSearchableUsing ( MakeSearchableUniquely :: class );
Scout :: removeFromSearchUsing ( RemoveFromSearchUniquely :: class );
这些任务使用 Laravel 的唯一任务锁 ,防止已在队列中的同一模型记录被重复派发。
各驱动的准备工作
Algolia
使用 Algolia 驱动时,请在 config/scout.php 中配置 id 与 secret,并安装 Algolia PHP SDK。
composer require algolia/algoliasearch-client-php
在 .env 中配置凭证。
ALGOLIA_APP_ID =your-app-id
ALGOLIA_SECRET =your-secret-key
索引配置
Algolia 可以通过 config/scout.php 管理索引设置。
use App\Models\ User ;
'algolia' => [
'id' => env ( 'ALGOLIA_APP_ID' , '' ),
'secret' => env ( 'ALGOLIA_SECRET' , '' ),
'index-settings' => [
User :: class => [
'searchableAttributes' => [ 'id' , 'name' , 'email' ],
'attributesForFaceting' => [ 'filterOnly(email)' ],
],
],
],
配置完毕后执行 scout:sync-index-settings 命令,让 Algolia 生效。
php artisan scout:sync-index-settings
Meilisearch
Meilisearch 是一款高性能的开源搜索引擎。本地开发中最简单的方法是使用 Laravel Sail 中的 Docker 服务。
# 使用 Laravel Sail
./vendor/bin/sail up -d meilisearch
不使用 Sail 时,可以直接用 Docker 启动。
docker run -it --rm \
-p 7700:7700 \
getmeili/meilisearch:latest \
meilisearch --master-key= "masterKey"
安装 Meilisearch PHP SDK。
composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle
在 .env 中配置驱动与主机。
SCOUT_DRIVER =meilisearch
MEILISEARCH_HOST =http://127.0.0.1:7700
MEILISEARCH_KEY =masterKey
升级 Scout 时请务必查阅 Meilisearch 本身的破坏性变更 。
索引配置(Meilisearch)
在 Meilisearch 中,用于 where() 过滤的字段必须提前注册到 filterableAttributes,用于 orderBy() 排序的字段则注册到 sortableAttributes。
use App\Models\ User ;
'meilisearch' => [
'host' => env ( 'MEILISEARCH_HOST' , 'http://localhost:7700' ),
'key' => env ( 'MEILISEARCH_KEY' , null ),
'index-settings' => [
User :: class => [
'filterableAttributes' => [ 'id' , 'name' , 'email' ],
'sortableAttributes' => [ 'created_at' ],
],
],
],
注意数值列的数据类型。Meilisearch 只会对类型正确的字段执行 >、< 之类的过滤操作。
public function toSearchableArray () : array
{
return [
'id' => ( int ) $this -> id ,
'name' => $this -> name ,
'price' => ( float ) $this -> price ,
];
}
设置完毕后执行 scout:sync-index-settings。
php artisan scout:sync-index-settings
Typesense
Typesense 是一款高性能的开源搜索引擎,支持关键字、语义、地理及向量检索。
composer require typesense/typesense-php
在 .env 中配置连接信息。
SCOUT_DRIVER =typesense
TYPESENSE_API_KEY =masterKey
TYPESENSE_HOST =localhost
TYPESENSE_PORT =8108
TYPESENSE_PATH =
TYPESENSE_PROTOCOL =http
使用 Typesense 时,toSearchableArray 需要把模型主键 cast 为字符串,把创建时间 cast 为 UNIX 时间戳。
public function toSearchableArray () : array
{
return array_merge ( $this -> toArray (), [
'id' => ( string ) $this -> id ,
'created_at' => $this -> created_at -> timestamp ,
]);
}
database / collection 引擎
如果希望在不引入外部服务的情况下添加搜索功能,这两种就是最合适的选项。
database 引擎 使用 MySQL / PostgreSQL 的全文索引和 LIKE 子句。多数应用足够使用。
collection 引擎 在 PHP 中过滤,因此可在 Laravel 支持的所有数据库(包括 SQLite)上运行。适合本地开发、测试或小数据集。
数据库引擎不像外部引擎那样需要手动管理索引,它直接搜索数据库表。
Searchable trait
自定义 toSearchableArray()
默认情况下,模型的 toArray() 全部数据都会写入搜索索引。要自定义同步到索引的数据,可以重写 toSearchableArray 方法。
<? php
namespace App\Models ;
use Illuminate\Database\Eloquent\ Model ;
use Laravel\Scout\ Searchable ;
class Post extends Model
{
use Searchable ;
/**
* 获取要索引的数据数组。
*
* @return array < string , mixed>
*/
public function toSearchableArray () : array
{
$array = $this -> toArray ();
// 自定义数据...
return $array ;
}
}
自定义索引名
默认情况下模型的表名(复数)会被用作索引名。可以通过重写 searchableAs 来自定义。
public function searchableAs () : string
{
return 'posts_index' ;
}
database 引擎的搜索策略
在 database 引擎中,可以通过 PHP Attribute 为每列指定高效的搜索策略。
use Laravel\Scout\Attributes\ SearchUsingFullText ;
use Laravel\Scout\Attributes\ SearchUsingPrefix ;
#[ SearchUsingPrefix ([ 'id' , 'email' ])]
#[ SearchUsingFullText ([ 'bio' ])]
public function toSearchableArray () : array
{
return [
'id' => $this -> id ,
'name' => $this -> name ,
'email' => $this -> email ,
'bio' => $this -> bio ,
];
}
使用 SearchUsingFullText 之前,请确认目标字段已建立全文索引 。
有条件地允许搜索
若只想在特定条件下让模型可被搜索,可以定义 shouldBeSearchable 方法。
/**
* 判断该模型是否应可被搜索。
*/
public function shouldBeSearchable () : bool
{
return $this -> isPublished ();
}
shouldBeSearchable 对 database 引擎无效。要在 database 引擎上实现类似效果,请使用where 子句 。
索引管理
本节命令主要针对 Algolia、Meilisearch、Typesense 等外部引擎。database 引擎无需管理索引。
导入已有记录
在已有项目中引入 Scout 时,可以使用 scout:import 命令把已有记录导入索引。
php artisan scout:import "App\Models\Post"
也可以通过队列在后台导入。
php artisan scout:queue-import "App\Models\Post" --chunk=500
清空索引
要从索引中删除某模型的全部记录,可以使用 scout:flush。
php artisan scout:flush "App\Models\Post"
暂停索引同步
想在一段 Eloquent 操作期间临时停止与索引的同步,使用 withoutSyncingToSearch。
use App\Models\ Order ;
Order :: withoutSyncingToSearch ( function () {
// 内部的模型操作都不会同步到搜索索引
});
手动添加 / 删除记录
可以基于查询把一批模型加入索引。
// 加入查询结果
Order :: where ( 'price' , '>' , 100 ) -> searchable ();
// 通过关联加入
$user -> orders () -> searchable ();
从索引中移除记录使用 unsearchable。
Order :: where ( 'price' , '>' , 100 ) -> unsearchable ();
模型 delete 时会自动从索引中删除。
search 方法用来搜索模型。再链上 get 就可以得到 Eloquent 模型集合。
use App\Models\ Order ;
$orders = Order :: search ( 'Star Trek' ) -> get ();
在控制器或路由中直接返回时会自动转 JSON。
use Illuminate\Http\ Request ;
Route :: get ( '/search' , function ( Request $request ) {
return Order :: search ( $request -> search ) -> get ();
});
如需拿到原始搜索结果,使用 raw 方法。
$orders = Order :: search ( 'Star Trek' ) -> raw ();
paginate 方法用来对搜索结果做分页,用法与普通 Eloquent 查询一致。
$orders = Order :: search ( 'Star Trek' ) -> paginate ();
// 指定每页条数
$orders = Order :: search ( 'Star Trek' ) -> paginate ( 15 );
在 database 引擎下也可以使用 simplePaginate,因为它不计算总数,对大数据集更高效。
$orders = Order :: search ( 'Star Trek' ) -> simplePaginate ( 15 );
Blade 模板中的展示示例:
< div class = "container" >
@foreach ($orders as $order)
{{ $order->price }}
@endforeach
</ div >
{{ $orders->links() }}
过滤与排序
where 方法可以给搜索查询添加过滤条件。
use App\Models\ Order ;
// 相等过滤
$orders = Order :: search ( 'Star Trek' ) -> where ( 'user_id' , 1 ) -> get ();
// 在数组内任一匹配
$orders = Order :: search ( 'Star Trek' ) -> whereIn ( 'status' , [ 'open' , 'paid' ]) -> get ();
// 不在数组内
$orders = Order :: search ( 'Star Trek' ) -> whereNotIn ( 'status' , [ 'closed' ]) -> get ();
使用 Meilisearch 时,在使用 where 前必须配置可过滤字段 。
query 方法用来自定义 Eloquent 查询。
use Illuminate\Database\Eloquent\ Builder ;
$orders = Order :: search ( 'Star Trek' )
-> query ( fn ( Builder $query ) => $query -> with ( 'invoices' ))
-> get ();
Eager Loading
Scout 会先从搜索引擎获得 ID 列表,再用 Eloquent 取回模型。要避免 N+1 问题,可以在 query 方法内使用 with() 提前加载。
use App\Models\ Order ;
use Illuminate\Database\Eloquent\ Builder ;
$orders = Order :: search ( 'Star Trek' )
-> query ( fn ( Builder $query ) => $query -> with ([ 'invoices' , 'user' ]))
-> get ();
批量导入时若也需要 Eager Load 关联,可以定义 makeAllSearchableUsing。
use Illuminate\Database\Eloquent\ Builder ;
protected function makeAllSearchableUsing ( Builder $query ) : Builder
{
return $query -> with ( 'author' );
}
makeAllSearchableUsing 在通过队列进行批量导入时可能不生效。队列任务处理模型集合时不会恢复关联。
软删除
如果被索引的模型使用了软删除 ,并希望在搜索时也能搜到已删除的模型,请把 config/scout.php 的 soft_delete 设为 true。
启用后可以通过 withTrashed 或 onlyTrashed 搜索已删除记录。
// 包含已删除
$orders = Order :: search ( 'Star Trek' ) -> withTrashed () -> get ();
// 仅搜索已删除
$orders = Order :: search ( 'Star Trek' ) -> onlyTrashed () -> get ();
自定义引擎
若内置引擎不能满足需求,可以自行实现自定义引擎。自定义引擎需继承 Laravel\Scout\Engines\Engine 抽象类,并实现下面 8 个方法。
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 类。
创建的自定义引擎需要在 App\Providers\AppServiceProvider 的 boot 方法中注册。
use App\ScoutExtensions\ MySqlSearchEngine ;
use Laravel\Scout\ EngineManager ;
public function boot () : void
{
resolve ( EngineManager :: class ) -> extend ( 'mysql' , function () {
return new MySqlSearchEngine ;
});
}
注册后在 config/scout.php 中作为驱动使用。
相关页面
Eloquent ORM 了解 Eloquent 模型的基础用法。
Eloquent 关联 了解关联定义与 Eager Loading。