まえがき
・先日、検索機能実装のお仕事を頂いたのですが、その中で結構時間がかかった部分を紹介したいと思います。
「セレクトボックス(optionタグ)で選択した値を、ページ遷移先でも選択状態にする」
「え?なにが難しいの?」
そう思う方もいらっしゃるでしょう…
「以下のようなコードで実現できますよ!!」
なんてうたってるサイト上に数あるコード、片っ端から全て試しましたが出来ませんでした。
問題となったコード3つと解決したコード1つの計4つのコードで発生した問題と解決方法を解説していきたいと思います。
目次
- 環境
- 要件定義
- 発生した問題点
- 問題となったコード(時系列順)
- 解決したコード
- 解説
- 参考文献
環境
- PHP 7.4.15
- Laravel 6.20.16
要件定義
以下の要件定義を実現する上で詰まりました
- configディレクトリに定数用のディレクトリ・ファイルを作成、そこにセレクトボックス用のデータ配列を定義
- 検索結果を表示した時に、検索条件を保持し、選択されたセレクトボックスの文字列を保持
- ページネーションで他ページに遷移した場合も検索条件、検索結果を保持
- その他
- 未選択時は null
- Undefined variable エラーを回避
- 未選択時は null
- その他
発生した問題点
1. ページ遷移時に選択した値が保持されず空欄になる
2. 空欄ではないが選択した値とは異なる値が表示される
3. 表示に成功したがユーザが選択した値を if 文の条件に使用しているため、値の未選択時に Undefined variable エラー
問題となったコード(時系列順)
1. ページ遷移時に選択した値が保持されず空欄になる
<select name="term">
@foreach (config('const.term') as $termNumber)
<option value="{{ $termNumber }}" @if(old('term') == $termNumber) selected @endif>{{ $termNumber }}</option>
@endforeach
</select>
2. 空欄ではないが選択した値とは異なる値が表示される
<select name="term">
@foreach (config('const.term') as $index => $termNumber)
<option value="{{ $termNumber }}" @if(old('term', $index) == $termNumber) selected @endif>{{ $termNumber }}</option>
@endforeach
</select>
3. 表示に成功したが、ユーザが選択した値を if 文の条件に使用しているために未選択時に Undefined variable エラー
<select name="term" id="term" class="mr-2">
@foreach (config('const.term') as $termNumber)
<option value="{{ $termNumber }}" @if(old('term', $inputTerm) == $termNumber) selected @endif>{{ $termNumber }}</option>
@endforeach
</select>
@foreach (config('const.term') as $index)
で使用している
config('const.term')
は下記のコードです。
<?php
return [
'term' => [
'' => '',
'1' => '1',
'2' => '2',
'3' => '3',
'4' => '4',
'5' => '5',
'6' => '6',
'7' => '7',
'8' => '8',
'9' => '9',
'10' => '10',
'11' => '11',
'12' => '12',
'13' => '13',
'14' => '14',
'15' => '15',
'16' => '16',
'17' => '17',
'18' => '18',
'19' => '19',
'20' => '20'
],
];
@if(old('term', $inputTerm)
の$inputTerm
はセレクトボックスで選択したterm
の値を request オブジェクト内から取得して変数に代入したものです。以下の controller 内のメソッドにて定義し blade に渡しています。
/**
* 検索結果画面表示
*
* @param App\Article $article
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function search(Article $article, Request $request)
{
$inputTerm = $request->term;
$inputCategory = $request->category;
$inputWord = $request->word;
〜中略〜
$articles = $article->getBySearchParameters($inputTerm, $inputCategory, $inputWord);
return view('articles.index', compact('inputTerm', 'inputCategory', 'inputWord', 'articles'));
}
解決したコード
<select name="term" id="term" class="mr-2">
@foreach (config('const.term') as $termNumber)
<option value="{{ $termNumber }}" @if(old('term', $inputTerm ?? '') == $termNumber) selected @endif>{{ $termNumber }}</option>
@endforeach
</select>
コードの変遷と変更箇所(2箇所)は以下
1. ページ遷移時に選択した値が保持されず空欄になる
@foreach (config('const.term') as $termNumber)
2. 空欄ではないが選択した値とは異なる値が表示される
@foreach (config('const.term') as $index => $termNumber)
3. 表示に成功したがユーザが選択した値を if 文の条件に使用しているため、未選択時に Undefined variable エラー
4. 解決したコード
@foreach (config('const.term') as $termNumber)
1. ページ遷移時に選択した値が保持されず空欄になる
<option value="{{ $termNumber }}" @if(old('term') == $termNumber) selected @endif>{{ $termNumber }}</option>
2. 空欄ではないが選択した値とは異なる値が表示される
<option value="{{ $termNumber }}" @if(old('term', $index) == $termNumber) selected @endif>{{ $termNumber }}</option>
3. 表示に成功したがユーザが選択した値を if 文の条件に使用しているため、未選択時に Undefined variable エラー
<option value="{{ $termNumber }}" @if(old('term', $inputTerm) == $termNumber) selected @endif>{{ $termNumber }}</option>
4. 解決したコード
<option value="{{ $termNumber }}" @if(old('term', $inputTerm ?? '') == $termNumber) selected @endif>{{ $termNumber }}</option>
解説
1. ページ遷移時に選択した値が保持されず空欄になる
option 毎に
「この選択肢を選んでいれば、それをselectedにする」
という if 文条件を記述していますが、結果は空欄。
繰り返し処理で出力された値とユーザが選択した値が一致することで、その値を selected に出来ると思ったのですが、上手いこと一致しませんでした。
<select name="term">
@foreach (config('const.term') as $termNumber)
<option value="{{ $termNumber }}" @if(old('term') == $termNumber) selected @endif>{{ $termNumber }}</option>
@endforeach
</select>
2. 空欄ではないが選択した値とは異なる値が表示される
old()
old関数はセッションにフラッシュデーターとして保存されている直前の入力値を取得します。
old関数は第2引数に記述した値を、初期値として設定できます。つまり、
name 属性に設定された値が old() に存在すれば value に入力され、nullの場合は第2引数に記述した値がされます。これでいける気がするのですが、保持される値は、何を選んでも「20」でした。正直どんな処理でこの結果になるのか未だによく分かりません…
<select name="term">
@foreach (config('const.term') as $index => $termNumber)
<option value="{{ $termNumber }}" @if(old('term', $index) == $termNumber) selected @endif>{{ $termNumber }}</option>
@endforeach
</select>
3. 表示に成功したがユーザが選択した値を if 文の条件に使用しているために未選択時に Undefined variable エラー
ユーザが選択した値を request オブジェクトから取得し old関数の第2引数に設定します。
これこそユーザが選択した値です。
ユーザが選択した値と繰り返し処理で順番に巡ってきた値とが == で true になればユーザが選択した値がselected されます!
やったー!!できたー!!と思いましたがまだ不完全です。
「ユーザが選択した値がない場合」
つまり
「ユーザが値を選択しなかった場合」
では$inputTerm
は 未定義で null ですらありません。Undefined variable 、いわゆる未定義エラーが発生します。
どういった場面かというと、サイト接続時の初回画面表示時です。
初めて訪問するページでは、old関数で初期値に設定した値が未定義状態になるコードです。
<select name="term" id="term" class="mr-2">
@foreach (config('const.term') as $termNumber)
<option value="{{ $termNumber }}" @if(old('term', $inputTerm) == $termNumber) selected @endif>{{ $termNumber }}</option>
@endforeach
</select>
4. 解決したコード
上記で発生した Undefined variabl エラーを解消するためには、初期値として設定した$inputTerm
が未定義の際、
代わりに null になるようにすれば良いのです。
これを実現したのが
「null合体演算子」
です。
$inputTerm
が falsy (null or 0 or undefined or false) な値の場合、??
以降の値にしてくれます。
ここは三項(エルビス)演算子では対応できません。Undefined variable が発生します
また、以下のような特徴もあります。
-
A ?? B
の A の存在チェックもしてくれます。isset
いらなくなるのかな? - A が falsy の場合のみBを参照するので、
??
は falsy なものだけをフィルタリングしてくれる様なイメージです。 - ネストすることも可能。1つ目がなければ2つ目、2つ目がなければ3つ目のような記述
(old('term', $inputTerm ?? '$inputWord' ?? '$inputCategory' ?? '')
参考はこちら:三項演算子 Null 合体演算子
<select name="term" id="term" class="mr-2">
@foreach (config('const.term') as $termNumber)
<option value="{{ $termNumber }}" @if(old('term', $inputTerm ?? '') == $termNumber) selected @endif>{{ $termNumber }}</option>
@endforeach
</select>
これで要件定義を満たすことが出来ました!
参考文献
- 三項演算子
- Null 合体演算子
- 似てるようで違う、PHPのエルビス演算子とNull合体演算子
- 【PHP】知らなかった比較演算子【null合体演算子】
- PHPのNull合体演算子でUndefined indexを防ぐ
- Laravelで定数をつかうよ
- Laravelで定数を使うときにConfigを使う腰抜けはもう死んだ
- 【Laravel】 都道府県のプルダウン作る
- Laravelで都道府県のプルダウンメニューを作ってみる
- 配列を使ったプルダウンの選択肢の表示の理屈について
- Laravelのoldヘルパーが空の場合の初期値...
- bladeのフォーム欄で使うold関数 | laravel
- oldヘルパー関数の初期値
- Laravel 5.8 ヘルパ old()
- Laravelのold()で配列を扱う場合のインデックスの表現方法