暇なときに予定を探せるアプリyoteiPickerをリリースしました。
実務でよく使うLaravelのバリデーションの実装方法について紹介します。
初級編・応用篇・実務篇と分けて解説していきますね。
開発環境
Docker 20.10.7
PHP 7.4.22
Laravel 8.53.1
mySQL 5.7
データベースのツール phpmyadmin
##想定するアプリ
今回想定するのは本を管理するアプリです。
本を登録する際にバリデーションをかけていきます。
登録画面はこんな感じになっています。

今回バリデーションをかけるのは本の名称だけにします。
関連するソースコードは以下のようになっています。
モデルBook.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
    use HasFactory;
    // モデルに関連付けるテーブル
    protected $table = 'books';
    // テーブルに関連付ける主キー
    protected $primaryKey = 'book_id';
    // 登録・更新可能なカラムの指定
    protected $fillable = [
        'book_id',
        'user_id',
        'category_id',
        'book_name',
        'created_at',
        'updated_at'
    ];
    /**
     * 登録処理
     */
    public function InsertBook($request)
    {
        // リクエストデータを基に管理マスターユーザーに登録する
        return $this->create([
            'book_name' => $request->book_name,
        ]);
    }
}
モデルCategory.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
    use HasFactory;
    // モデルに関連付けるテーブル
    protected $table = 'categories';
    // テーブルに関連付ける主キー
    protected $primaryKey = 'category_id';
    // 登録・更新可能なカラムの指定
    protected $fillable = [
        'category_id',
        'user_id',
        'category_name',
        'created_at',
        'updated_at'
    ];
    /**
     * カテゴリーテーブルの全件取得
     */
    public function findCategories()
    {
        return $this->all();
    }
}
コントローラーBookController.php
<?php
namespace App\Http\Controllers;
use App\Models\Book;
use App\Models\Category;
use Illuminate\Http\Request;
class BookController extends Controller
{
    public function __construct()
    {
        $this->book = new Book();
        $this->category = new Category();
    }
    /**
     * 登録画面
     */
    public function create(Request $request)
    {
        $categories = $this->category->findCategories();
        return view('book.create', compact('categories'));
    }
    /**
     * 登録処理
     */
    public function store(Request $request)
    {
        // リクエストされたデータを元に登録処理を実行
        $registerBook = $this->book->InsertBook($request);
        return redirect()->route('book.index');
    }
}
登録画面create.blade.php
@extends('layouts.app')
@section('content')
<div class="container small">
  <h1>本を登録</h1>
  <form action="{{ route('book.store') }}" method="POST">
  @csrf
    <fieldset>
      <div class="form-group">
        <label for="book_name">{{ __('本の名称') }}<span class="badge badge-danger ml-2">{{ __('必須') }}</span></label>
        <input type="text" class="form-control" name="book_name" id="book_name">
      </div>
      <!--  カテゴリープルダウン -->
      <div class="form-group">
        <label for="category-id">{{ __('カテゴリー') }}<span class="badge badge-danger ml-2">{{ __('必須') }}</span></label>
        <select class="form-control" id="category-id" name="category_id">
            @foreach ($categories as $category)
                <option value="{{ $category->category_id }}">{{ $category->category_name }}</option>
            @endforeach
        </select>
      </div>
      <div class="d-flex justify-content-between pt-3">
        <a href="{{ route('book.index') }}" class="btn btn-outline-secondary" role="button">
            <i class="fa fa-reply mr-1" aria-hidden="true"></i>{{ __('一覧画面へ') }}
        </a>
        <button type="submit" class="btn btn-success">
            {{ __('登録') }}
        </button>
      </div>
    </fieldset>
  </form>
</div>
@endsection
##〜初級編〜コントローラーにベタ書き
最初のステップとして、コントローラーにバリデーションをベタ書きする方法です。
Laravelでバリデーションを実装する入り口としてよく紹介されます。
実装方法はこんな感じ。BookController.php
    public function __construct()
    {
        $this->book = new Book();
    }
    /**
     * 登録処理
     */
    public function store(Request $request)
    {
        // 本の名称を入力必須とするバリデーション
        $validated = $request->validate([
            'book_name' => 'required'
        ]);
        // リクエストされたデータを元に登録処理を実行
        $registerBook = $this->book->InsertBook($request);
        return redirect()->route('book.index');
    }
laravelに元々用意されているvalidate関数を使うことでバリデーションが実装できます。
このままだとバリデーションメッセージが英語だし、book_nameもバリデーション側で定義していないので定義します。
resoources>lang>validation.phpを開いてください。101行目付近
    'required' => ':attributeは必須です',
に変更してください。
そして、154行目付近
    'attributes' => [
        'book_name' => '本の名称'
    ],
としてください。
あとは画面にバリデーションメッセージを表示させます。
      <div class="form-group">
        <label for="book_name">{{ __('本の名称') }}<span class="badge badge-danger ml-2">{{ __('必須') }}</span></label>
        <input type="text" class="form-control {{ $errors->has('book_name') ? 'is-invalid' : '' }}" name="book_name" id="book_name">
        @if ($errors->has('book_name'))
          <span class="invalid-feedback" role="alert">
            {{ $errors->first('book_name') }}
          </span>
        @endif
      </div>
三項演算子です。ifの条件分岐を1行でかけます。
{{ $errors->has('book_name') ? 'is-invalid' : '' }}
上記は、以下のif文と同じです。
      @if ($errors->has('book_name'))
        'is-invalid'
      @else
        ''
      @endif
エラーがあった時にis-invalidのクラスを与え、エラーがない場合は何もクラスに追加しないってロジックを表しています。
is-invalidがあると、フォームの枠が赤くなります。
※ブートストラップの仕様です。各自で導入してください。
$errors->first('book_name')とすることで、バリデーションに引っかかった最初のメッセージを取り出せます。
ちなみに「本の名称の長さを10文字以内にしたい」というバリデーションを追加でかけたい場合は、以下のようにできます。
    /**
     * 登録処理
     */
    public function store(Request $request)
    {
        // 本の名称を入力必須・名称の長さ10文字以内とするバリデーション
        $validated = $request->validate([
            'book_name' => 'required|max:10'
        ]);
        // リクエストされたデータを元に登録処理を実行
        $registerBook = $this->book->InsertBook($request);
        return redirect()->route('book.index');
    }
resources>lang>validation.php80行目付近
    'max' => [
        'numeric' => 'The :attribute must not be greater than :max.',
        'file' => 'The :attribute must not be greater than :max kilobytes.',
        'string' => ':attributeは10文字以内で入力してください。',
        'array' => 'The :attribute must not have more than :max items.',
    ],
このようにコントローラーにベタ書きでバリデーションは実装できます。
ただ、実務では使えないでしょう。なぜならコントローラーの記述量が膨大になるので、バリデーションはバリデーションで他のファイルに切り分けるからです。
それでは、バリデーションを他のファイルで実装させる方法を紹介します。
##〜応用篇〜バリデーションをコントローラーと切り分ける
バリデーションをコントローラーと切り分けるには、フォームリクエストを作成します。
イメージとしては、
app>Http>Requestsにフォームリクエストを作成
(ここにバリデーションを書く)
コントローラーでそのファイルを読み込む
(登録処理の際にバリデーションファイルを読み込んでバリデーション判定させる)
こんな感じです。
それでは実際に書いていきます。
フォームリクエストの作成
$ php artisan make:request BookRequestを実行
app>Http>Requests配下にBookRequest.phpが作成されます。
初期状態はこんな感じ。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class BookRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return false;
    }
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}
以下のように記述しましょう。
※authorize()のfalseからtrueに変更することを忘れないでください。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class BookRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        // falseからtrueに変更する
        return true;
    }
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        // コントローラーに記述していたバリデーションを移行
        return [
            'book_name' => 'required|max:10'
        ];
    }
}
コントローラーを変更します。
変更点は3つ
・use App\Http\Requests\BookRequest;追加
・store(BookRequest $request)に変更
・バリデーションの処理を削除
<?php
namespace App\Http\Controllers;
use App\Models\Book;
use App\Models\Category;
use Illuminate\Http\Request;
use App\Http\Requests\BookRequest;
class BookController extends Controller
{
    public function __construct()
    {
        $this->book = new Book();
        $this->category = new Category();
    }
    /**
     * 登録画面
     */
    public function create(Request $request)
    {
        $categories = $this->category->findCategories();
        return view('book.create', compact('categories'));
    }
    /**
     * 登録処理
     */
    public function store(BookRequest $request)
    {
        // リクエストされたデータを元に登録処理を実行
        $registerBook = $this->book->InsertBook($request);
        return redirect()->route('book.index');
    }
}
これでよりスマートにバリデーションをかけました。
これでもいいのですが、実務ではバリデーションの内容もより複雑になります。
現在は、requiredやmaxなど基本的なものですが、自作でバリデーション作成したりするので、時にはバリデーション1つで5〜6行使ったり、if文で条件分岐させたり...さまざまです。
なので、私がよく実務で使っている方法を紹介します。
##〜実務篇〜変数を用意して配列に格納していく
BookRequest.phpを改造します。
    public function rules()
    {
        $validate = [];
        $validate += [
            'book_name' => [
                'required',
                'max:10'
            ]
        ];
        return $validate;
    }
ここでやっていることは最終的に$validateという変数にバリデーションを配列で格納しています。
最初に$validateの空の配列を用意します。
バリデーションに引っ掛かれば、$validateにどんどん配列として格納されるってロジックです。
これであればバリデーションのロジックが長くなってもコードを追いやすくなってみやすいです。
    public function rules()
    {
        $validate = [];
        $validate += [
            'book_name' => [
                'required',
                'max:10'
            ]
        ];
        $validate += [
            'category_name' => [
                'required',
                'max:10'
            ]
        ];
        return $validate;
    }
みたいな感じです。
今回はここまで!
最後までありがとうございました〜
暇なときに予定を探せるアプリyoteiPickerをリリースしました。


