LaravelからSupabaseでPGroongaを使う
SupabaseをLaravelから使う方法
Dさん:「いや〜、Laravelでアプリを開発する方法は色々ありますが、やっぱり一番興奮するのはマネージドなDBで高速日本語全文検索のPGroonga
を使えた時ですね!」
Tさん:『間違いないね!』
サインアップ
まずはsupabaseにサインアップしてアカウントを作成し、適当に組織を作り、下記のようなダッシュボードにアクセスします
上図のように Project Settings > Database > Database Setting の箇所にLaravelからDBに接続する際に必要な情報が書かれているので、これを使います
(これはあくまでサンプルなので、既にこのスクリーンショットのプロジェクトは消してあります)
.env
へのDB設定
Laravelの自分のLaravelの.env
ファイルにダッシュボードでメモした設定値を下記のように入れて行きます(それぞれの箇所は自分の値に直してね)
DB_CONNECTION=pgsql
DB_HOST=db.phimnigkzjblpukeobou.supabase.co
DB_PORT=5432
DB_DATABASE=postgres
DB_USERNAME=postgres
DB_PASSWORD=自分のパスワード
PGroongaを使っていくぞ!
いく🐘!
ExtensionをONにする
Supabaseは日本語検索に強いPGroonga
を使える唯一のマネージドなDBなので、使います!
左側のDatabaseのExtensionを開いて検索フィールドにPGroonga
と入れると出てきます(左側のPGroongaを選択してね)
Schemaはpublicを選択
データテーブルは基本public
スキーマに作られるので、public
にExtensionをONにしておきます
Laravel側でテーブル作成やダミーデータ作成
雛形(Laravel Breeze)準備
画面生成がしやすいのでLaravel Breezeとその日本語化パッケージを入れておきます
composer require laravel/breeze --dev
composer require askdkc/breezejp --dev
php artisan breeze:install blade
php artisan breezejp
これでLaravel Breezeと日本語化が完了しました👍🇯🇵(Breezejpは便利なのでGithubのリポジトリにスターしてあげましょう💝)
migrationとfactoryの作成
サンプルとしてブログ記事(Post)モデルを作って行きます
php artisan make:model -mf Post
database/migrations/yyyy_mm_dd_ssssss_create_posts_table.php
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->text('title');
$table->text('body')->nullable();
$table->timestamps();
});
}
database/factories/PostFactory.php
public function definition(): array
{
return [
'title' => $this->faker->realText(20),
'body' => $this->faker->realText(200),
];
}
database/seeders/DatabaseSeeder.php
public function run(): void
{
for ($i = 0; $i < 100; $i++) {
\App\Models\Post::factory(100)->create();
}
}
migrateと同時にダミーデータ放り込み
php artisan migrate --seed
10,000件のダミーデータをSupabase側に作るのには10分程度かかるのでコーヒーでも飲んで待ちます☕️
むしろ自分にコーヒー買ってください💕
なお、途中経過はSupabaseダッシュボードのTable editor > postsから確認可能です
PGroonga用のインデックス作成(重要)
PGroonga
を使う上での最重要項目:インデックスを作成します
SQL Editor > +New queryして、下記を実行します
CREATE INDEX pg_title_index ON posts USING pgroonga (title);
CREATE INDEX pg_body_index ON posts USING pgroonga (body);
Laravel側で検索してみるぞ🔍
検索用コントローラやビュー作成
Postモデルに検索機能付与
app/Models/Post.php
class Post extends Model
{
use HasFactory;
public static function search($keyword)
{
if(empty($keyword)){
return static::query();
}
$search_columns = ['title', 'body'];
$search_query = static::query();
foreach($search_columns as $column){
$search_query->orWhereRaw("$column &@~ ?", [$keyword]);
}
return $search_query;
}
}
検索用コントローラ作成
php artisan make:controller SearchController
app/Http/Controllers/SearchController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\View\View;
class SearchController extends Controller
{
public function index() : View
{
$posts = \App\Models\Post::query()->paginate(10);
return view('posts.index', compact('posts'));
}
public function search(Request $request) : View
{
$keyword = $request->input('keyword');
$posts = \App\Models\Post::search($keyword)->paginate(10)->withQueryString();
return view('posts.index', compact('posts', 'keyword'));
}
}
検索用ビュー作成
resources/views/layouts/guest.blade.php
検索画面に使いやすいように少し幅を拡大
Before
<!-- Line 25 -->
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg">
{{ $slot }}
</div>
After
<!-- Line 25 -->
<div class="w-full lg:max-w-6xl mt-6 px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg">
{{ $slot }}
</div>
検索兼一覧表示画面
resources/views/posts/index.blade.php
<x-guest-layout>
<div class="py-2">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-2 bg-white border-b border-gray-200">
<div>
<div class="flex w-full justify-between items-center sm:mb-2">
<h2 class="text-3xl font-extrabold tracking-tight text-gray-900 sm:text-4xl sm:mb-4"><a href="/">Supabase Laravel</a> </h2>
</div>
<form action="{{ route('posts.search') }}" method="GET">
<div class="flex mb-4 justify-between items-center">
<div class="block w-3/4">
<div class="flex flex-col sm:flex-row justify-start items-center pl-2">
<input type="search" name="keyword" class="form-control w-full sm:w-5/6 " type="text" value="@if (isset($keyword)) {{ $keyword }} @endif" placeholder="{{ __('Enter search keyword') }}">
<button class="collapse sm:visible inline-block align-left text-base sm:w-20 rounded-md border border-gray-700 sm:p-2 sm:ml-4" type="submit">{{ __('Search') }}</button>
</div>
</div>
</div>
</form>
@if($posts ?? false)
<div class="container">
<div class="bg-white">
<div class="max-w-2xl mx-auto px-4 grid items-center grid-cols-1 gap-y-16 gap-x-8 sm:px-6 lg:max-w-7xl lg:px-8 lg:grid-cols-1">
<div>
<dl class="sm:mt-8 grid grid-cols-1 gap-x-6 gap-y-2 grid-cols-1 sm:grid-cols-7 sm:gap-y-2 lg:gap-x-8">
<div class="border-t border-gray-200">
<dt class="text-sm sm:text-base sm:font-medium text-gray-900">
ID
</dt>
</div>
<div class="border-t border-gray-200 sm:col-span-2">
<dt class="text-sm sm:text-base sm:font-medium text-gray-900">
{{ __('Title') }}
</dt>
</div>
<div class="border-t border-gray-200 sm:col-span-4">
<dt class="text-sm sm:text-base font-medium text-gray-900">
{{ __('Body') }}
</dt>
</div>
@foreach($posts as $post)
<div class="border-t border-gray-200 pt-1">
<dd class="sm:mt-2 text-sm text-gray-500">
<span class="block m-1">ID: {{ $post->id }}</span>
</dd>
</div>
<div class="border-t border-gray-200 pt-1 sm:col-span-2">
<dd class="sm:mt-2 text-sm text-gray-500">
<span class="block m-1">{{ $post->title }}</span>
</dd>
</div>
<div class="border-t border-gray-200 pt-1 sm:col-span-4">
<dd class="sm:mt-2 text-sm text-gray-500">
<span class="block m-1 line-break">{{ $post->body }}</span>
</dd>
</div>
@endforeach
</dl>
</div>
<div>
{{ $posts->links() }}
</div>
</div>
</div>
</div>
@endif
</div>
</div>
</div>
</div>
</div>
</x-guest-layout>
Route設定
routes/web.php
/* 元々のTOPページの箇所をコメントアウト
Route::get('/', function () {
return view('welcome');
});
*/
Route::get('/', [\App\Http\Controllers\SearchController::class, 'index']) // 代わりに追加
->name('posts.index'); // 代わりに追加
Route::get('/search', [\App\Http\Controllers\SearchController::class, 'search']) // 代わりに追加
->name('posts.search'); // 代わりに追加
(残りは省略)
動作確認
必要なCSSとかビルドして起動
npm run build
php artisan serve
http://127.0.0.1:8000 へアクセス
「カムパネルラ」で検索してみます
PGroonga、それは便利検索
- AND検索
検索キーワードをスペースで区切ればAND検索だ!便利!
例:ジョバンニ カムパネルラ → 両方を含むデータを検索
- NOT検索
含んで欲しくないキーワードがあったら-
を付けるのです!
例:ジョバンニ -カムパネルラ → ジョバンニを含み、かつカムパネルラが含まれないデータを検索
- OR検索
とにかく色々ヒットさせたいあなたに!間にORを付けるのです!
例:ジョバンニ OR カムパネルラ → ジョバンニもしくはカムパネルラが含まれてればヒット!
メニューの日本語化
Breeezejpによって殆どは日本語化されてるのですが、検索画面用の値が無いので、下記をlang/ja.json
に追加します
(どこかの中間に入れてください)
"Title": "タイトル",
"Body": "本文",
"Search": "検索",
"Enter search keyword": "検索キーワードを入力",
Laravel Breezeの認証機能も動くよ
当たり前だけどLaravel Breezeのユーザ登録や認証機能も動きます
トップページを変えてしまっているので、直接下記のURLにアクセスしてみてください
あら便利💕
(guest.blade.php
をいじったので、ちょっとカードスタイルが幅広🥲)
Discussion