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要素に対して、下記のようにエラーが出力されるようにする。
下記キャプチャは全てのエラーが出力されてた場合(ルールが複数設定されている要素は最初のエラーを表示)。
ポイントとしては、
- <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
<!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。少々複雑ですが、とりあえず全ソースをペースト。
@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>オプション1</option>
<option value="option2" @if(Input::old('area')=="option2") selected @endif>オプション2</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
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にて値を表示。エラー時は、冒頭の画面が表示される。
ソースは、こんな感じ。ただ表示しているだけ。
@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