【初心者向け】Laravelで簡単検索機能を実装|あいまい・完全一致・複数条件に対応

こんにちは、フロントエンドエンジニアのてりーです。
この記事ではLaravelの検索機能を「基本 → 応用 → 本格検索(Scout+Meilisearch) → リアルタイム(Livewire)」の順でまとめて解説します。

・WHERE句での検索(完全一致/部分一致/複数条件)
・日本語のあいまい検索の実装ポイント
Laravel Scout + Meilisearchでの全文検索
Livewireで作るリアルタイム検索UI
・実務で効くパフォーマンス最適化(インデックス/キャッシュ)

📚 Laravelの基礎から学びたい方へ:
👉 Laravel学習に役立つおすすめ教材

1. 基本の検索:完全一致/部分一致/複数条件

まずはEloquentを使った基本パターンです。実務では部分一致複数条件の組み合わせが最も使われます。

① 完全一致検索

$articles = Post::where('title', $search)->paginate(8);

② 部分一致(LIKE)検索

$articles = Post::where('title', 'LIKE', "%{$search}%")->paginate(8);

%はワイルドカード。前後に付けると部分一致になります。

③ 複数フィールドを横断する検索

$articles = Post::where(function($q) use ($search) {     $q->where('title', 'LIKE', "%{$search}%")       ->orWhere('tag', 'LIKE', "%{$search}%")       ->orWhere('body', 'LIKE', "%{$search}%"); })->orderBy('created_at', 'desc')->paginate(8);

2. 日本語の「あいまい検索」を安定させる

日本語は空白や全角半角の揺れでヒット率が落ちます。前処理で精度を底上げしましょう。

// 入力前処理:空白正規化 & トリム & 複数語対応 $keyword   = preg_replace('/\s+/', ' ', trim($request->input('search', ''))); $keywords  = array_filter(explode(' ', $keyword));  $query = Post::query(); foreach ($keywords as $word) {     $query->where('title', 'like', "%{$word}%"); } $articles = $query->paginate(8);
・検索対象カラム(title, tag, body)にINDEXを貼ると高速化。
・カテゴリ/期間/タグなどは別UIで絞り込み、検索精度と体験を両立。

3. Laravel Scout + Meilisearch で全文検索

ScoutはLaravel公式の全文検索抽象レイヤ、Meilisearchは軽量・爆速の検索エンジン。データ量が多いプロジェクトで威力を発揮します。

導入手順

composer require laravel/scout meilisearch/meilisearch-php php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

.env設定

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

モデル設定 & 検索

use Laravel\Scout\Searchable;  class Post extends Model {   use Searchable; }  // インデックスへ投入(初回) Post::query()->searchable();  // 検索 $posts = Post::search('laravel 検索機能')->get();
・インストールが簡単、軽量、更新が速い
・キーボードタイプ中の検索(typeahead)と相性が良い

4. Livewire で「リアルタイム検索」UI

ページ遷移なしで結果が更新されるので、ユーザー体験が抜群に良いです。Scout/Meilisearchと組み合わせれば爆速全文検索も可能。

コンポーネント

class SearchPosts extends \Livewire\Component {     public string $search = '';      public function render()     {         $posts = Post::when($this->search, function($q){             $q->where('title', 'like', "%{$this->search}%");         })->limit(20)->get();          return view('livewire.search-posts', compact('posts'));     } }

Blade(ビュー)

<div>   <input type="text" class="form-control" placeholder="記事タイトルで検索" wire:model.debounce.300ms="search">   <ul class="mt-2">     @forelse($posts as $post)       <li>{{ $post->title }}</li>     @empty       <li>一致する記事がありません</li>     @endforelse   </ul> </div>

5. 実装手順(フォーム/Controller/ページネーション)

① フォーム

<form class="form-inline my-2 my-lg-0 ml-2" method="GET">   <div class="form-group">     <input type="search" class="form-control mr-sm-2" name="search"            value="{{ request('search') }}" placeholder="キーワードを入力" aria-label="検索">   </div>   <input type="submit" value="検索" class="btn btn-info"> </form>

② Controller

$articles = Post::when($search = request('search'), function($q) use ($search) {                 $keyword  = preg_replace('/\s+/', ' ', trim($search));                 $words    = array_filter(explode(' ', $keyword));                 foreach ($words as $w) {                     $q->where('title', 'like', "%{$w}%");                 }             })             ->orderBy('created_at', 'desc')             ->paginate(8)             ->withQueryString(); // 入力値を維持

③ ページネーション

<div class="d-flex justify-content-center">   {{ $articles->links() }} </div>

6. パフォーマンス最適化

  • INDEXを貼る:ALTER TABLE posts ADD INDEX idx_posts_title (title);
  • キャッシュ:同じ検索条件が続く場合は Cache::remember() を活用
  • 大量データは Scout + Meilisearch を前提に設計すると◎

まとめ:最短で使える検索から本格全文検索まで

  • 最短は LIKE + 複数条件
  • 日本語は事前正規化でヒット率UP
  • スケールするなら Scout + Meilisearch
  • 体験向上は Livewireのリアルタイム検索

よりLaravelを深く学ぶならこちらもどうぞ👇

👉 【Laravel】マイグレーションの使い方(追加/更新)
👉 Laravel Debugbarでクエリとパフォーマンスを可視化
👉 【学習ロードマップ】React + TypeScript

質問があればコメントでどうぞ!この記事が役に立ったらSNSでシェアしてもらえると嬉しいです 🙏