Scout 全文搜索
Scout 全文搜索
Laravel Scout
簡(jiǎn)介
Laravel Scout 為 Eloquent 模型 的全文搜索提供了基于驅(qū)動(dòng)的簡(jiǎn)單的解決方案。通過(guò)使用模型觀察者, Scout 會(huì)自動(dòng)同步 Eloquent 記錄的搜索索引。
目前, Scout 自帶一個(gè) Algolia 驅(qū)動(dòng)。不過(guò),編寫自定義驅(qū)動(dòng)也很簡(jiǎn)單,你可以輕松的通過(guò)自己的搜索實(shí)現(xiàn)來(lái)擴(kuò)展 Scout。
安裝
首先,通過(guò) Composer 包管理器來(lái)安裝 Scout:
composer require laravel/scout
Scout 安裝完成后,使用 vendor:publish
Artisan 命令來(lái)生成 Scout 配置文件。這個(gè)命令將在你的 config
目錄下生成一個(gè) scout.php
配置文件。
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
最后,在你要做搜索的模型中添加 Laravel\Scout\Searchable
trait。這個(gè) trait 會(huì)注冊(cè)一個(gè)模型觀察者來(lái)保持模型和所有驅(qū)動(dòng)的同步:
<?php namespace App; use Laravel\Scout\Searchable; use Illuminate\Database\Eloquent\Model; class Post extends Model{ use Searchable; }
隊(duì)列
雖然并不強(qiáng)制使用 Scout,但是在使用這個(gè)庫(kù)之前,強(qiáng)烈建議你配置一個(gè) 隊(duì)列驅(qū)動(dòng),使用它運(yùn)行一個(gè)隊(duì)列來(lái)處理允許 Scout 將模型信息同步到搜索索引的所有操作,為你的應(yīng)用的 web 接口提供更快的響應(yīng)。
一旦你配置了隊(duì)列驅(qū)動(dòng)程序,你的 config/scout.php
配置文件中 queue
選項(xiàng)的值要設(shè)置為 true
:
'queue' => true,
驅(qū)動(dòng)必要條件
Algolia
使用 Algolia 驅(qū)動(dòng)時(shí),需要在 config/scout.php
配置文件配置你的 Algolia id
和 secret
憑證。配置好憑證之后,還需要使用 Composer 包管理器安裝 Algolia PHP SDK:
composer require algolia/algoliasearch-client-php:^2.2
配置
配置模型索引
每個(gè) Eloquent 模型都是通過(guò)給定的 「索引」 進(jìn)行同步,該 「索引」 包含所有可搜索的模型記錄。換句話說(shuō),你可以把每一個(gè) 「索引」 設(shè)想為一張 MySQL 數(shù)據(jù)表。默認(rèn)情況下,每個(gè)模型都會(huì)被持久化到與模型的 「表」 名(通常是模型名稱的復(fù)數(shù)形式)相匹配的索引。你也可以通過(guò)重寫模型上的 searchableAs
方法來(lái)自定義模型的索引:
<?php namespace App; use Laravel\Scout\Searchable; use Illuminate\Database\Eloquent\Model; class Post extends Model{ use Searchable; /** * 獲取索引名稱 * * @return string */ public function searchableAs() { return 'posts_index'; } }
配置可搜索數(shù)據(jù)
默認(rèn)情況下,模型以完整的 toArray
格式持久化到搜索索引。如果要自定義同步到搜索索引的數(shù)據(jù),可以覆蓋模型上的 toSearchableArray
方法:
<?php namespace App; use Laravel\Scout\Searchable; use Illuminate\Database\Eloquent\Model; class Post extends Model{ use Searchable; /** * 獲取模型的可搜索數(shù)據(jù) * * @return array */ public function toSearchableArray() { $array = $this->toArray(); // Customize array... return $array; } }
配置模型 ID
默認(rèn)情況下,Scout 將使用模型的主鍵作為搜索索引中存儲(chǔ)的唯一 ID 。 可以通過(guò)模型上的 getScoutKey
方法自定義:
<?php namespace App; use Laravel\Scout\Searchable; use Illuminate\Database\Eloquent\Model; class User extends Model{ use Searchable; /** * 獲取模型主鍵 * * @return mixed */ public function getScoutKey() { return $this->email; } }
索引
批量導(dǎo)入
如果你想安裝 Scout 到已存在的項(xiàng)目中,你可能已經(jīng)有了想要導(dǎo)入搜索驅(qū)動(dòng)的數(shù)據(jù)庫(kù)記錄。Scout 提供了 Artisan 命令 import
用來(lái)導(dǎo)入所有已存在的記錄到搜索索引:
php artisan scout:import "App\Post"
flush
命令可用于從搜索索引中刪除所有模型的記錄:
php artisan scout:flush "App\Post"
添加記錄
當(dāng)你將 Laravel\Scout\Searchable trait
添加到模型中,你需要做的就是 save
一個(gè)模型實(shí)例,它將自動(dòng)被添加到搜索索引。如果你已經(jīng)將 Scout 配置為 使用隊(duì)列,那這個(gè)操作會(huì)在后臺(tái)由你的隊(duì)列工作進(jìn)程來(lái)執(zhí)行:
$order = new App\Order;// ...$order->save();
通過(guò)查詢添加
如果你想通過(guò) Eloquent 查詢構(gòu)造器將模型集合添加到搜索索引中,你也可以在 Eloquent 查詢構(gòu)造器上鏈?zhǔn)秸{(diào)用 searchable
方法。searchable
會(huì)把構(gòu)造器的查詢 結(jié)果分塊 并且將記錄添加到你的搜索索引里。同樣的,如果你已經(jīng)配置 Scout 為使用隊(duì)列,則所有的數(shù)據(jù)塊將在后臺(tái)由你的隊(duì)列工作進(jìn)程添加:
// 通過(guò) Eloquent 查詢構(gòu)造器增加.. App\Order::where('price', '>', 100)->searchable(); // 你也可以通過(guò)模型關(guān)系增加記錄... $user->orders()->searchable(); // 你也可以通過(guò)集合增加記錄... $orders->searchable();
searchable
方法可以被看做是「更新插入」的操作。換句話說(shuō),如果模型記錄已經(jīng)在你的索引里了,它就會(huì)被更新。如果搜索索引中不存在,則將其添加到索引中。
更新記錄
要更新可搜索的模型,只需要更新模型實(shí)例的屬性并將模型 save
到數(shù)據(jù)庫(kù)。Scout 會(huì)自動(dòng)將更新同步到你的搜索索引中:
$order = App\Order::find(1); // 更新訂單... $order->save();
你也可以在 Eloquent 查詢語(yǔ)句上使用 searchable
方法來(lái)更新一個(gè)模型的集合。如果這個(gè)模型不存在你檢索的索引里,就會(huì)被創(chuàng)建:
// 通過(guò) Eloquent 查詢更新... App\Order::where('price', '>', 100)->searchable(); // 你也可以通過(guò)數(shù)據(jù)間的關(guān)聯(lián)進(jìn)行更新... $user->orders()->searchable(); // 你也可以通過(guò)數(shù)據(jù)集合進(jìn)行更新... $orders->searchable();
刪除記錄
使用 delete
從數(shù)據(jù)庫(kù)中刪除該模型就可以移除索引里的記錄。這種刪除形式甚至與 軟刪除 的模型兼容:
$order = App\Order::find(1); $order->delete();
如果你不希望記錄在刪除之前被檢索到,可以在 Eloquent 查詢實(shí)例或集合上使用 unsearchable
方法:
// 通過(guò) Eloquent 查詢刪除... App\Order::where('price', '>', 100)->unsearchable(); // 你可以通過(guò)數(shù)據(jù)間的關(guān)系進(jìn)行刪除... $user->orders()->unsearchable(); // 你可以通過(guò)數(shù)據(jù)集合進(jìn)行刪除... $orders->unsearchable();
暫停索引
你可能需要在執(zhí)行一批 Eloquent 操作的時(shí)候,不同步模型數(shù)據(jù)到搜索索引。此時(shí)你可以使用 withoutSyncingToSearch
方法來(lái)執(zhí)行此操作。這個(gè)方法接受一個(gè)立即執(zhí)行的回調(diào)。該回調(diào)中所有的操作都不會(huì)同步到模型的索引:
App\Order::withoutSyncingToSearch(function () { // 執(zhí)行模型操作... });
有條件的搜索模型實(shí)例
有時(shí)候你可能需要在某些條件下模型是可搜索的。例如,假設(shè)你有 App\Post
模型可能兩種狀態(tài)之一:「草稿」和「發(fā)布」。你可能只允許搜索 「發(fā)布」過(guò)的帖子。為了實(shí)現(xiàn)這一點(diǎn),你需要在模型中定義一個(gè) shouldBeSearchable
方法:
public function shouldBeSearchable(){ return $this->isPublished(); }
只有在通過(guò) save
方法、查詢或關(guān)聯(lián)模型操作時(shí),才應(yīng)使用 shouldBeSearchable
方法。直接使用 searchable
方法將使模型或集合的可搜索結(jié)果覆蓋 shouldBeSearchable
方法的結(jié)果:
// 此處將遵循 "shouldBeSearchable" 結(jié)果... App\Order::where('price', '>', 100)->searchable(); $user->orders()->searchable();$order->save(); // 此處將覆蓋 "shouldBeSearchable" 結(jié)果... $orders->searchable(); $order->searchable();
搜索
你可以使用 search
方法來(lái)搜索模型。search 方法接受一個(gè)用于搜索模型的字符串。你還需要在搜索查詢上鏈?zhǔn)秸{(diào)用 get
方法,才能用給定的搜索語(yǔ)句查詢與之匹配的 Eloquent 模型:
$orders = App\Order::search('Star Trek')->get();
Scout 搜索返回 Eloquent 模型的集合,因此你可以直接從路由或控制器返回結(jié)果,它們會(huì)被自動(dòng)轉(zhuǎn)換成 JSON 格式:
use Illuminate\Http\Request; Route::get('/search', function (Request $request) { return App\Order::search($request->search)->get(); });
如果你想在它們返回 Eloquent 模型前得到原結(jié)果,你應(yīng)該使用 raw
方法:
$orders = App\Order::search('Star Trek')->raw();
搜索查詢通常會(huì)在模型的 searchableAs
方法指定的索引上執(zhí)行。當(dāng)然,你也可以使用 within
方法指定應(yīng)該搜索的自定義索引:
$orders = App\Order::search('Star Trek') ->within('tv_shows_popularity_desc') ->get();
Where 語(yǔ)句
允許你在搜索查詢中增加簡(jiǎn)單的 "where" 語(yǔ)句。目前,這些語(yǔ)句只支持基本的數(shù)值等式檢查,并且主要是用于根據(jù)租戶 ID 進(jìn)行的范圍搜索查詢。由于搜索索引不是關(guān)系型數(shù)據(jù)庫(kù),因此當(dāng)前不支持更高級(jí)的 "where" 語(yǔ)句:
$orders = App\Order::search('Star Trek')->where('user_id', 1)->get();
分頁(yè)
除了檢索模型的集合,你也可以使用 paginate
方法對(duì)搜索結(jié)果進(jìn)行分頁(yè)。這個(gè)方法會(huì)返回一個(gè)就像 傳統(tǒng)的 Eloquent 查詢分頁(yè) 一樣的 Paginator
實(shí)例:
$orders = App\Order::search('Star Trek')->paginate();
你可以通過(guò)將數(shù)量作為第一個(gè)參數(shù)傳遞給 paginate
方法來(lái)指定每頁(yè)檢索多少個(gè)模型:
$orders = App\Order::search('Star Trek')->paginate(15);
獲取到檢索結(jié)果后,就可以使用 Blade 來(lái)渲染分頁(yè)鏈接和顯示檢索結(jié)果,就像傳統(tǒng)的 Eloquent 查詢分頁(yè)一樣:
<div class="container"> @foreach ($orders as $order) {{ $order->price }} @endforeach </div> {{ $orders->links() }}
軟刪除
如果你的索引模型是 軟刪除 ,并且你需要搜索軟刪除的模型,設(shè)置 config/scout.php
配置文件的 soft_delete
選項(xiàng)的值為 true
:
'soft_delete' => true,
當(dāng)這個(gè)配置選項(xiàng)是 true
的時(shí)候, Scout 不會(huì)從搜索索引中移除軟刪除模型。相反,它會(huì)在索引記錄中設(shè)置一個(gè)隱藏 __soft_deleted
屬性。 然后,在搜索的時(shí)候,你可以使用 withTrashed
或 onlyTrashed
方法檢索軟刪除記錄:
// 搜索結(jié)果包括已刪除的記錄... $orders = App\Order::withTrashed()->search('Star Trek')->get(); // 搜索結(jié)果只含已刪除的記錄... $orders = App\Order::onlyTrashed()->search('Star Trek')->get();
{提示} 要永久刪除模型可以使用
forceDelete
來(lái)刪除,Scout 將自動(dòng)的從搜索索引中移除模型。
自定義搜索引擎
如果需要自定義引擎的搜索行為,可以將回調(diào)作為第二個(gè)參數(shù)傳遞給 search
方法。例如,在將搜索查詢傳遞給 Algolia 之前,可以使用這個(gè)回調(diào)將地理位置數(shù)據(jù)添加到搜索請(qǐng)求中:
use Algolia\AlgoliaSearch\SearchIndex; App\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();
自定義引擎
寫引擎
如果內(nèi)置的 Scout 搜索引擎不能滿足你的需求,你可以編寫自定義的引擎并且將它注冊(cè)到 Scout。你的引擎需要繼承 Laravel\Scout\Engines\Engine
抽象類,這個(gè)抽象類包含了你自定義的引擎必須要實(shí)現(xiàn)的七個(gè)方法:
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($results, $model); abstract public function getTotalCount($results); abstract public function flush($model);
在 Laravel\Scout\Engines\AlgoliaEngine
類里查看這些方法的實(shí)現(xiàn)會(huì)對(duì)你有較大的幫助。這個(gè)類會(huì)為你在學(xué)習(xí)如何在自定義引擎中實(shí)現(xiàn)這些方法提供一個(gè)好的起點(diǎn)。
注冊(cè)引擎
一旦你寫好了自定義引擎,你可以用 Scout 引擎管理的 extend
方法將它注冊(cè)到 Scout。你只需要從 AppServiceProvider
下的 boot
方法或者應(yīng)用中使用的任何一個(gè)服務(wù)提供器中調(diào)用 extend
方法。舉個(gè)例子,如果你寫好了一個(gè) MySqlSearchEngine
,你可以像這樣去注冊(cè)它:
use Laravel\Scout\EngineManager; /** * 啟動(dòng)任何的服務(wù) * * @return void */ public function boot(){ resolve(EngineManager::class)->extend('mysql', function () { return new MySqlSearchEngine; }); }
引擎注冊(cè)后,你可以在 config/scout.php
配置文件中指定它為默認(rèn)的 Scout driver
:
'driver' => 'mysql',
生成宏命令
如果你想要自定義生成器方法,你可以使用 Laravel\Scout\Builder
類下的 macro
方法。通常,定義 "macros" 時(shí), 需要實(shí)現(xiàn) service provider's boot
方法:
<?php namespace App\Providers; use Laravel\Scout\Builder; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Response; class ScoutMacroServiceProvider extends ServiceProvider{ /** * 注冊(cè)應(yīng)用的Scout 宏命令. * * @return void */ public function boot() { Builder::macro('count', function () { return $this->engine->getTotalCount( $this->engine()->search($this) ); }); } }
macro
函數(shù)接受一個(gè)名字作為第一個(gè)參數(shù),第二個(gè)參數(shù)為一個(gè)閉包函數(shù)。當(dāng)
調(diào)用 Laravel\Scout\Builder
宏命令時(shí),調(diào)用這個(gè)函數(shù).
App\Order::search('Star Trek')->count();