あじちゃんの備忘録。

〜ここはメモ帳です

【Laravel】クエリビルダーでスペース区切りの複数ワード検索を行う

所感とか

所感

🤪 考えるのすごい時間かかったのに、寝たら一瞬で思いついた. 睡眠は大事.

大事だと思ったこと

  • ワードをどの要素にどういう条件で絞り込むのかを明確にすること
  • はじめに目的とするSQLを書いてみること
  • 要素ごとにまとめてwhere句を作っていくこと
  • 正規表現は何パターンか覚えておきたいところ

ポイント

  • 半角スペースを全角スペースにする: mb_convert_kana($request->words, 's');
  • スペースごとに配列に格納: preg_split('/[\s]+/', $request->words);
  • Illuminate\Support\Collectionで配列要素全てに処理を追加: Collection::make($strArry)->map(function($p){return "%".$p."%";})->toArray();
  • それぞれごとにwhere句を形成する: $query->where(function($query)use(...){}); を複数使うとこでまとまったwhere句を形成できる
  • 最初に受け取ったinputを引き出せる: $request->session()->getOldInput()

ソース

// 検索ワード[aaa bbb ccc] の場合

 select * from `products`
 where (`title` like "%aaa%" and `title` like "%bbb%" and `title` like "%ccc%")
       or (`description` like "%aaa%" and `description` like "%bbb%" and `description` like "%ccc%");
  • Model(Search.php)
<?php

static function search(Request $request)
{
    $products = self::query();
    $hasParam = true;
    $search_words;

    if(isset($request->words)) {
        //検索ワードを分割
        if(isset($request->words)) {
            //半角スペースを全角スペースにする
            $request->words = mb_convert_kana($request->words, 's');
            //スペースごとに配列に格納
            $strArry = preg_split('/[\s]+/', $request->words);

            //use Illuminate\Support\Collectionで配列要素全てにワイルドカード(%)を追加
            //$pが配列の要素ひとつずつになる. その要素に処理をして,returnで元の要素と入れ替えるイメージ
            $search_words = Collection::make($strArry)->map(function($p)
            {
                return "%" . $p . "%";
            })->toArray();
        }
        $count = count($search_words);

        //title, descriptionそれぞれごとにwhere句を形成
        $products = $products
            ->where(function($query)use($search_words){
                foreach($search_words as $search_word) {
                    $query->where('title', 'like', $search_word);
                }
            })
            ->orwhere(function($query)use($search_words){
                foreach($search_words as $search_word) {
                    $query->where('description', 'like', $search_word);
                }
            });
    }
    else {
        $hasParam = false;
        $products = $products;
    }

    $result = [];

    //初期表示にメッセージが出ないようにする
    if($products->count() <= 0 && $hasParam){
        $result['message'] = "検索結果は0件です";
    } else {
        $result['message'] = "";
    }

    $result['products'] = $products;

    return $result;
}
  • Controller(Controller.php)
<?php

public function index(Request $request)
{
    $products = Search::search($request);

    $message = $products['message'];
    $products = $products['products'];

    if($message) {
        //検索結果が0件の場合
        return view('index', [
            'products' => $products,
            'inputs' => array_merge($request->input(), $request->session()->getOldInput()),
            'message' => $message
        ]);
    }
    
    //検索結果が1件以上の場合
    return view('akashic-game.products.index', [
        'products' => $products,
        'inputs' => array_merge($request->input(), $request->session()->getOldInput()),
        'message' => $message
    ]);
}
  • View(index.blade.php)
@if (isset($message))
  <p>{{ $message }}</p>
@else

<form action="{{ action('Controller@index') }}" method="GET">
  <fieldset>
    <div>
      <input type="text" name="words" value="@if(!empty($inputs['words'])){{ $inputs['words'] }}@endif">
    </div>
    <button type="submit">検索</button>
  </fieldset>
</form>

<div>
@forelse($items as $item)
  <li>{{ $item->title }} : {{ $item->description }}</li>
@endforeach
</div>