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' |
nullable | null許容 | 'bio' => 'nullable|string' |
string | 文字列のみ | 'name' => 'string' |
integer | 整数のみ | 'age' => 'integer' |
boolean | 真偽値 | 'active' => 'boolean' |
email | メールアドレス形式 | 'email' => 'email' |
url | URL形式 | '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/langphp artisan lang:publishconfig/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()や言語ファイルでエラーメッセージを日本語化できる- ドット記法で配列・ネストデータのバリデーションができる