Laravel 6.x 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トレイトを追加します。このトレイトはモデルオブザーバを登録し、サーチドライバとモデルの同期を取り続けます。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;
}

キュー

Scoutを厳格(リアルタイム)に利用する必要がないのであれば、このライブラリを使用する前にキュードライバの設定を考えてみるべきでしょう。キューワーカの実行により、モデルの情報を検索インデックスに同期する全操作をキューイングでき、アプリケーションのWebインターフェイスのレスポンス時間を改善できるでしょう。

キュードライバを設定したら、config/scout.php設定ファイルのqueueオプション値をtrueに設定してください。

'queue' => true,

ドライバの事前要件

Algolia

Algoliaドライバを使用する場合、Algolia idsecret接続情報をconfig/scout.php設定ファイルで設定する必要があります。接続情報を設定し終えたら、Algolia PHP SDKをComposerパッケージマネージャで、インストールする必要があります。

composer require algolia/algoliasearch-client-php:^2.2

設定

モデルインデックスの設定

各Eloquentモデルは、検索可能レコードすべてを含む、指定された検索「インデックス」と同期されます。言い換えれば、各インデックスはMySQLテーブルのようなものであると、考えられます。デフォルトで、各モデルはそのモデルの典型的な「テーブル」名に一致するインデックスへ保存されます。通常、モデルの複数形ですが、モデルのsearchableAsメソッドをオーバーライドすることで、このモデルのインデックスを自由にカスタマイズ可能です。

<?php

namespace App;

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;

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として使用します。この振る舞いをカスタマイズしたい場合は、モデルのgetScoutKeygetScoutKeyNameメソッドをオーバーライドしてください。

<?php

namespace App;

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をインストールする場合、検索ドライバへ取り込むために必要なデータベースレコードは、すでに存在しています。Scoutは既存の全レコードを検索インデックスへ取り込むために使用する、import Artisanコマンドを提供しています。

php artisan scout:import "App\Post"

flushコマンドは、検索インデックスからモデルの全レコードを削除するために使用します。

php artisan scout:flush "App\Post"

レコード追加

モデルにLaravel\Scout\Searchableトレイトを追加したら、必要なのはモデルインスタンスをsaveすることです。これにより自動的に検索インデックスへ追加されます。Scoutでキューを使用する設定にしている場合は、この操作はキューワーカにより、バックグランドで実行されます。

$order = new App\Order;

// ...

$order->save();

クエリによる追加

Eloquentクエリにより、検索インデックスへモデルのコレクションを追加したい場合は、Eloquentクエリにsearchableメソッドをチェーンします。searchableメソッドは、クエリの結果をチャンクへ分割し、レコードを検索エンジンへ追加します。この場合も、Scoutでキューを使用する設定をしていれば、キューワーカが全チャンクをバックグランドで追加します。

// Eloquentクエリにより追加
App\Order::where('price', '>', 100)->searchable();

// リレーションにより、レコードを追加することもできる
$user->orders()->searchable();

// コレクションにより、追加することもできる
$orders->searchable();

searchableメソッドは"upsert(update+insert)"操作と考えられます。言い換えれば、モデルレコードがインデックスへすでに存在していれば、更新されます。検索エンジンに存在していなければ、インデックスへ追加されます。

レコード更新

検索可能モデルを更新するには、モデルインスタンスのプロパティを更新し、saveでモデルをデータベースへ保存します。Scoutは自動的に変更を検索インデックスへ保存します。

$order = App\Order::find(1);

// 注文を更新…

$order->save();

モデルのコレクションを更新するためにも、Eloquentクエリのsearchableメソッドを使用します。検索エンジンにモデルが存在していない場合は、作成します。

// Eloquentクエリによる更新
App\Order::where('price', '>', 100)->searchable();

// リレーションによる更新も可能
$user->orders()->searchable();

// コレクションによる更新も可能
$orders->searchable();

レコード削除

インデックスからレコードを削除するには、データベースからモデルをdeleteで削除します。この形態による削除は、モデルのソフト削除と互換性があります。

$order = App\Order::find(1);

$order->delete();

レコードを削除する前に、モデルを取得したくない場合は、Eloquentクエリインスタンスかコレクションに対し、unsearchableメソッドを使用します。

// Eloquentクエリによる削除
App\Order::where('price', '>', 100)->unsearchable();

// リレーションによる削除も可能
$user->orders()->unsearchable();

// コレクションによる削除も可能
$orders->unsearchable();

インデックスの一時停止

Eloquentモデルをバッチ処理するが、検索インデックスへモデルデータを同期したくない場合もときどきあります。withoutSyncingToSearchメソッドを使用することで可能です。このメソッドは、即時に実行されるコールバックを1つ引数に取ります。コールバック中のモデル操作は、インデックスへ同期されることはありません。

App\Order::withoutSyncingToSearch(function () {
    // モデルアクションの実行…
});

条件付き検索可能モデルインスタンス

特定の条件下でのみ、モデルを検索可能にする必要がある場合も起きるでしょう。たとえば、App\Postモデルが、"draft"か"published"の2つのうち、どちらか1つの状態を取ると想像してください。「公開済み:published」のモデルのみ検索可能にする必要があります。これを実現するには、モデルにshouldBeSearchableメソッドを定義してください。

public function shouldBeSearchable()
{
    return $this->isPublished();
}

shouldBeSearchableメソッドは、saveメソッド、クエリ、リレーションによるモデル操作の場合のみ適用されます。searchableメソッドを使用し、直接searchableなモデルかコレクションを作成する場合は、shouldBeSearchableメソッドの結果をオーバーライドします。

// "shouldBeSearchable"が利用される
App\Order::where('price', '>', 100)->searchable();

$user->orders()->searchable();

$order->save();

// "shouldBeSearchable"はオーバーライドされる
$orders->searchable();

$order->searchable();

検索

searchメソッドにより、モデルの検索を開始しましょう。searchメソッドはモデルを検索するために使用する文字列だけを引数に指定します。getメソッドを検索クエリにチェーンし、指定した検索クエリに一致するEloquentモデルを取得できます。

$orders = App\Order::search('Star Trek')->get();

Scoutの検索ではEloquentモデルのコレクションが返されるため、ルートやコントローラから直接結果を返せば、自動的にJSONへ変換されます。

use Illuminate\Http\Request;

Route::get('/search', function (Request $request) {
    return App\Order::search($request->search)->get();
});

Eloquentモデルにより変換される前の、結果をそのまま取得したい場合は、rawメソッドを使用してください。

$orders = App\Order::search('Star Trek')->raw();

検索クエリは通常、モデルのsearchableAsメソッドに指定されたインデックスを使い実行されます。しかし、その代わりに検索で使用するカスタムインデックスをwithinメソッドで使用することもできます。

$orders = App\Order::search('Star Trek')
    ->within('tv_shows_popularity_desc')
    ->get();

Where節

Scoutは検索クエリに対して"WHERE"節を単に追加する方法も提供しています。現在、この節としてサポートしているのは、基本的な数値の一致を確認することだけで、主にIDにより検索クエリを絞り込むために使用します。検索インデックスはリレーショナル・データベースではないため、より上級の"WHERE"節は現在サポートしていません。

$orders = App\Order::search('Star Trek')->where('user_id', 1)->get();

ペジネーション

コレクションの取得に付け加え、検索結果をpaginateメソッドでページづけできます。このメソッドは、Paginatorインスタンスを返しますので、Eloquentクエリのペジネーションと同様に取り扱えます。

$orders = App\Order::search('Star Trek')->paginate();

paginateメソッドの第1引数として、各ページごとに取得したいモデル数を指定します。

$orders = App\Order::search('Star Trek')->paginate(15);

結果が取得できたら、通常のEloquentクエリのペジネーションと同様に、結果を表示し、Bladeを使用してページリンクをレンダーできます。

<div class="container">
    @foreach ($orders as $order)
        {{ $order->price }}
    @endforeach
</div>

{{ $orders->links() }}

ソフトデリート

インデックス付きのモデルがソフトデリートされ、ソフトデリート済みのモデルをサーチする必要がある場合、config/scout.php設定ファイルのsoft_deleteオプションをtrueに設定してください。

'soft_delete' => true,

この設定オプションをtrueにすると、Scoutは検索インデックスからソフトデリートされたモデルを削除しません。代わりに、インデックスされたレコードへ、隠し__soft_deleted属性をセットします。これにより、検索時にソフトデリート済みレコードを取得するために、withTrashedonlyTrashedメソッドがつかえます。

// 結果の取得時に、削除済みレコードも含める
$orders = App\Order::search('Star Trek')->withTrashed()->get();

// 結果の取得時に、削除済みレコードのみを対象とする
$orders = App\Order::search('Star Trek')->onlyTrashed()->get();

Tip!! ソフトデリートされたモデルが、forceDeleteにより完全に削除されると、Scoutは自動的に検索インデックスから削除します。

エンジンの検索のカスタマイズ

エンジンの検索の振る舞いをカスタマイズする必要があれば、searchメソッドの第2引数にコールパックを渡してください。たとえば、Algoliaへサーチクエリが渡される前に、サーチオプションにgeo-locationデータを追加するために、このコールバックが利用できます。

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();

カスタムエンジン

エンジンのプログラミング

組み込みのScout検索エンジンがニーズに合わない場合、独自のカスタムエンジンを書き、Scoutへ登録してください。エンジンは、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クラスが役に立つでしょう。このクラスは独自エンジンで、各メソッドをどのように実装すればよいかの、良い取り掛かりになるでしょう。

エンジンの登録

カスタムエンジンを書いたら、Scoutエンジンマネージャのextendメソッドを使用し、Scoutへ登録します。AppServiceProviderかアプリケーションで使用している他のサービスプロバイダのbootメソッドで、extendメソッドを呼び出してください。たとえば、MySqlSearchEngineを書いた場合、次のように登録します。

use Laravel\Scout\EngineManager;

/**
 * 全アプリケーションサービスの登録
 *
 * @return void
 */
public function boot()
{
    resolve(EngineManager::class)->extend('mysql', function () {
        return new MySqlSearchEngine;
    });
}

エンジンが登録できたら、Scoutのデフォルトdriverとして、config/scout.php設定ファイルで設定します。

'driver' => 'mysql',

ビルダマクロ

カスタムビルダメソッドを定義したい場合は、Laravel\Scout\Builderクラスのmacroメソッドを使用してください。通常、「マクロ」はサービスプロバイダbootメソッドの中で定義します。

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;
use Laravel\Scout\Builder;

class ScoutMacroServiceProvider extends ServiceProvider
{
    /**
     * アプリケーションのScoutマクロ登録
     *
     * @return void
     */
    public function boot()
    {
        Builder::macro('count', function () {
            return $this->engine->getTotalCount(
                $this->engine()->search($this)
            );
        });
    }
}

macro関数の最初の引数は、名前を渡します。第2引数はクロージャです。マクロのクロージャはLaravel\Scout\Builder実装から、そのマクロ名を呼び出されたときに実行されます。

App\Order::search('Star Trek')->count();

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる