テクニカル雑記帳です
【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()
ソース
- SQL
// 検索ワード[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>