Docker×Laravel8でもくもく会アプリを作成するバリデーション編です。
前回の記事の続きになりますので、まだの方は以下の記事をご参照ください。
休日で空いた時間の暇つぶしを探せるアプリを公開しています。
バリデーションを実装する流れ
バリデーションを実装する流れは以下です。
なお、Laravelでバリデーションを実装する方法についてより詳しく以下の記事で解説しています。
①フォームリクエストを作成する
バリデーションをフォームリクエストに書いていきますが、まずはフォームリクエストを作成しましょう。
/var/www/html# php artisan make:request EventRequest
フォームリクエストが作成されました。
初期設定をする
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class EventRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true; // falseからtrueに変更する
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$validate = [];
return $validate;
}
}
rulesの中はさまざまな書き方がありますが、$validate = []を最初に宣言し、バリデーションに引っ掛かれば、validateの配列に追加していきます。
最終的にreturnでvalidateを返していく方針です。こちらの書き方も、上記のバリデーション実装方法の解説記事をご参照ください。
②バリデーションを記述する
次に先ほど作成したeventRequest.phpにバリデーションを記述していきます。
まずは試しにタイトル必須のバリデーションです。
public function rules()
{
$validate = [];
$validate += [
// タイトル必須
'title' => [
'required',
],
];
return $validate;
}
③コントローラーでeventRequestを適用する
次に、eventRequestに書いたバリデーションルールをeventControllerで適用させましょう。
<?php
namespace App\Http\Controllers;
use App\Models\Event;
use App\Models\category;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Requests\EventRequest; // 追加
class EventController extends Controller
{
public function __construct()
{
$this->event = new Event();
$this->category = new Category();
}
省略
/**
* もくもく会登録処理
*/
public function create(EventRequest $request) // Request→EventRequestに修正
{
try {
// トランザクション開始
DB::beginTransaction();
// リクエストされたデータをもとにeventsテーブルにデータをinsert
$insertEvent = $this->event->insertEventData($request);
// 処理に成功したらコミット
DB::commit();
} catch (\Throwable $e) {
// 処理に失敗したらロールバック
DB::rollback();
// エラーログ
\Log::error($e);
// 登録処理失敗時にリダイレクト
return redirect()->route('event.index')->with('error', 'もくもく会の登録に失敗しました。');
}
return redirect()->route('event.index')->with('success', 'もくもく会の登録に成功しました。');
}
}
コントローラー冒頭にEventRequestを読み込みます。
createメソッドの引数で、Request→EventRequestに変更します。
これで、createメソッドが呼ばれたときに、EventRequesのバリデーションを最初に検証してから、createメソッド内のinsert処理などが走るようになります。
④validation.phpに追加する
lang > ja > validation.phpを修正・追加していきます。
// 'required' => ':attribute field is required.', となっているので以下のように修正。102行目付近
'required' => ':attributeは必須です。',
ファイルの一番下にattributeの項目があるのでtitleを追加します。
// 156行目付近
'attributes' => [
'title' => 'タイトル',
],
⑤ビューファイルにエラー時の表記を追加する
下準備ができたので、バリデーション時にユーザー側に知らせるようにしましょう。
{{-- タイトルフォーム --}}
<div class="form-group">
<label for="title">{{ 'タイトル' }}<span class="badge badge-danger ml-2">{{ '必須' }}</span></label>
<input type="text" class="form-control{{ $errors->has('title') ? ' is-invalid' : '' }}" name="title" id="title">
@if ($errors->has('title'))
<span class="invalid-feedback" role="alert">
{{ $errors->first('title') }}
</span>
@endif
</div>
ビューファイルでバリデーションエラー時に、メッセージを表示させたり、フォームを赤線で囲ったりするために追記しました。
これも詳しくは、
こちらの記事を参考にしていただきたいのですが、少し解説すると、こんな感じ。
// 三項演算子。if文を1行で表している。
{{ $errors->has('title') ? ' is-invalid' : '' }}
// if文で表すと以下になる
@if ($errors->has('title'))
' is-invalid'
@else
''
@endif
// バリデーションエラー時
class="form-control is-invalid"
// 正常
class="form-control"
エラー時にclassにis-invalidを追加し、エラーがない時は、classに何も追加しません。
bootstrapでは、is-invalidがclassにあると、以下のようにフォームの枠が赤線で囲まれます。
⑥old関数を使って、入力した値を保持する
タイトルのバリデーションはできましたが、現状だと直前に入力した値が保持されません。
入力欄を空
入力欄に文字を入力
他の項目でバリデーションに引っかかった時に、直前の入力が保持されない
ユーザーは、バリデーションを通過した項目でもまた一から入力する必要があるので面倒。だから、直前の入力は保持する必要があります。
{{-- タイトルフォーム --}}
<div class="form-group">
<label for="title">{{ 'タイトル' }}<span class="badge badge-danger ml-2">{{ '必須' }}</span></label>
<input type="text" class="form-control{{ $errors->has('title') ? ' is-invalid' : '' }}" name="title" id="title" value="{{ old('title') }}">
@if ($errors->has('title'))
<span class="invalid-feedback" role="alert">
{{ $errors->first('title') }}
</span>
@endif
</div>
valueにold(‘name属性’)を記述することで、入力を保持できます。
以下の記事で詳しく解説しているので、ぜひチェックしてみてくださいね!
例えば、参加費の必須を追加し、タイトルはOKだけど、参加費のバリデーションに引っかかったとしましょう。
public function rules()
{
$validate = [];
$validate += [
// タイトル必須
'title' => [
'required',
],
'entry_fee' => [
'required',
],
];
return $validate;
}
タイトルは入力し、参加費は空にします。そして、開催するボタンを押します。
参加費の入力は必須なので、バリデーションに引っかかりますが、タイトルはold関数を使っているので入力された値は保持できているのが確認できました。
バリデーションを実装する流れはOKでしょうか?
こんな感じで、バリデーションを完成させていきます。
バリデーションを完成させる
それではバリデーションを完成させます。
- タイトル→必須・50文字まで
- カテゴリー→必須
- 開催日→必須・今日以降
- 開始時間、終了時間→必須
- 参加費→必須・数字・整数・0以上
- 詳細→必須
この記事では、上記ですが、他にも自分で調べてバリデーションを追加してみてくださいね。
※開始時間や終了時間のバリデーションはかなり複雑になるので取り上げませんでした。ex.終了時間は開始時間よりも遅い時間にする、開始時間と終了時間は本日の現在時刻以降にするなど。
バリデーション
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class EventRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$validate = [];
$validate += [
// タイトル必須・50文字以内
'title' => [
'required',
'max:50',
],
// カテゴリー必須
'category_id' => [
'required',
],
// 日付
'date' => [
'required',
'after:yesterday',
],
// 開始時間
'start_time' => [
'required',
],
// 終了時間
'end_time' => [
'required',
],
// 参加費
'entry_fee' => [
'required',
'numeric',
'integer',
'min:0',
],
// 詳細
'content' => [
'required',
],
];
return $validate;
}
}
validation.php
<?php
return [
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| as the size rules. Feel free to tweak each of these messages here.
|
*/
'accepted' => 'The :attribute must be accepted.',
'accepted_if' => 'The :attribute must be accepted when :other is :value.',
'active_url' => 'The :attribute is not a valid URL.',
'after' => ':attributeは今日以降の日付を選んでください。',
'after_or_equal' => 'The :attribute must be a date after or equal to :date.',
'alpha' => 'The :attribute must only contain letters.',
'alpha_dash' => 'The :attribute must only contain letters, numbers, dashes and underscores.',
'alpha_num' => 'The :attribute must only contain letters and numbers.',
'array' => 'The :attribute must be an array.',
'before' => 'The :attribute must be a date before :date.',
'before_or_equal' => 'The :attribute must be a date before or equal to :date.',
'between' => [
'numeric' => 'The :attribute must be between :min and :max.',
'file' => 'The :attribute must be between :min and :max kilobytes.',
'string' => 'The :attribute must be between :min and :max characters.',
'array' => 'The :attribute must have between :min and :max items.',
],
'boolean' => 'The :attribute field must be true or false.',
'confirmed' => 'The :attribute confirmation does not match.',
'current_password' => 'The password is incorrect.',
'date' => 'The :attribute is not a valid date.',
'date_equals' => 'The :attribute must be a date equal to :date.',
'date_format' => 'The :attribute does not match the format :format.',
'different' => 'The :attribute and :other must be different.',
'digits' => 'The :attribute must be :digits digits.',
'digits_between' => 'The :attribute must be between :min and :max digits.',
'dimensions' => 'The :attribute has invalid image dimensions.',
'distinct' => 'The :attribute field has a duplicate value.',
'email' => 'The :attribute must be a valid email address.',
'ends_with' => 'The :attribute must end with one of the following: :values.',
'exists' => 'The selected :attribute is invalid.',
'file' => 'The :attribute must be a file.',
'filled' => 'The :attribute field must have a value.',
'gt' => [
'numeric' => 'The :attribute must be greater than :value.',
'file' => 'The :attribute must be greater than :value kilobytes.',
'string' => 'The :attribute must be greater than :value characters.',
'array' => 'The :attribute must have more than :value items.',
],
'gte' => [
'numeric' => 'The :attribute must be greater than or equal to :value.',
'file' => 'The :attribute must be greater than or equal to :value kilobytes.',
'string' => 'The :attribute must be greater than or equal to :value characters.',
'array' => 'The :attribute must have :value items or more.',
],
'image' => 'The :attribute must be an image.',
'in' => 'The selected :attribute is invalid.',
'in_array' => 'The :attribute field does not exist in :other.',
'integer' => ':attributeは整数で入力してください。',
'ip' => 'The :attribute must be a valid IP address.',
'ipv4' => 'The :attribute must be a valid IPv4 address.',
'ipv6' => 'The :attribute must be a valid IPv6 address.',
'json' => 'The :attribute must be a valid JSON string.',
'lt' => [
'numeric' => 'The :attribute must be less than :value.',
'file' => 'The :attribute must be less than :value kilobytes.',
'string' => 'The :attribute must be less than :value characters.',
'array' => 'The :attribute must have less than :value items.',
],
'lte' => [
'numeric' => 'The :attribute must be less than or equal to :value.',
'file' => 'The :attribute must be less than or equal to :value kilobytes.',
'string' => 'The :attribute must be less than or equal to :value characters.',
'array' => 'The :attribute must not have more than :value items.',
],
'max' => [
'numeric' => 'The :attribute must not be greater than :max.',
'file' => 'The :attribute must not be greater than :max kilobytes.',
'string' => ':attributeは50文字以内で入力してください。',
'array' => 'The :attribute must not have more than :max items.',
],
'mimes' => 'The :attribute must be a file of type: :values.',
'mimetypes' => 'The :attribute must be a file of type: :values.',
'min' => [
'numeric' => ':attributeは0以上の数字を入力してください。',
'file' => 'The :attribute must be at least :min kilobytes.',
'string' => 'The :attribute must be at least :min characters.',
'array' => 'The :attribute must have at least :min items.',
],
'multiple_of' => 'The :attribute must be a multiple of :value.',
'not_in' => 'The selected :attribute is invalid.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => ':attributeは数字で入力してください。',
'password' => 'The password is incorrect.',
'present' => 'The :attribute field must be present.',
'regex' => 'The :attribute format is invalid.',
'required' => ':attributeは必須です。',
'required_if' => 'The :attribute field is required when :other is :value.',
'required_unless' => 'The :attribute field is required unless :other is in :values.',
'required_with' => 'The :attribute field is required when :values is present.',
'required_with_all' => 'The :attribute field is required when :values are present.',
'required_without' => 'The :attribute field is required when :values is not present.',
'required_without_all' => 'The :attribute field is required when none of :values are present.',
'prohibited' => 'The :attribute field is prohibited.',
'prohibited_if' => 'The :attribute field is prohibited when :other is :value.',
'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.',
'prohibits' => 'The :attribute field prohibits :other from being present.',
'same' => 'The :attribute and :other must match.',
'size' => [
'numeric' => 'The :attribute must be :size.',
'file' => 'The :attribute must be :size kilobytes.',
'string' => 'The :attribute must be :size characters.',
'array' => 'The :attribute must contain :size items.',
],
'starts_with' => 'The :attribute must start with one of the following: :values.',
'string' => 'The :attribute must be a string.',
'timezone' => 'The :attribute must be a valid timezone.',
'unique' => 'The :attribute has already been taken.',
'uploaded' => 'The :attribute failed to upload.',
'url' => 'The :attribute must be a valid URL.',
'uuid' => 'The :attribute must be a valid UUID.',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap our attribute placeholder
| with something more reader friendly such as "E-Mail Address" instead
| of "email". This simply helps us make our message more expressive.
|
*/
'attributes' => [
'title' => 'タイトル',
'category_id' => 'カテゴリーID',
'date' => '開催日',
'start_time' => '開始時間',
'end_time' => '終了時間',
'entry_fee' => '参加費',
'content' => '詳細',
],
];
register.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<form action="{{ route('event.create') }}" method="POST">
@csrf
{{-- タイトルフォーム --}}
<div class="form-group">
<label for="title">{{ 'タイトル' }}<span class="badge badge-danger ml-2">{{ '必須' }}</span></label>
<input type="text" class="form-control{{ $errors->has('title') ? ' is-invalid' : '' }}" name="title" id="title" value="{{ old('title') }}">
@if ($errors->has('title'))
<span class="invalid-feedback" role="alert">
{{ $errors->first('title') }}
</span>
@endif
</div>
{{-- カテゴリープルダウン --}}
<div class="form-group w-50">
<label for="category-id">{{ 'カテゴリー' }}<span class="badge badge-danger ml-2">{{ '必須' }}</span></label>
<select class="form-control{{ $errors->has('category_id') ? ' is-invalid' : '' }}" id="category-id" name="category_id">
@foreach ($categories as $category)
<option value="{{ $category->category_id }}">{{ $category->category_name }}</option>
@endforeach
</select>
@if ($errors->has('category_id'))
<span class="invalid-feedback" role="alert">
{{ $errors->first('category_id') }}
</span>
@endif
</div>
{{-- 開催日をカレンダーで選択 --}}
<div class="form-group w-25">
<label for="date">{{ '開催日' }}<span class="badge badge-danger ml-2">{{ '必須' }}</span></label>
<input type="date" class="form-control{{ $errors->has('date') ? ' is-invalid' : '' }}" name="date" id="date" value="{{ old('date') }}">
@if ($errors->has('date'))
<span class="invalid-feedback" role="alert">
{{ $errors->first('date') }}
</span>
@endif
</div>
{{-- もくもく会開催時間 --}}
<div class="form-group w-50">
<div class="row">
{{-- 開始時間 --}}
<div class="col">
<label for="start_time">{{ '開始時間' }}<span class="badge badge-danger ml-2">{{ '必須' }}</span></label>
<input type="time" class="form-control{{ $errors->has('start_time') ? ' is-invalid' : '' }}" name="start_time" id="start_time" value="{{ old('start_time') }}">
@if ($errors->has('start_time'))
<span class="invalid-feedback" role="alert">
{{ $errors->first('start_time') }}
</span>
@endif
</div>
{{-- 終了時間 --}}
<div class="col">
<label for="end_time">{{ '終了時間' }}<span class="badge badge-danger ml-2">{{ '必須' }}</span></label>
<input type="time" class="form-control{{ $errors->has('end_time') ? ' is-invalid' : '' }}" name="end_time" id="end_time" value="{{ old('end_time') }}">
@if ($errors->has('end_time'))
<span class="invalid-feedback" role="alert">
{{ $errors->first('end_time') }}
</span>
@endif
</div>
</div>
</div>
{{-- 参加費フォーム --}}
<div class="form-group w-25">
<label for="entry-fee">{{ '参加費' }}<span class="badge badge-danger ml-2">{{ '必須' }}</span></label>
<input type="text" class="form-control{{ $errors->has('entry_fee') ? ' is-invalid' : '' }}" name="entry_fee" id="entry-fee" value="{{ old('entry_fee') }}">
@if ($errors->has('entry_fee'))
<span class="invalid-feedback" role="alert">
{{ $errors->first('entry_fee') }}
</span>
@endif
</div>
{{-- もくもく会の詳細 --}}
<div class="form-group">
<label for="content">{{ '詳細' }}<span class="badge badge-danger ml-2">{{ '必須' }}</span></label>
<textarea class="form-control{{ $errors->has('content') ? ' is-invalid' : '' }}" name="content" id="content" rows="10" placeholder="もくもく会の詳細を記載してください。">{{ old('content') }}</textarea>
@if ($errors->has('content'))
<span class="invalid-feedback" role="alert">
{{ $errors->first('content') }}
</span>
@endif
</div>
<button type="submit" class="btn btn-success w-100">
{{ 'もくもく会を開催する' }}
</button>
</form>
</div>
@endsection
これでバリデーションの実装はできました。
次回は詳細画面の実装をしていきます。
休日で空いた時間の暇つぶしを探せるアプリを公開しています。
コメント