Laravel

Laravelバリデーション入門|入力値を正しく検証する方法

Laravel バリデーション フォーム

Laravelバリデーション入門
入力値を正しく検証する方法

Laravelのバリデーションを基礎から解説。基本ルール、Form Request、カスタムルール、エラーメッセージのカスタマイズまで学べます。

こんな人向けの記事です

  • Laravelのバリデーションを学びたい
  • Form Requestの使い方を知りたい
  • カスタムバリデーションルールを作りたい

Step 1バリデーションの基本

Laravelでは、コントローラー内で$request->validate()メソッドを呼ぶだけで、簡単に入力値の検証ができます。バリデーションに失敗すると、自動的に前のページにリダイレクトされ、エラーメッセージがセッションに保存されます。

基本的なバリデーション

app/Http/Controllers/PostController.php
public function store(Request $request)
{
    $validated = $request->validate([
        'title' => 'required|max:255',
        'body'  => 'required',
        'email' => 'required|email',
    ]);

    // バリデーション通過後、$validated には検証済みデータのみ含まれる
    Post::create($validated);

    return redirect()->route('posts.index')
        ->with('success', '投稿を作成しました');
}
validate() の動作
バリデーションに成功すると、検証済みデータの配列が返されます。
バリデーションに失敗すると、ValidationExceptionがスローされ、自動的に前のページにリダイレクトされます。APIリクエストの場合は422 JSONレスポンスが返されます。

Bladeテンプレートでのエラー表示

resources/views/posts/create.blade.php
<form method="POST" action="{{ route('posts.store') }}">
    @csrf
    <div>
        <label>タイトル</label>
        <input type="text" name="title" value="{{ old('title') }}">
        @error('title')
            <span class="text-danger">{{ $message }}</span>
        @enderror
    </div>

    <div>
        <label>本文</label>
        <textarea name="body">{{ old('body') }}</textarea>
        @error('body')
            <span class="text-danger">{{ $message }}</span>
        @enderror
    </div>

    <button type="submit">投稿する</button>
</form>
old() ヘルパー
old('title')は、バリデーション失敗時に前回入力した値を復元します。ユーザーが入力し直す手間を省ける重要な機能です。

ルール指定の書き方

バリデーションルールは、パイプ区切りの文字列か配列で指定できます。

ルール指定の2つの書き方
// パイプ区切り(シンプルなルール向き)
'email' => 'required|email|max:255'

// 配列(複雑なルール向き・推奨)
'email' => ['required', 'email', 'max:255']

Step 2主要なバリデーションルール

Laravelには90以上のバリデーションルールが組み込まれています。ここでは実務で特によく使うルールを紹介します。

基本ルール一覧

ルール説明
required必須項目'name' => 'required'
nullablenull許容'bio' => 'nullable|string'
string文字列のみ'name' => 'string'
integer整数のみ'age' => 'integer'
boolean真偽値'active' => 'boolean'
emailメールアドレス形式'email' => 'email'
urlURL形式'website' => 'url'
date日付形式'birthday' => 'date'

サイズ・範囲ルール

ルール説明
min:値最小値(文字数/数値/配列数)'password' => 'min:8'
max:値最大値'title' => 'max:255'
between:min,max範囲指定'age' => 'between:18,65'
size:値完全一致(文字数/数値/配列数)'code' => 'size:6'
digits:値指定桁数の数値'zip' => 'digits:7'

データベース関連ルール

unique / exists ルール
// ユニーク制約(新規登録時)
'email' => 'unique:users,email'

// ユニーク制約(更新時:自分自身を除外)
'email' => 'unique:users,email,' . $user->id

// Ruleクラスを使った書き方(推奨)
use Illuminate\Validation\Rule;

'email' => [
    'required',
    Rule::unique('users', 'email')->ignore($user->id),
]

// 存在チェック(外部キーの検証)
'category_id' => 'exists:categories,id'
unique ルールの注意点
更新処理でuniqueルールを使う場合、自分自身のレコードを除外しないと、自分のデータでバリデーションエラーになります。必ずRule::unique()->ignore()を使いましょう。

日付ルール

日付関連のバリデーション
'start_date' => 'required|date|after:today',
'end_date'   => 'required|date|after:start_date',
'birthday'   => 'required|date|before:today',
'event_date' => 'required|date_format:Y-m-d',

ファイルルール

ファイルアップロードのバリデーション
'avatar' => 'required|image|mimes:jpeg,png,gif|max:2048',  // 最大2MB
'document' => 'required|file|mimes:pdf,doc,docx|max:10240', // 最大10MB
'photos' => 'required|array|max:5',
'photos.*' => 'image|max:2048',

Step 3Form Requestによるバリデーション

コントローラーにバリデーションロジックを書くと、コードが肥大化します。Form Requestを使えば、バリデーションを専用クラスに分離して管理できます。

Form Requestの作成

ターミナル
php artisan make:request StorePostRequest
app/Http/Requests/StorePostRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
    /**
     * リクエストの認可判定
     */
    public function authorize(): bool
    {
        // trueを返すと全ユーザーに許可
        // 権限チェックが必要な場合はここにロジックを書く
        return true;
    }

    /**
     * バリデーションルール
     */
    public function rules(): array
    {
        return [
            'title'       => ['required', 'string', 'max:255'],
            'body'        => ['required', 'string'],
            'category_id' => ['required', 'exists:categories,id'],
            'tags'        => ['nullable', 'array'],
            'tags.*'      => ['exists:tags,id'],
            'thumbnail'   => ['nullable', 'image', 'max:2048'],
        ];
    }

    /**
     * バリデーション属性名(日本語化)
     */
    public function attributes(): array
    {
        return [
            'title'       => 'タイトル',
            'body'        => '本文',
            'category_id' => 'カテゴリ',
            'tags'        => 'タグ',
            'thumbnail'   => 'サムネイル',
        ];
    }
}

コントローラーでの使用

app/Http/Controllers/PostController.php
use App\Http\Requests\StorePostRequest;

public function store(StorePostRequest $request)
{
    // Form Requestの型でタイプヒントするだけ
    // バリデーションは自動実行される
    $validated = $request->validated();

    Post::create($validated);

    return redirect()->route('posts.index')
        ->with('success', '投稿を作成しました');
}
Form Request のメリット
1. コントローラーがスッキリ:バリデーションロジックが別ファイルに分離される
2. 再利用可能:複数のコントローラーで同じForm Requestを使える
3. テストしやすい:バリデーションロジックを単体テストできる
4. 認可と検証を一箇所で管理authorize()rules()がセットになる

更新用Form Requestの例

app/Http/Requests/UpdatePostRequest.php
use Illuminate\Validation\Rule;

class UpdatePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        // 投稿の所有者のみ許可
        return $this->user()->id === $this->route('post')->user_id;
    }

    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'body'  => ['required', 'string'],
            'slug'  => [
                'required',
                'string',
                Rule::unique('posts')->ignore($this->route('post')),
            ],
        ];
    }
}

Step 4カスタムバリデーションルール

標準ルールでカバーできない検証が必要な場合、独自のバリデーションルールを作成できます。

Ruleクラスの作成

ターミナル
php artisan make:rule JapanesePhoneNumber
app/Rules/JapanesePhoneNumber.php
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class JapanesePhoneNumber implements ValidationRule
{
    /**
     * バリデーション実行
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        // 日本の電話番号形式をチェック
        if (!preg_match('/^0[0-9]{9,10}$/', preg_replace('/[-\s]/', '', $value))) {
            $fail(':attributeは正しい日本の電話番号形式で入力してください。');
        }
    }
}

カスタムルールの使用

バリデーションルールでの使用
use App\Rules\JapanesePhoneNumber;

'phone' => ['required', new JapanesePhoneNumber],

クロージャによるカスタムルール

簡単なルールであれば、クロージャで直接記述することもできます。

クロージャを使ったカスタムルール
'username' => [
    'required',
    'string',
    function (string $attribute, mixed $value, Closure $fail) {
        // NGワードチェック
        $ngWords = ['admin', 'root', 'system'];
        if (in_array(strtolower($value), $ngWords)) {
            $fail("この{}は使用できません。");
        }
    },
],

条件付きバリデーション

sometimes / required_if の活用
// フィールドが存在する場合のみバリデーション
'nickname' => 'sometimes|string|max:50',

// 別フィールドの値に応じてバリデーション
'company_name' => 'required_if:user_type,corporate',
'tax_id'       => 'required_if:user_type,corporate|digits:13',

// 複数条件
'shipping_address' => 'required_unless:pickup,true',
クロージャ vs Ruleクラス
一度きりの簡単な検証にはクロージャが便利ですが、再利用する予定がある場合はRuleクラスを作成しましょう。Ruleクラスはテストも書きやすく、保守性が高くなります。

Step 5エラーメッセージのカスタマイズ

デフォルトのエラーメッセージは英語です。日本語化や、独自のメッセージに変更する方法を解説します。

コントローラーでのカスタムメッセージ

コントローラー内で直接指定
$request->validate([
    'title' => 'required|max:255',
    'email' => 'required|email|unique:users',
], [
    'title.required' => 'タイトルは必須です。',
    'title.max'      => 'タイトルは255文字以内で入力してください。',
    'email.required' => 'メールアドレスは必須です。',
    'email.email'    => '正しいメールアドレスを入力してください。',
    'email.unique'   => 'このメールアドレスは既に登録されています。',
]);

Form Requestでのカスタムメッセージ

app/Http/Requests/StorePostRequest.php
class StorePostRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'body'  => ['required', 'string', 'min:10'],
        ];
    }

    /**
     * カスタムエラーメッセージ
     */
    public function messages(): array
    {
        return [
            'title.required' => 'タイトルを入力してください。',
            'title.max'      => 'タイトルは:max文字以内です。',
            'body.required'  => '本文を入力してください。',
            'body.min'       => '本文は:min文字以上で入力してください。',
        ];
    }

    /**
     * 属性名のカスタマイズ
     */
    public function attributes(): array
    {
        return [
            'title' => 'タイトル',
            'body'  => '本文',
        ];
    }
}

言語ファイルによる一括日本語化

lang/ja/validation.php(一部抜粋)
return [
    'required' => ':attributeは必須です。',
    'email'    => ':attributeは有効なメールアドレスを指定してください。',
    'max' => [
        'string'  => ':attributeは:max文字以内で入力してください。',
        'numeric' => ':attributeは:max以下で入力してください。',
        'file'    => ':attributeは:maxキロバイト以下にしてください。',
    ],
    'min' => [
        'string'  => ':attributeは:min文字以上で入力してください。',
        'numeric' => ':attributeは:min以上で入力してください。',
    ],
    'unique' => 'この:attributeは既に使用されています。',

    // 属性名の日本語化
    'attributes' => [
        'name'     => '名前',
        'email'    => 'メールアドレス',
        'password' => 'パスワード',
        'title'    => 'タイトル',
        'body'     => '本文',
    ],
];
日本語言語ファイルの導入
Laravel公式の日本語言語パックは以下のコマンドでインストールできます。
composer require laravel-lang/lang
php artisan lang:publish
config/app.php'locale' => 'ja'に設定すると、バリデーションメッセージが日本語になります。

Step 6配列・ネストデータのバリデーション

フォームで複数の項目を一括入力する場合や、APIでネストしたJSONデータを受け取る場合のバリデーション方法を解説します。

配列データのバリデーション

配列のバリデーション
// 入力データ例:
// {"tags": ["PHP", "Laravel", "Vue.js"]}

$request->validate([
    'tags'   => 'required|array|min:1|max:10',
    'tags.*' => 'required|string|max:50',
]);

ネストデータのバリデーション

ネストした配列のバリデーション
// 入力データ例:
// {
//   "items": [
//     {"product_id": 1, "quantity": 2},
//     {"product_id": 3, "quantity": 1}
//   ]
// }

$request->validate([
    'items'              => 'required|array|min:1',
    'items.*.product_id' => 'required|exists:products,id',
    'items.*.quantity'   => 'required|integer|min:1|max:99',
]);

実践例:注文フォーム

app/Http/Requests/StoreOrderRequest.php
class StoreOrderRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            // 注文者情報
            'customer_name'    => ['required', 'string', 'max:100'],
            'customer_email'   => ['required', 'email'],
            'customer_phone'   => ['required', new JapanesePhoneNumber],

            // 配送先
            'shipping.zip'     => ['required', 'digits:7'],
            'shipping.address' => ['required', 'string', 'max:500'],

            // 注文明細(配列)
            'items'              => ['required', 'array', 'min:1', 'max:50'],
            'items.*.product_id' => ['required', 'exists:products,id'],
            'items.*.quantity'   => ['required', 'integer', 'min:1', 'max:99'],
            'items.*.options'    => ['nullable', 'array'],
            'items.*.options.*'  => ['string', 'max:100'],
        ];
    }

    public function messages(): array
    {
        return [
            'items.required'            => '商品を1つ以上選択してください。',
            'items.*.product_id.exists' => ':position番目の商品が見つかりません。',
            'items.*.quantity.min'      => '数量は1以上を指定してください。',
        ];
    }

    public function attributes(): array
    {
        return [
            'customer_name'    => 'お名前',
            'customer_email'   => 'メールアドレス',
            'customer_phone'   => '電話番号',
            'shipping.zip'     => '郵便番号',
            'shipping.address' => '住所',
        ];
    }
}
ドット記法のまとめ
items.* :配列の各要素
items.*.name :配列の各要素のnameプロパティ
shipping.zip :ネストしたオブジェクトのプロパティ
items.*.options.* :二重ネストの各要素

まとめチェックリスト

  • $request->validate()で基本的なバリデーションを実装できる
  • required, email, min, max, unique など主要ルールを理解した
  • Form Requestでバリデーションをコントローラーから分離できる
  • Ruleクラスやクロージャでカスタムルールを作成できる
  • messages()や言語ファイルでエラーメッセージを日本語化できる
  • ドット記法で配列・ネストデータのバリデーションができる