Laravel 8.x Laravel Scout

イントロダクション

Laravel Scout(Scout、斥候)は、Eloquentモデルへ、シンプルなドライバベースのフルテキストサーチを提供します。モデルオブサーバを使い、Scoutは検索インデックスを自動的にEloquentレコードと同期します。

現在、ScoutはAlgoliaMeiliSearchドライバを用意しています。さらに、Scoutは、ローカル開発用に設計した"collection"ドライバも用意しており、これは外部依存やサードパーティのサービスを必要としません。さらに、カスタムドライバの記述も簡単で、独自な検索実装を行い自由にScoutを拡張できます。

インストール

最初に、Composerパッケージマネージャを使い、Scoutをインストールします。

composer require laravel/scout

Scoutをインストールした後、vendor:publish Artisanコマンドを実行してScout設定ファイルをリソース公開する必要があります。このコマンドは、scout.php設定ファイルをアプリケーションのconfigディレクトリへリソース公開します。

php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

最後に、検索可能にしたいモデルにLaravel\Scout\Searchableトレイトを追加します。このトレイトは、モデルを検索ドライバと自動的に同期させるモデルオブザーバを登録します。

<?php

namespace App\Models;

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

class Post extends Model
{
    use Searchable;
}

ドライバの事前要件

Algolia

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

composer require algolia/algoliasearch-client-php

MeiliSearch

MeiliSearchは、非常に高速なオープンソースの検索エンジンです。ローカルマシンにMeiliSearchをインストールする方法がわからない場合は、Laravelの公式サポートのDocker開発環境であるLaravel Sailを利用できます。

Meilisearchドライバを使用する場合は、Composerパッケージマネージャを使用して、MeiliSearch PHP SDKをインストールする必要があります。

composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle

次に、アプリケーションの.envファイル内のSCOUT_DRIVER環境変数とMeiliSearchhostkey認証情報を設定します。

SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey

MeiliSearchの詳細については、MeiliSearchのドキュメントを参照してください。

さらに、MeiliSearchのバイナリ互換のドキュメントを見て、自分が使っているMeiliSearchのバイナリバージョンと互換性のあるバージョンのmeilisearch/meilisearch-phpをインストールしてください。

Note: MeiliSearchを利用しているアプリケーションのScoutをアップグレードする際には、常にMeiliSearchサービス自体に追加の破壊的な変更がないか確認する必要があります。

キュー投入

厳密にはScoutを使用する必要はありませんが、ライブラリを使用する前に、キュードライバの設定を強く考慮する必要があります。キューワーカを実行すると、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/キーとして使用します。この動作をカスタマイズする必要がある場合は、モデルの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に渡されるため、これらのデータはそのユーザーが行った検索リクエストへ関連付けられます。

ローカル開発

ローカル開発時には、AlgoliaやMeiliSearchの検索エンジンを自由に使用することができますが、「コレクション(collection)」エンジンでスタートした方が便利な場合もあります。コレクション・エンジンは、既存データベースからの結果に対して、「where」節とコレクション・フィルタリングを用いて、クエリに該当する検索結果を決定します。このエンジンを使用する場合、Searchableモデルをインデックス化する必要はなく、シンプルにローカル・データベースから検索します。

コレクションエンジンを使用するには,環境変数SCOUT_DRIVERの値をcollectionに設定するか,アプリケーションのscout設定ファイルでcollectionドライバを直接指定します。

SCOUT_DRIVER=collection

コレクションドライバを使用ドライバに指定したら、モデルに対して検索クエリの実行を開始できます。コレクションエンジンを使用する場合、AlgoliaやMeiliSearchのインデックスのシードに必要なインデックス作成などの検索エンジンのインデックス作成は不要です。

インデックス

バッチ取り込み

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トレイトを追加したら、モデルインスタンスを保存または作成するだけで、検索インデックスに自動的に追加されます。キューを使用するようにScoutを設定した場合、この操作はキューワーカによってバックグラウンドで実行されます。

use App\Models\Order;

$order = new Order;

// ...

$order->save();

クエリによるレコード追加

Eloquentクエリを介してモデルのコレクションを検索インデックスに追加する場合は、searchableメソッドをEloquentクエリにチェーンできます。searchableメソッドはクエリの結果をチャンクし、レコードを検索インデックスに追加します。繰り返しますが、キューを使用するようにScoutを設定した場合、すべてのチャンクはキューワーカによってバックグラウンドでインポートされます。

use App\Models\Order;

Order::where('price', '>', 100)->searchable();

Eloquentリレーションインスタンスで searchableメソッドを呼び出すこともできます。

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

または、メモリ内にEloquentモデルのコレクションが既にある場合は、コレクションインスタンスでsearchableメソッドを呼び出して、モデルインスタンスを対応するインデックスに追加できます。

$orders->searchable();

Tip!! 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モデルが、"draft"か"published"の2つのうち、どちらか1つの状態を取ると想像してください。「公開済み:published」のポストのみ検索可能にする必要があります。これを実現するには、モデルにshouldBeSearchableメソッドを定義してください。

/**
 * モデルを検索可能にする判定
 *
 * @return bool
 */
public function shouldBeSearchable()
{
    return $this->isPublished();
}

shouldBeSearchableメソッドは、saveおよびcreateメソッド、クエリ、またはリレーションを通してモデルを操作する場合にのみ適用されます。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')->paginate();
    ->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メソッドの第1引数として、各ページごとに取得したいモデル数を指定します。

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

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

<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属性をセットします。これにより、検索時にソフトデリート済みレコードを取得するために、withTrashedonlyTrashedメソッドがつかえます。

use App\Models\Order;

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

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

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

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

エンジンの検索動作の高度なカスタマイズを実行する必要がある場合は、 searchメソッドの2番目の引数にクロージャを渡せます。たとえば、このコールバックを使用して、検索クエリが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抽象クラスを拡張してください。この抽象クラスは、カスタムエンジンが実装する必要のある、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へ登録します。Scoutのエンジンマネージャは、Laravelサービスコンテナが依存解決できます。App\Providers\AppServiceProviderクラスのbootメソッドまたはアプリケーションが使用している他のサービスプロバイダからextendメソッドを呼び出せます。

use App\ScoutExtensions\MySqlSearchEngine
use Laravel\Scout\EngineManager;

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

エンジンを登録したら、アプリケーションのconfig/scout.php設定ファイルでデフォルトのスカウトdriverとして指定できます。

'driver' => 'mysql',

ビルダマクロ

カスタムのScout検索ビルダメソッドを定義する場合は、Laravel\Scout\Builderクラスでmacroメソッドが使用できます。通常、「マクロ」はサービスプロバイダbootメソッド内で定義する必要があります。

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

/**
 * 全アプリケーションサービスの初期起動処理
 *
 * @return void
 */
public function boot()
{
    Builder::macro('count', function () {
        return $this->engine->getTotalCount(
            $this->engine()->search($this)
        );
    });
}

macro関数は、最初の引数にマクロ名、2番目の引数にクロージャを取ります。マクロのクロージャは、Laravel\Scout\Builder実装からマクロ名を呼び出すときに実行されます。

use App\Models\Order;

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)へ移動

その他

?

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