45
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

laravel5 日付入力のバリデーションはどう書くのが気持ちいいか

Last updated at Posted at 2016-09-21

laravelのバリデーションルールはとても種類が多くてありがたいのですが、
それでも既存のバリデーションでは対応してないやり方をしたい場合もあります。

7c5605de-1f49-0795-c0b0-e5d6f966bb8d.png

マイページ系の画面でこんな感じの生年月日入力はよく見かけます。
これのバリデーションを考えたいと思います。
環境はlaravel5.3ですが、たぶん5系ならどれでもいけるかと思います。

やりたいこと

  • 生年月日を「年」「月」「日」別のプルダウンで入力させたい
  • 入力必須項目ではないものとする(「年」「月」「日」が全て選択されてない場合は未入力)
  • 「年」「月」「日」のどれかが選択されててどれかが選択されてない、という場合はエラー
  • 「年」「月」「日」が全て選択されていて、不正な日付(02/30とか)の場合はエラー
  • 上記2つのエラー表示を分けたい

前準備

方針

  • カスタムバリデーションでの実装はあまり気持ちいい書き方を見つけられなかった
  • フォームリクエストバリデーションで validate() getValidatorInstance()をオーバーライドして、日付を連結したフィールドを作ることにした
  • 連結したフィールドに標準のdateバリデーションをかけてチェック

実装

ごくシンプルな投稿コントローラー

app\Http\Controllers\UserController.php
<?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()を使用します。

app\Http\Requests\UserRequests.php
<?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();
    }
}

フィールドの日本語化設定をしておく。

resources\lang\ja\validation.php(抜粋)
    'attributes' => [
        'name_last'       => '姓',
        'name_first'      => '名',
        'birth'           => '生年月日',
        'birth_year'      => '生年月日(年)',
        'birth_month'     => '生年月日(月)',
        'birth_day'       => '生年月日(日)',
    ],

テンプレート。
「birth_year」「birth_month」「birth_day」「birth」に対して別々にエラーを引ける。

resources\views\users\index.blade.php(抜粋)
    <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>

こんな感じ

正しい日付の場合は普通に投入できる
laravel1.png

生年月日が全て未入力の場合も普通に投入できる
laravel1.png

生年月日が一部しか入力されてない場合はエラー
※冗長なのでエラーメッセージは差し替えたほうがいいかも
laravel1.png

生年月日が全部入力されてるけど正しい日付じゃない場合はエラー
laravel1.png

気持いいポイント

  • 最終的に標準のバリデーションルールしか使ってないのが気持ちいい
  • 連結したbirthフィールドをそのままDBに突っ込めちゃうからコントローラーがすっきりして気持ちいい
  • エラーを別々にできるのがとても気持ちいい(最終的に「日付が不正だ」というエラーを$errors->first('birth_year')で引いてくる、みたいなのは避けたかった(だって別に年のエラーじゃなく日付全体のエラーなわけだし))

あとがき

laravel触りはじめてそんなに経ってない身ですので
「このやり方だとここが気持ちよくない」とか
「カスタムバリデーションならこうやれば気持ちいい」とか
「こうするともっと気持ちよくなる」とか
「俺のコードはまた別の気持ちよさが」とか
あれば是非教えて下さるとうれしいです。

参考リンク

45
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?