Laravelでの各Form要素でのValidationやエラー制御（のメモ）


Laravelにかぎらず、いろいろなフレームワークにForm処理に関する便利機能がありますが、input textではうまく動くものでも、checkboxやfile処理とかだと結局使えず、むしろイライラすることありますね。。。

ここではいざというときに困らないようにFormの主要要素とLaravelのバリデーション、さらにはBootstrapとの組み合わせをまとめておきたいと思います。本当は、各バーツ毎にテストしたいのですが、とりあえずここでは全体をメモ。

##やりたいこと

Laravel + Bootstrapを利用してFormデータのエラー処理パターンをまとめておきたい。

細かく書くと、

* 正常時の値の受け取り(checkboxとか選択しないとそもそもnullのやつの扱いとかチェック）。
* エラーの検知（Validation）。
* エラー時の表示の整理（エラーメッセージの受け取り方、表示やスタイル変更制御）。
* 入力値の保持方法（Laravelでは基本Input::old('hoge')の利用）。
* checkboxやfile等のおろそかになりがちな要素との連携チェック。

あたりを整理しておきたい。

##Formのビュー

formを表示するviewをviews/form.blade.phpとして作成。

エラー時にForm要素に対して、下記のようにエラーが出力されるようにする。
下記キャプチャは全てのエラーが出力されてた場合（ルールが複数設定されている要素は最初のエラーを表示）。

![form](http://www.bluecode.jp/qiita/lara_form002.png)


ポイントとしては、

* \<div class="form-group">で全体をくくれる要素はくくる
* くくれない要素は、エラーメッセージだけをくくる
* エラーの表示、クラスの変更は@if(!empty($errors.first('name'))) has-error @endifという感じで。
* 値の維持は基本Input::old('name')で。
* select,checkbox等は、@if(Input::old('name')=='name') selected @endifという感じで。

あたり。それを考慮した記述が下記。

共通レイアウト layout.blade.php

```html
<!doctype html>
<html>
<head>
	<meta charset="utf-8">
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
</head>
<body>

	<div class="container">
		@yield('content')
	</div>

<!-- js -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script>
	@yield('script')
</script>
</body>
</html>
```

肝心の中身。テスト用のform.blade.php。少々複雑ですが、とりあえず全ソースをペースト。

```html
@extends('layout')

@section('content')

	<h1 style="margin-bottom:30px;">新規登録</h1>

	<!-- form -->
	<form method="post" action="/store" enctype="multipart/form-data">

		<!-- input type="text" string -->
		 <div class="form-group @if(!empty($errors->first('name'))) has-error @endif">
			<label>名前</label>
			<input type="text" name="name" value="{{Input::old('name')}}" class="form-control">
			<span class="help-block">{{$errors->first('name')}}</span>
		</div>

		<!-- input type="text" string confirm -->
		 <div class="form-group @if(!empty($errors->first('email'))) has-error @endif">
			<label>E-Mail</label>
			<input type="text" name="email" value="{{Input::old('email')}}" class="form-control">
			<span class="help-block">{{$errors->first('email')}}</span>
		</div>

		<div class="form-group @if(!empty($errors->first('email_confirmation'))) has-error @endif">
			<label>E-Mail（再入力）</label>
			<input type="text" name="email_confirmation" value="{{Input::old('email_confirmation')}}" class="form-control">
			<span class="help-block">{{$errors->first('email_confirmation')}}</span>
		</div>

		<!-- input type="text" int -->
		 <div class="form-group @if(!empty($errors->first('age'))) has-error @endif">
			<label>年齢</label>
			<input type="text" name="age" value="{{Input::old('age')}}" class="form-control">
			<span class="help-block">{{$errors->first('age')}}</span>
		</div>

		<!-- select -->
		 <div class="form-group @if(!empty($errors->first('area'))) has-error @endif">
			<label>エリア</label>
			<select name="area" class="form-control">
				<option value="">選択して下さい</option>
				<option value="option1" @if(Input::old('area')=="option1") selected @endif>オプション１</option>
				<option value="option2" @if(Input::old('area')=="option2") selected @endif>オプション２</option>
			</select>
			<span class="help-block">{{$errors->first('area')}}</span>
		</div>

		<!-- radio -->
		<p><b>性別</b></p>
		<div class="radio-inline">
			<label>
				<input type="radio" name="gender" value="man" @if(Input::old('gender')=="man") checked @endif> 男
			</label>
		</div>
		<div class="radio-inline">
			<label>
				<input type="radio" name="gender" value="woman" @if(Input::old('gender')=="woman") checked @endif> 女
			</label>
		</div>
		 <div class="form-group @if(!empty($errors->first('gender'))) has-error @endif">
			<span class="help-block">{{$errors->first('gender')}}</span>
		</div>

		<!-- checkbox -->
		<p><b>告知メディア</b></p>
		<div class="checkbox">
			<label>
				{{-- <input type="hidden" name="media1" value="none"> --}}
				<input type="checkbox" name="media1" value="web" @if(Input::old('media1')=="web") checked @endif> Web
			</label>
		</div>
		 <div class="form-group @if(!empty($errors->first('media1'))) has-error @endif">
			<span class="help-block">{{$errors->first('media1')}}</span>
		</div>

		<div class="checkbox">
			<label>
				{{-- <input type="hidden" name="media2" value="none"> --}}
				<input type="checkbox" name="media2" value="TV" @if(Input::old('media2')=="TV") checked @endif> TV
			</label>
		</div>
		 <div class="form-group @if(!empty($errors->first('media2'))) has-error @endif">
			<span class="help-block">{{$errors->first('media2')}}</span>
		</div>


		<!-- textarea -->
		 <div class="form-group @if(!empty($errors->first('note'))) has-error @endif">
			<label>感想</label>
			<textarea name="note" class="form-control" rows="3">{{Input::old('note')}}</textarea>
			<span class="help-block">{{$errors->first('note')}}</span>
		</div>

		<!-- file -->
		 <div class="form-group @if(!empty($errors->first('image'))) has-error @endif">
			<label>画像</label>
			<input type="file" name="image">
			<span class="help-block">{{$errors->first('image')}}</span>
		</div>

		<!-- token -->
		<input type="hidden" name="_token" value="{{csrf_token()}}">

		<input type="submit" value="登録" class="btn btn-primary">

	</form>

@stop
```

##コントローラ（バリデーション実行）

とりあえず、上記のエラー時表示をテストするためのもの。
formからの値を受け取りバリデート。NGならviewに戻し、OKならstore.blade.phpにリダイレクトし、受け取った内容を表示するものとする。ファイル名はFormController.phpとした。

コントローラーのコードは、Laravelのスマートな？Validation記述のお陰でそれほど複雑ではない。

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

class FormController extends Controller
{
    //
    public function form()
    {
        return view('form');
    }

    public function store(Request $request)
    {

        //inputs
        $inputs = $request->all();

        //rules
        $rules = [
            'name'=>'required|min:6|max:10',
            'email'=>'required|email|confirmed',
            'age'=>'required|numeric',
            'area'=>'required',
            'gender'=>'required',
            'media1'=>'required',
            'media2'=>'required',
            'note'=>'required|max:10',
            'image'=>'required|image|max:100', //kbyte
        ];

        //validation
        $validation = \Validator::make($inputs,$rules);

        //if fails
        if($validation->fails())
        {
            return redirect()->back()->withErrors($validation->errors())->withInput();
        }

        //バリデーションOK

        //fileは個別に処理
        $file = \Input::file('image');
        if(!empty($file))
        {
            $filename = $file->getClientOriginalName();
            $move = $file->move('./',$filename); //public
        }
        else
        {
            //ファイルアップロードが無いときは変数を初期化（viewでのエラー防止）
            $inputs["image"] = "none";
        }
       

        //
        return view('store',compact('inputs'));
    }
}

```

##OKだったとき受け取った値を表示するビュー

バリデーションがOKなら、store.blade.phpにて値を表示。エラー時は、冒頭の画面が表示される。

![form](http://www.bluecode.jp/qiita/lara_form003.png)

ソースは、こんな感じ。ただ表示しているだけ。


```html
@extends('layout')

@section('content')

	<h1>内容表示</h1>

	<div class="row">
		<div class="col-sm-12">
			<a href="/form" class="btn btn-primary" style="margin:20px;">フォームに戻る</a>
		</div>
	</div>

	<!-- table -->
	<table class="table table-striped">
		<tr><td>名前</td><td>{{$inputs["name"]}}</tr>
		<tr><td>E-Mail</td><td>{{$inputs["email"]}}</tr>
		<tr><td>年齢</td><td>{{$inputs["age"]}}</tr>
		<tr><td>エリア</td><td>{{$inputs["area"]}}</tr>
		<tr><td>性別</td><td>{{$inputs["gender"]}}</tr>
		<tr><td>メディア</td><td>{{$inputs["media1"]}}</tr>
		<tr><td>メディア</td><td>{{$inputs["media2"]}}</tr>
		<tr><td>感想</td><td>{{$inputs["note"]}}</tr>
		<tr><td>ファイル</td><td>{{$inputs["image"]}}</tr>
	</table>

@stop
```