laravelのバリデーションルールはとても種類が多くてありがたいのですが、
それでも既存のバリデーションでは対応してないやり方をしたい場合もあります。
マイページ系の画面でこんな感じの生年月日入力はよく見かけます。
これのバリデーションを考えたいと思います。
環境はlaravel5.3ですが、たぶん5系ならどれでもいけるかと思います。
やりたいこと
- 生年月日を「年」「月」「日」別のプルダウンで入力させたい
- 入力必須項目ではないものとする(「年」「月」「日」が全て選択されてない場合は未入力)
- 「年」「月」「日」のどれかが選択されててどれかが選択されてない、という場合はエラー
- 「年」「月」「日」が全て選択されていて、不正な日付(02/30とか)の場合はエラー
- 上記2つのエラー表示を分けたい
前準備
方針
- カスタムバリデーションでの実装はあまり気持ちいい書き方を見つけられなかった
- フォームリクエストバリデーションで
validate()getValidatorInstance()をオーバーライドして、日付を連結したフィールドを作ることにした - 連結したフィールドに標準のdateバリデーションをかけてチェック
実装
ごくシンプルな投稿コントローラー
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
a
use App\Http\Requests;
use App\Http\Requests\UserRequest;
use App\User;
class UserController extends Controller
{
public function index()
{
return view('users.index');
}
public function store(UserRequest $request)
{
$user = User::create($request->all());
if (!$user->birth) {
$user->birth = null;
}
$user->save();
return view('users.store');
}
}
フォームリクエストの validate() getValidatorInstance() をオーバーライドして、
「birth_year」「birth_month」「birth_day」を連結した「birth」フィールドを作成。
あとは普通にbirthに対してdateバリデーションを行うようrules()に記述。
「年が入力されてて月が入力されてない」みたいなのはrequired_withで対応。
[追記] laravel 5.4とか5.5あたりから、validate()では捕捉できなくなったようです。
getValidatorInstance()を使用します。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UserRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name_first' => 'required',
'name_last' => 'required',
'birth' => 'date',
'birth_year' => 'required_with:birth_month,birth_day',
'birth_month' => 'required_with:birth_year,birth_day',
'birth_day' => 'required_with:birth_year,birth_month',
];
}
public function getValidatorInstance()
{
if ($this->input('birth_day') && $this->input('birth_month') && $this->input('birth_year'))
{
$birthDate = implode('-', $this->only(['birth_year', 'birth_month', 'birth_day']));
$this->merge([
'birth' => $birthDate,
]);
}
return parent::getValidatorInstance();
}
}
フィールドの日本語化設定をしておく。
'attributes' => [
'name_last' => '姓',
'name_first' => '名',
'birth' => '生年月日',
'birth_year' => '生年月日(年)',
'birth_month' => '生年月日(月)',
'birth_day' => '生年月日(日)',
],
テンプレート。
「birth_year」「birth_month」「birth_day」「birth」に対して別々にエラーを引ける。
<div class="form-group{{ $errors->has('birth') || $errors->has('birth_year') || $errors->has('birth_month') || $errors->has('birth_day') ? ' has-error' : '' }}">
<label for="birth_year" class="col-md-4 control-label">生年月日</label>
<div class="row">
<div class="col-md-2">
<select id="birth_year" class="form-control" name="birth_year">
<option value="">----</option>
@for ($i = 1980; $i <= 2005; $i++)
<option value="{{ $i }}"@if(old('birth_year') == $i) selected @endif>{{ $i }}</option>
@endfor
</select>
@if ($errors->has('birth_year'))
<span class="help-block">
<strong>{{ $errors->first('birth_year') }}</strong>
</span>
@endif
</div>
<div class="col-md-2">
<select id="birth_month" class="form-control" name="birth_month">
<option value="">--</option>
@for ($i = 1; $i <= 12; $i++)
<option value="{{ $i }}"@if(old('birth_month') == $i) selected @endif>{{ $i }}</option>
@endfor
</select>
@if ($errors->has('birth_month'))
<span class="help-block">
<strong>{{ $errors->first('birth_month') }}</strong>
</span>
@endif
</div>
<div class="col-md-2">
<select id="birth_day" class="form-control" name="birth_day">
<option value="">--</option>
@for ($i = 1; $i <= 31; $i++)
<option value="{{ $i }}"@if(old('birth_day') == $i) selected @endif>{{ $i }}</option>
@endfor
</select>
@if ($errors->has('birth_day'))
<span class="help-block">
<strong>{{ $errors->first('birth_day') }}</strong>
</span>
@endif
</div>
</div>
<div class="row col-md-6 col-md-offset-4">
@if ($errors->has('birth'))
<span class="help-block">
<strong>{{ $errors->first('birth') }}</strong>
</span>
@endif
</div>
</div>
こんな感じ
生年月日が一部しか入力されてない場合はエラー
※冗長なのでエラーメッセージは差し替えたほうがいいかも
生年月日が全部入力されてるけど正しい日付じゃない場合はエラー
気持いいポイント
- 最終的に標準のバリデーションルールしか使ってないのが気持ちいい
- 連結したbirthフィールドをそのままDBに突っ込めちゃうからコントローラーがすっきりして気持ちいい
- エラーを別々にできるのがとても気持ちいい(最終的に「日付が不正だ」というエラーを$errors->first('birth_year')で引いてくる、みたいなのは避けたかった(だって別に年のエラーじゃなく日付全体のエラーなわけだし))
あとがき
laravel触りはじめてそんなに経ってない身ですので
「このやり方だとここが気持ちよくない」とか
「カスタムバリデーションならこうやれば気持ちいい」とか
「こうするともっと気持ちよくなる」とか
「俺のコードはまた別の気持ちよさが」とか
あれば是非教えて下さるとうれしいです。
参考リンク