Laravelで全文検索エンジンのalgoliaを使ってみた

[インフォ・ラウンジ Advent Calendar 2023](https://qiita.com/advent-calendar/2023/info-lounge) の23日目です。

## はじめに
普段はElasticSearchやAWSのOpenSearch Serviceを使っているのですが、社内でalgoliaの話を聞いたのでどんなもんかと思い使ってみることにしました。

とりあえず感触を掴みたいだけなので、慣れているPHP/Laravelでやってみることにします。

## Laravelのセットアップ
```bash
composer global require laravel/installer
laravel new algolia

# とりあえずSQLiteを選んでおきます
┌ Which database will your application use? ───────────────────┐
│ SQLite                                                       │
└──────────────────────────────────────────────────────────────┘

cd algolia/

php artisan -V
Laravel Framework 10.38.2
```

## algolia用にScoutパッケージのインストール
```bash
composer require laravel/scout
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
composer require algolia/algoliasearch-client-php
```
ドライバ別に設定が必要なようですが、初期状態でalgoliaが使えるようになっていました。

```php:config/scout.php
'driver' => env('SCOUT_DRIVER', 'algolia'),
```

# ダミーデータの作成
fakerを使います。日本語を試してみたいので設定を書き換えておきます

```php:config/app.php
'faker_locale' => 'ja_JP',
```

Seederでデータ投入します。とりあえずユーザを100人くらい
```php:database/seeders/DatabaseSeeder.php
public function run()
{
    \App\Models\User::factory(100)->create();
}
```

```bash
# DB作成＆投入
php artisan migrate
┌ Would you like to create it? ────────────────────────────────┐
│ Yes                                                          │
└──────────────────────────────────────────────────────────────┘

php artisan db:seed
```
100人のユーザが作成されました
```php
php artisan tinker
Psy Shell v0.11.22 (PHP 8.2.10 — cli) by Justin Hileman
> User::all()
[!] Aliasing 'User' to 'App\Models\User' for this Tinker session.
= Illuminate\Database\Eloquent\Collection {#7355
    all: [
      App\Models\User {#7357
        id: 1,
        name: "吉本 さゆり",
        email: "haruka.sakamoto@example.net",
        email_verified_at: "2023-12-23 02:40:35",
        #password: "$2y$12$H4vyHtxOyoCX7zM0qDNdheJdnpwzhQMQ3jGMNTRph0LsLq0/TaGpa",
        #remember_token: "D3q80KvS7u",
        created_at: "2023-12-23 02:40:36",
        updated_at: "2023-12-23 02:40:36",
      },
      App\Models\User {#7358
        id: 2,
        name: "工藤 舞",
        email: "tanaka.hideki@example.com",
        email_verified_at: "2023-12-23 02:40:36",
        #password: "$2y$12$H4vyHtxOyoCX7zM0qDNdheJdnpwzhQMQ3jGMNTRph0LsLq0/TaGpa",
        #remember_token: "uiXlXq4SDV",
        created_at: "2023-12-23 02:40:36",
        updated_at: "2023-12-23 02:40:36",
      }
・・・（略）
```

## algoliaに投入
アカウント作成等は割愛しますが、ApplicationKeyとAdminKeyを取得してenvに書いておきます。

```ini:.env
ALGOLIA_APP_ID=XXXXXX
ALGOLIA_SECRET=XXXXXXXXXXXXXXXXXXXXXXX
```

atrisanにコマンドが追加されるのでimportします。この程度なら一瞬で終わります。
```
php artisan scout:import "App\Models\User"
Imported [App\Models\User] models up to ID: 100
All [App\Models\User] records have been imported.
```
algoliaのindexを確認してみましょう。

![スクリーンショット 2023-12-23 12.45.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15004/a738ae82-8fe7-28e6-9e0b-b055f6ae1a83.png)

ここでindexの言語設定を変えておきます。Laravel側から変える方法はちょっとわかりませんでした（無いかも）。Index LanguagesとQuery LanguagesにJapaneseを追加します。

![スクリーンショット 2023-12-23 12.02.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15004/ea4ad69c-e5b1-57d1-25e5-5bbbddededdf.png)

algoria上で検索してみます。

![スクリーンショット 2023-12-23 12.04.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15004/191821ed-c24b-0f95-42c9-08867edea8cd.png)

読みで検索できているので、うまくいっているように見えますね。

# Laravel側からalgoriaを使う
Viewを作るのは面倒なので、コマンドで検索機能だけ確認してみましょう。

```php
php artisan tinker
Psy Shell v0.11.22 (PHP 8.2.10 — cli) by Justin Hileman

> User::search('たろう')->get()

[!] Aliasing 'User' to 'App\Models\User' for this Tinker session.
= Illuminate\Database\Eloquent\Collection {#6361
    all: [
      App\Models\User {#7337
        id: 87,
        name: "山口 太郎",
        email: "naoto11@example.org",
        email_verified_at: "2023-12-23 02:40:36",
        #password: "$2y$12$H4vyHtxOyoCX7zM0qDNdheJdnpwzhQMQ3jGMNTRph0LsLq0/TaGpa",
        #remember_token: "4RxlBWJQg4",
        created_at: "2023-12-23 02:40:36",
        updated_at: "2023-12-23 02:40:36",
      },
      App\Models\User {#7335
        id: 10,
        name: "田中 太郎",
        email: "kharada@example.com",
        email_verified_at: "2023-12-23 02:40:36",
        #password: "$2y$12$H4vyHtxOyoCX7zM0qDNdheJdnpwzhQMQ3jGMNTRph0LsLq0/TaGpa",
        #remember_token: "2LO8hxOoWg",
        created_at: "2023-12-23 02:40:36",
        updated_at: "2023-12-23 02:40:36",
      },
    ],
  }
```
できているようですね。

ユーザを追加してみましょう

```php
> $user = new App\Models\User;
= App\Models\User {#7287}

> $user->name = "ジョナサン・ジョースター"
= "ジョナサン・ジョースター"

> $user->email = "jojo@example.com";
= "jojo@example.com"

> $user->password = 'hogefuga'
= "hogefuga"

> $user->save();
= true

> User::search('じょなさん')->get()
= Illuminate\Database\Eloquent\Collection {#7359
    all: [
      App\Models\User {#7860
        id: 101,
        name: "ジョナサン・ジョースター",
        email: "jojo@example.com",
        email_verified_at: null,
        #password: "$2y$12$j/srUBj0AfeL11aZve2aWOeulXYgCIPkBze6bTDHpdbQ8AFFaHpbG",
        #remember_token: null,
        created_at: "2023-12-23 03:08:19",
        updated_at: "2023-12-23 03:08:19",
      },
    ],
  }
```
簡単ですね。save()するだけでalgoriaにも反映されます。


## Scout Extended
ちなみにalgoliaではLaravel用の拡張パッケージが用意されているようなので、インストールしておくと便利そうです。

```
composer require "algolia/scout-extended"
```

機能が色々あるようなのですが、例えば

インデックスのステータスチェック
```bash
php artisan scout:status
 🔎 Analysing information from: [App\Models\User]

 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ----------------- ------- ----------------- --------------- ----------------
  Searchable        Index   Settings          Local records   Remote records
 ----------------- ------- ----------------- --------------- ----------------
  App\Models\User   users   Local not found   101             101
 ----------------- ------- ----------------- --------------- ----------------
```

ダウンタイムなしでのインデックス再生成（一時インデックスを作って差し替え）
```bash
php artisan scout:reimport
 🔎 Importing: [App\Models\User]

 0/3 [░░░░░░░░░░░░░░░░░░░░░░░░░░░░]   0% Creating temporary index temp_users
 2/3 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░]  66% Importing records to index temp_users
 3/3 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% Replacing index users by index temp_users
```
みたいなことができます。ElasticSearchではaliasを使ったりするとなかなか面倒なこともありますが、このような拡張が用意されているのは嬉しいです。

他にも複数モデルにまたがる検索などもできるようなので、ドキュメントを参照してみてください。

# まとめ
全文検索エンジンのalgoliaをLaravelから使ってみました。scoutや拡張機能が用意されているので、普段のLaravel開発から入りやすいなと思いました。細かい設定もかなりあるので、柔軟な対応もできそうです。

## 参考URL

https://laravel.com/docs/10.x/scout

https://www.algolia.com/doc/framework-integration/laravel/getting-started/introduction-to-scout-extended/?client=php

https://www.algolia.com/doc/framework-integration/laravel/production/zero-downtime-reimports/?client=php