Laravelバリデーションをマスターする:基礎から上級テクニックまで

Laravelを使う理由の大部分を占めるのがバリデーション設定の豊富さです。

バリデーション処理を1から実装するのはなかなか大変で、あれもこれもとやる事がとにかく多いのですがLaravelのバリデーションを活用することで労力が格段に減ります。

今回はそんなLaravelのバリデーションについて、最小限の労力で済ます使い方のコツや、実務で使ってきた私自身の経験を元にしてまとめて行こうと思います。

この記事を読むことで解決することは以下になります。

  • バリデーションの使い方
  • エラー文言の日本語化
  • バリデーションルールで実際に使えるもの
    • ルールの一覧を確認する方法
  • API対応やハマりポイント
  • 独自ルールの作り方
  • 込み入ったバリデーションの分離方法

今回は2023年6月現在、最新バージョンであるLaravel10系の情報でまとめていきます。

Laravel バリデーションの基本的な使い方

バリデーションは Requestクラス から簡単に呼び出す事ができます。

/**
 * Store a new blog post.
 */
public function store(Request $request): RedirectResponse
{
    $validated = $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
    ]);

    dd($validated); // ['title' => '<入力値>', 'body' => '<入力値>']

    return redirect('/posts');
}

https://laravel.com/docs/10.x/validation#quick-writing-the-validation-logic

この記述によって

  • POSTかGETリクエストのパラメーター
    • title
      • 必須入力
      • Postモデルに登録されたデータでtitleの重複無し
      • 最大255文字
    • body
      • 必須入力

のバリデーション処理が完了し、バリデーションが通過した場合には $validated 変数に配列として titlebody へのアクセスが出来ます。

エラーの場合には入力画面にGETアクセスで戻されて入力値がセッションに格納されます。セッションに入った入力値は old() で表示出来ます

<input type="text" name="title" value="{{ old('title') }}">

エラー文言の表示方法は以下になります。

<!-- /resources/views/post/create.blade.php -->
// まとめてリスト表示
 
<h1>Create Post</h1>
 
@if ($errors->any())
    <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif
 
<!-- Create Post Form -->

// 単体で表示
@error($name)
    <div class="alert alert-danger">{{ $errors->first('title') }}</div>
    or
    // @errorの中では $message 変数が勝手に宣言されます
    <div class="alert alert-danger">{{ $message }}</div>
@enderror

基本的な使い方は以上になります。

バリデーションルールが基本的には | (パイプ)区切りで設定できますが、数が多くなったり読みづらい場合には配列での設定も可能です

$validatedData = $request->validate([
    'title' => ['required', 'unique:posts', 'max:255'],
    'body' => ['required'],
]);

POSTパラメータがネストされてたり

$request->validate([
    'title' => 'required|unique:posts|max:255',
    'author.name' => 'required',
    'author.description' => 'required',
]);

ネストされたパラメータ配列の1番目のみバリデーションルールを適応することもできます。

$request->validate([
    'title' => 'required|unique:posts|max:255',
    'v1\.0' => 'required',
]);

文字列での指定だと打ち間違いがわからない懸念もあります。クラスでの指定も出来ます。(全ての指定が対応しているわけではありません)

use Illuminate\Validation\Rule;

$request->validate([
    'title' => ['required', Rule::unique('posts'), 'max:255'],
]);

エラー文言の日本語化

デフォルトでは英語表記でのエラー文言になっているのを日本語表示に変更します。

まずは直接設定する方法。

$request->validate([
    'title' => 'required|unique:posts|max:255',
    'author.name' => 'required',
    'author.description' => 'required',
], [
    'title.required' => 'タイトルは入力が必須です',
    'title.unique:posts' => 'タイトルが重複しています',
    'title.max:255' => 'タイトルの文字数は255文字以内に収めてください',
    'author.name.required' => '投稿者の名前は必須です',
    'author.description.required' => '投稿者の詳細は必須です',
]);

日本語化自体はこれで設定できるのですが、必須の言い回しを共通化する方法もあります。

$request->validate([
    'title' => 'required|unique:posts|max:255',
    'author.name' => 'required',
    'author.description' => 'required',
], [
    'required' => ':attributeは入力が必須です',
], [
    'title' => 'タイトル',
    'author.name' => '投稿者の名前',
    'author.description' => '投稿者の詳細',
]);

ルールでの指定にすることで :attribute を使用した汎用表示になります。第3引数に :attribute に該当する文言が指定できます。

バリデーションの設定に毎回エラー文言を指定するのも手間になるので、バリデーションが複数に渡る場合にはアプリケーション全体の日本語化設定を行いましょう。

アプリケーション全体のエラーメッセージ設定はこちらが参考になります。

【Laravel】認証メール・エラーメッセージを日本語化する|Into the Progra

https://laravel.com/docs/10.x/validation#specifying-custom-messages-in-language-files

バリデーションルール: 最小限版

ここからは設定できるバリデーションルールについてまとめていきます。

実務で使ってきた現場レベルでの最小設定です。効果的な方法は色々あれど、とりあえずこれだけ知っておけば汎用的に使えます。

required: 必須

$request->validate([
    'title' => 'required',
]);

必須入力の設定です。

パラメータとして存在していなければエラーになります。

nullable: null許可 (必須ではない)

$request->validate([
    'title' => 'nullable',
]);

必須の逆でなくても問題ない指定になります。

パラメータが無くても問題ないが、リクエストがあれば受け取る指定です。わざわざ nullable を指定しなくても空白でも同じ処理になるのですが、明示的に指定した方が何を期待しているのか分かりやすくなります。

string: 文字列

$request->validate([
    'title' => 'string',
]);

文字列指定です。

無くても構いませんが、厳密に設定した方がいい場合には設定します。

integer: 数値

$request->validate([
    'age' => 'integer',
]);

数値の指定です。

PHPは文字列や数値を勝手に変換してしまう挙動があるので数値が来るのがわかっていれば指定しておきましょう。

url: URLの書式

$request->validate([
    'company_url' => 'url',
]);

URLの入力チェックです。URL以外の入力は書式エラーになります。

email: メールアドレスの書式

$request->validate([
    'company_email' => 'email',
]);

メールアドレスのチェックです。

正規表現でメールアドレスを確認するのは非常に難易度が高いので、素直にフレームワークに載っているルールでバリデーションしましょう。

accepted: 同意チェックボックス

$request->validate([
    'agree' => 'accepted',
]);

agreeパラメータが同意チェックボックスになることを想定。プライバシーポリシーや利用規約などの確認に使用されます。

チェックしてなければエラー処理に入ります。

ファイル系

$request->validate([
    'file' => 'nullable|file|mimes:jpg,bmp,png|max:1024',
]);
  • fileパラメータ
    • nullable: null許可
    • file: ファイルリクエスト限定
    • mimes: MIMEタイプ(ファイルの種類)がjpgとbmpとpng限定
    • max: ファイルサイズ上限が1024バイト(1MB)制限

ファイル系の取り扱いはPHPファイルを偽造してアップロードされる危険性があるので注意してバリデーションしましょう。

もっと知りたい場合、公式ドキュメントで確認

以上で汎用的に実務で設定してきたルールについての紹介でした。

今回紹介したルールについては汎用的に使える物のみピックアップしました。というか私がLaravelプロジェクトで実際に使ってきたルールなので紹介もしやすかったです。

紹介した物以上に設定できるものや設定してきた物も存在しますが、全て紹介するものでも無いのでもっと見たい場合には公式ドキュメントを見ましょう。

(uniqueやinなど便利に使える設定もあります)

こちらで使用できるルールが一括で確認できるので使えるものを把握しておくのも実務に活用させやすいかと思います。

https://laravel.com/docs/10.x/validation#available-validation-rules

実務で経験したハマりポイントやコツ

ここからは設定するポイントです。私が実務レベルで活用していくなかで盲点だった所や工夫した部分です。

ネストされたパラメータ全てに適応

$request->validate([
    'file.*' => 'nullable|file|mimes:jpg,bmp,png|max:1024',
]);

$files = $request->file('file', []);
$paths = [];
if (count($files)) {
    foreach ($files as $file) {
        $paths[] = $file->store('imgs');
    }
}

file.* の指定がポイントです。

複数ファイルのアップロードを対応させる場合に *(アスタリスク) での指定が可能になります。

バリデーションエラー時の挙動変更

エラー時には入力画面に戻りますが、別のページに遷移させたり他の挙動を実装したい場合には Validator クラスを使用します。

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
 
    public function store(Request $request): RedirectResponse
    {
        $validator = Validator::make($request->all(), [
            'title' => 'required|unique:posts|max:255',
            'body' => 'required',
        ]);
 
        if ($validator->fails()) {
            return redirect('post/create')
                        ->withErrors($validator)
                        ->withInput();
        }
 
        // Store the blog post...
 
        return redirect('/posts');
    }

エラー文言は ->withErrors()でセッションに格納されて渡ります。 ->withInput() は入力値です。

ルーティングでModelの存在チェック

Userページを実装する際に存在しないUserがリクエストされたらエラーにしたい場合。

まずはバリデーションなどで確認する方法等々

use App\Http\Controllers\UserController;
 
Route::get('/user/{name}', [UserController::class, 'show']);
public function show(Request $request, $name)
{
    $request->validate([
        'name' => 'required|exists:App\Models\User,name',
    ]);

    // もしくは
    $user = User::where('name', $name)->first();
    if (!$user) {
        return abort(403);
    }
}

これがバリデーションを使わないでチェックする事ができます。

use App\Http\Controllers\UserController;
 
Route::get('/user/{user:name}', [UserController::class, 'show']);
use App\Models\User;

public function show(Request $request, User $user)
{
    return $user;
}

https://laravel.com/docs/10.x/routing#customizing-the-key

API対応

API向けにバリデーションの対応は必要ありません。

LaravelはリクエストヘッダーがJSON形式を指定しているのであれば422ステータスで勝手にJSON形式のエラーとして返却してくれます。

以下はエラー時のJSONレスポンスの例です。

{
    "message": "The team name must be a string. (and 4 more errors)",
    "errors": {
        "team_name": [
            "The team name must be a string.",
            "The team name must be at least 1 characters."
        ],
        "authorization.role": [
            "The selected authorization.role is invalid."
        ],
        "users.0.email": [
            "The users.0.email field is required."
        ],
        "users.2.email": [
            "The users.2.email must be a valid email address."
        ]
    }
}

messageerrors としてデータを返却してくれます。

注意するべき点はエラーキーがネストに対応していない点です。ネストされたリクエストパラメータは . (ドット) 形式で区切られて返却されます。

APIのサーバーサイドとしては対応が必要ありませんが、APIを受け取るフロントサイド(アプリ開発者等) にはエラー時の型定義を提供する必要があります。

独自ルールの作り方

Laravel バリデーションルールに存在しない場合、独自ルールを作る方法です。

$ php artisan make:rule Uppercase
<?php
 
namespace App\Rules;
 
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
 
class Uppercase implements ValidationRule
{
    /**
     * Run the validation rule.
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (strtoupper($value) !== $value) {
            $fail('The :attribute must be uppercase.');
        }
    }
}
use App\Rules\Uppercase;
 
$request->validate([
    'name' => ['required', 'string', new Uppercase],
]);

公式ドキュメント: https://laravel.com/docs/10.x/validation#custom-validation-rules

バリデーションの切り分け方

込み入ったバリデーションを作成する場合、Request クラスを作成して処理を切り分ける事でコントローラの肥大化を防ぐ事ができます。

$ php artisan make:request StorePostRequest
/**
 * Get the validation rules that apply to the request.
 *
 * @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
 */
public function rules(): array
{
    return [
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
    ];
}
/**
 * Store a new blog post.
 */
public function store(StorePostRequest $request): RedirectResponse
{
    // The incoming request is valid...
 
    // Retrieve the validated input data...
    $validated = $request->validated();
 
    // Retrieve a portion of the validated input data...
    $validated = $request->safe()->only(['name', 'email']);
    $validated = $request->safe()->except(['name', 'email']);
 
    // Store the blog post...
 
    return redirect('/posts');
}

公式ドキュメント: https://laravel.com/docs/10.x/validation#form-request-validation

Laravel バリデーション、終わりに

バリデーションは1から実装すると奥が深いですし、考慮されていない物があるとエラーの原因になったり最悪格納データが漏れてしまって損害賠償問題にもなりかねません。

1から実装してしまえば自分自身が把握しやすくはなりますが、チームでの作業分担も出来なくなったり、考慮出来ていないパターンが発生します。

Laravelフレームワークの形に沿った方法で適切にバリデーションを設定していくのがベストの選択です。自分の経験と自分の知らなかったオープンソースに組み込まれた世界中からの経験を合わせる事で、より良いアプリケーションを開発する事ができます。

今まで無かった新しいバグもフレームワークに組み込まれていくのでセキュリティアップデートとして対応するだけで将来的なメンテナンスコストも最小限にする事ができます。

バリデーションの設定を上手く活用していきましょう。

今回もお疲れ様でした。

投稿者


Comments

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA