Laravel Validation
GitHub概要
laravel/framework
The Laravel Framework.
スター33,844
ウォッチ958
フォーク11,444
作成日:2013年1月10日
言語:PHP
ライセンス:MIT License
トピックス
frameworklaravelphp
スター履歴
データ取得日時: 2025/7/19 09:31
ライブラリ
Laravel Validation
概要
Laravel Validationは、LaravelフレームワークにビルトインされたPHP向けの包括的なバリデーションシステムです。豊富な組み込みルール、フォームリクエスト統合、カスタムバリデーターのサポートにより、シンプルな入力検証から複雑なビジネスロジックまで対応可能。MVCアーキテクチャと完全に統合され、コントローラーを清潔に保ちながら堅牢なデータ検証を実現します。2025年現在、PHPウェブ開発における最も人気のあるバリデーションソリューションの一つです。
詳細
Laravel 11.xおよび12.xは2025年現在の最新版で、世界中の数百万のプロジェクトで使用されているPHPエコシステムの標準的なバリデーションシステムです。60以上の組み込みバリデーションルールを提供し、必須フィールド、データ型、フォーマット、サイズ、データベース検証など幅広い検証ニーズに対応。フォームリクエストクラスによる検証ロジックのカプセル化、カスタムルールオブジェクトの作成、条件付きバリデーション、配列とネストデータの検証など、エンタープライズレベルの要件に対応する高度な機能を備えています。
主な特徴
- 60以上の組み込みルール: required、email、unique、exists、minなど豊富なバリデーションルール
- フォームリクエスト統合: 検証ロジックを専用クラスにカプセル化し、コントローラーを清潔に保つ
- カスタムバリデーター: クロージャ、ルールオブジェクト、バリデーター拡張による柔軟なカスタマイズ
- 自動エラーハンドリング: Webリクエストでは自動リダイレクト、APIでは422 JSONレスポンス
- 多言語対応: エラーメッセージの国際化と簡単なカスタマイズ
- 条件付きバリデーション: 動的なルール適用とコンテキストベースの検証
メリット・デメリット
メリット
- Laravelフレームワークとの完全な統合による開発効率の向上
- 豊富な組み込みルールセットによる即座の生産性
- フォームリクエストによるクリーンなコード構造の実現
- 自動エラーハンドリングとリダイレクト機能
- 日本語を含む多言語対応の充実したサポート
- アクティブなコミュニティと豊富なドキュメント
デメリット
- Laravelフレームワーク外での使用が困難(スタンドアロン利用に制限)
- 学習曲線が急(多くの機能と規約の理解が必要)
- 小規模プロジェクトではオーバーエンジニアリングになる可能性
- パフォーマンスオーバーヘッド(特に大量のデータ検証時)
- カスタマイズが複雑になりがちな場合がある
- フレームワークのバージョンアップ時の互換性問題
参考ページ
書き方の例
基本的なバリデーション実装
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* 基本的なバリデーション例
*/
public function store(Request $request)
{
// シンプルなバリデーション
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed',
'age' => 'nullable|integer|min:18|max:100',
'terms' => 'accepted'
]);
// バリデーション成功後の処理
// $validatedには検証済みデータのみが含まれる
User::create($validated);
return redirect('/users')->with('success', 'ユーザーが作成されました');
}
/**
* 配列形式でのルール定義
*/
public function update(Request $request, $id)
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => [
'required',
'email',
Rule::unique('users')->ignore($id)
],
'profile' => ['nullable', 'array'],
'profile.bio' => ['nullable', 'string', 'max:1000'],
'profile.website' => ['nullable', 'url'],
'skills' => ['required', 'array', 'min:1'],
'skills.*' => ['required', 'string', 'distinct']
]);
User::findOrFail($id)->update($validated);
return redirect()->back()->with('success', '更新が完了しました');
}
}
// カスタムエラーメッセージの定義
public function storeWithCustomMessages(Request $request)
{
$messages = [
'name.required' => '名前は必須項目です',
'email.required' => 'メールアドレスを入力してください',
'email.email' => '有効なメールアドレスを入力してください',
'email.unique' => 'このメールアドレスは既に使用されています',
'password.min' => 'パスワードは:min文字以上で入力してください',
'password.confirmed' => 'パスワードが確認用と一致しません'
];
$attributes = [
'name' => '名前',
'email' => 'メールアドレス',
'password' => 'パスワード'
];
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed'
], $messages, $attributes);
// 処理を継続...
}
フォームリクエストの実装
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StoreUserRequest extends FormRequest
{
/**
* ユーザーがこのリクエストを実行する権限があるかを判定
*/
public function authorize(): bool
{
// 認証されたユーザーのみ許可
return auth()->check();
}
/**
* バリデーションルールの定義
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'unique:users,email'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
'role' => ['required', Rule::in(['admin', 'user', 'moderator'])],
'profile_image' => ['nullable', 'image', 'max:2048'], // 2MBまで
'birth_date' => ['nullable', 'date', 'before:today'],
'phone' => ['nullable', 'regex:/^0[0-9]{9,10}$/'], // 日本の電話番号
'postal_code' => ['nullable', 'regex:/^\d{3}-?\d{4}$/'], // 日本の郵便番号
'preferences' => ['nullable', 'array'],
'preferences.notifications' => ['boolean'],
'preferences.newsletter' => ['boolean']
];
}
/**
* カスタムエラーメッセージ
*/
public function messages(): array
{
return [
'name.required' => '名前を入力してください',
'name.max' => '名前は255文字以内で入力してください',
'email.required' => 'メールアドレスを入力してください',
'email.email' => '有効なメールアドレスを入力してください',
'email.unique' => 'このメールアドレスは既に登録されています',
'password.required' => 'パスワードを入力してください',
'password.min' => 'パスワードは8文字以上で入力してください',
'password.confirmed' => 'パスワードが確認用と一致しません',
'role.required' => '役割を選択してください',
'role.in' => '無効な役割が選択されました',
'profile_image.image' => '画像ファイルをアップロードしてください',
'profile_image.max' => '画像は2MB以下にしてください',
'birth_date.date' => '有効な日付を入力してください',
'birth_date.before' => '誕生日は今日より前の日付を入力してください',
'phone.regex' => '有効な電話番号を入力してください(例:090-1234-5678)',
'postal_code.regex' => '有効な郵便番号を入力してください(例:123-4567)'
];
}
/**
* カスタム属性名
*/
public function attributes(): array
{
return [
'name' => '名前',
'email' => 'メールアドレス',
'password' => 'パスワード',
'role' => '役割',
'profile_image' => 'プロフィール画像',
'birth_date' => '誕生日',
'phone' => '電話番号',
'postal_code' => '郵便番号'
];
}
/**
* バリデーション前のデータ準備
*/
protected function prepareForValidation(): void
{
// 電話番号からハイフンを削除
if ($this->has('phone')) {
$this->merge([
'phone' => str_replace('-', '', $this->phone)
]);
}
// メールアドレスを小文字に変換
if ($this->has('email')) {
$this->merge([
'email' => strtolower($this->email)
]);
}
}
/**
* バリデーション後の処理
*/
protected function passedValidation(): void
{
// パスワードをハッシュ化
$this->merge([
'password' => bcrypt($this->password)
]);
}
}
// コントローラーでの使用
namespace App\Http\Controllers;
use App\Http\Requests\StoreUserRequest;
use App\Models\User;
class UserController extends Controller
{
public function store(StoreUserRequest $request)
{
// $requestは既にバリデーション済み
$user = User::create($request->validated());
return redirect()->route('users.show', $user)
->with('success', 'ユーザーが正常に作成されました');
}
}
カスタムバリデーションルールの作成
<?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
{
// 日本の電話番号形式をチェック
$patterns = [
'/^0\d{9,10}$/', // 固定電話(ハイフンなし)
'/^0\d{1,4}-\d{1,4}-\d{4}$/', // 固定電話(ハイフンあり)
'/^0[789]0-\d{4}-\d{4}$/', // 携帯電話(ハイフンあり)
'/^0[789]0\d{8}$/' // 携帯電話(ハイフンなし)
];
$isValid = false;
foreach ($patterns as $pattern) {
if (preg_match($pattern, $value)) {
$isValid = true;
break;
}
}
if (!$isValid) {
$fail('有効な日本の電話番号を入力してください');
}
}
}
// 複雑なビジネスロジックを含むカスタムルール
namespace App\Rules;
use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\ValidationRule;
class ValidBusinessHours implements ValidationRule, DataAwareRule
{
/**
* バリデーション対象の全データ
*/
protected array $data = [];
/**
* バリデーション対象の全データを設定
*/
public function setData(array $data): static
{
$this->data = $data;
return $this;
}
/**
* 営業時間のバリデーション
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
// 開始時間と終了時間の取得
$openTime = $this->data['open_time'] ?? null;
$closeTime = $this->data['close_time'] ?? null;
if (!$openTime || !$closeTime) {
$fail('営業時間を正しく入力してください');
return;
}
// 時間の妥当性チェック
$open = strtotime($openTime);
$close = strtotime($closeTime);
if ($open >= $close) {
$fail('閉店時間は開店時間より後に設定してください');
return;
}
// 営業時間の長さチェック(最大16時間)
if (($close - $open) > (16 * 3600)) {
$fail('営業時間は16時間以内に設定してください');
return;
}
// 深夜営業のチェック
$closeHour = (int)date('H', $close);
if ($closeHour >= 2 && $closeHour <= 5) {
if (!($this->data['has_late_night_permit'] ?? false)) {
$fail('深夜営業には許可が必要です');
}
}
}
}
// カスタムルールの使用例
public function storeShop(Request $request)
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'phone' => ['required', new JapanesePhoneNumber],
'open_time' => ['required', 'date_format:H:i'],
'close_time' => ['required', 'date_format:H:i', new ValidBusinessHours],
'has_late_night_permit' => ['boolean']
]);
Shop::create($validated);
return redirect()->route('shops.index')
->with('success', '店舗が登録されました');
}
高度なバリデーション技法
<?php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class AdvancedValidationController extends Controller
{
/**
* 条件付きバリデーション
*/
public function conditionalValidation(Request $request)
{
$validator = Validator::make($request->all(), [
'account_type' => 'required|in:personal,business',
'name' => 'required|string|max:255',
'email' => 'required|email'
]);
// アカウントタイプに応じて追加ルール
$validator->sometimes('company_name', 'required|string|max:255', function ($input) {
return $input->account_type === 'business';
});
$validator->sometimes('company_number', 'required|regex:/^\d{13}$/', function ($input) {
return $input->account_type === 'business';
});
$validator->sometimes('birth_date', 'required|date|before:-18 years', function ($input) {
return $input->account_type === 'personal';
});
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
// 処理を継続...
}
/**
* 複雑な配列バリデーション
*/
public function arrayValidation(Request $request)
{
$validated = $request->validate([
'products' => 'required|array|min:1',
'products.*.name' => 'required|string|max:255',
'products.*.price' => 'required|numeric|min:0',
'products.*.quantity' => 'required|integer|min:1',
'products.*.categories' => 'required|array|min:1',
'products.*.categories.*' => 'exists:categories,id',
'products.*.specifications' => 'nullable|array',
'products.*.specifications.*.key' => 'required_with:products.*.specifications|string',
'products.*.specifications.*.value' => 'required_with:products.*.specifications|string'
]);
// 在庫チェックなどの追加バリデーション
$validator = Validator::make($validated, []);
$validator->after(function ($validator) use ($validated) {
foreach ($validated['products'] as $index => $product) {
// 在庫チェック
$stock = Stock::where('product_name', $product['name'])->first();
if ($stock && $stock->quantity < $product['quantity']) {
$validator->errors()->add(
"products.{$index}.quantity",
"{$product['name']}の在庫が不足しています(在庫: {$stock->quantity})"
);
}
}
});
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
// 注文処理...
}
/**
* ファイルアップロードバリデーション
*/
public function fileUploadValidation(Request $request)
{
$validated = $request->validate([
'document' => [
'required',
'file',
'mimes:pdf,doc,docx',
'max:10240' // 10MB
],
'images' => [
'required',
'array',
'max:5' // 最大5枚
],
'images.*' => [
'required',
'image',
'mimes:jpeg,png,jpg,webp',
'max:5120', // 5MB
'dimensions:min_width=100,min_height=100,max_width=4000,max_height=4000'
],
'video' => [
'nullable',
'file',
'mimetypes:video/mp4,video/mpeg,video/quicktime',
'max:102400' // 100MB
]
]);
// カスタムファイルバリデーション
if ($request->hasFile('images')) {
foreach ($request->file('images') as $index => $image) {
// EXIF情報チェック
if (function_exists('exif_read_data')) {
try {
$exif = @exif_read_data($image->getPathname());
if ($exif && isset($exif['Orientation'])) {
// 画像の向きを修正する処理
}
} catch (\Exception $e) {
// EXIF読み取りエラーの処理
}
}
}
}
// ファイル保存処理...
}
/**
* データベース関連バリデーション
*/
public function databaseValidation(Request $request)
{
$userId = auth()->id();
$validated = $request->validate([
// ユニーク制約(自分自身を除外)
'email' => [
'required',
'email',
Rule::unique('users')->ignore($userId)
],
// 存在チェック
'category_id' => [
'required',
Rule::exists('categories', 'id')->where(function ($query) {
$query->where('is_active', true);
})
],
// 複合ユニーク制約
'slug' => [
'required',
'string',
'max:255',
Rule::unique('posts')->where(function ($query) use ($request) {
return $query->where('user_id', auth()->id())
->where('category_id', $request->category_id);
})
],
// リレーション存在チェック
'tags' => 'array',
'tags.*' => [
Rule::exists('tags', 'id')->where(function ($query) {
$query->where('is_published', true);
})
]
]);
// 処理を継続...
}
}
// バリデーションサービスクラス
namespace App\Services;
use Illuminate\Support\Facades\Validator;
class ValidationService
{
/**
* 日本の住所バリデーション
*/
public static function validateJapaneseAddress(array $data): array
{
$rules = [
'postal_code' => [
'required',
'regex:/^\d{3}-?\d{4}$/'
],
'prefecture' => [
'required',
Rule::in(config('constants.prefectures'))
],
'city' => [
'required',
'string',
'max:50'
],
'address' => [
'required',
'string',
'max:100'
],
'building' => [
'nullable',
'string',
'max:100'
]
];
$messages = [
'postal_code.required' => '郵便番号を入力してください',
'postal_code.regex' => '郵便番号は123-4567の形式で入力してください',
'prefecture.required' => '都道府県を選択してください',
'prefecture.in' => '有効な都道府県を選択してください',
'city.required' => '市区町村を入力してください',
'address.required' => '番地を入力してください'
];
$validator = Validator::make($data, $rules, $messages);
// 郵便番号API連携(オプション)
$validator->after(function ($validator) use ($data) {
if (!$validator->errors()->has('postal_code')) {
$postalCode = str_replace('-', '', $data['postal_code']);
// 郵便番号APIを使用した住所検証
// $addressData = PostalCodeAPI::lookup($postalCode);
// if (!$addressData) {
// $validator->errors()->add('postal_code', '有効な郵便番号を入力してください');
// }
}
});
if ($validator->fails()) {
throw new \Illuminate\Validation\ValidationException($validator);
}
return $validator->validated();
}
/**
* クレジットカード情報のバリデーション
*/
public static function validateCreditCard(array $data): array
{
$currentYear = date('Y');
$currentMonth = date('n');
$rules = [
'card_number' => [
'required',
'string',
'regex:/^\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}$/',
function ($attribute, $value, $fail) {
// Luhnアルゴリズムでカード番号を検証
$number = preg_replace('/\D/', '', $value);
if (!self::luhnCheck($number)) {
$fail('有効なカード番号を入力してください');
}
}
],
'card_holder' => [
'required',
'string',
'max:100',
'regex:/^[A-Z\s]+$/i'
],
'expiry_month' => [
'required',
'integer',
'between:1,12'
],
'expiry_year' => [
'required',
'integer',
"min:{$currentYear}",
'max:' . ($currentYear + 10)
],
'cvv' => [
'required',
'digits_between:3,4'
]
];
$validator = Validator::make($data, $rules);
// 有効期限チェック
$validator->after(function ($validator) use ($data, $currentYear, $currentMonth) {
if (!$validator->errors()->has(['expiry_month', 'expiry_year'])) {
if ($data['expiry_year'] == $currentYear && $data['expiry_month'] < $currentMonth) {
$validator->errors()->add('expiry_month', 'カードの有効期限が切れています');
}
}
});
if ($validator->fails()) {
throw new \Illuminate\Validation\ValidationException($validator);
}
return $validator->validated();
}
/**
* Luhnアルゴリズムによるカード番号検証
*/
private static function luhnCheck(string $number): bool
{
$sum = 0;
$length = strlen($number);
$parity = $length % 2;
for ($i = 0; $i < $length; $i++) {
$digit = (int)$number[$i];
if ($i % 2 == $parity) {
$digit *= 2;
if ($digit > 9) {
$digit -= 9;
}
}
$sum += $digit;
}
return $sum % 10 == 0;
}
}
APIレスポンス用バリデーション
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Http\JsonResponse;
class ApiValidationController extends Controller
{
/**
* API用バリデーション(JSON応答)
*/
public function store(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed',
'device_token' => 'required|string',
'platform' => 'required|in:ios,android,web'
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'バリデーションエラー',
'errors' => $validator->errors(),
'error_count' => $validator->errors()->count()
], 422);
}
// ユーザー作成処理
$user = User::create($validator->validated());
return response()->json([
'success' => true,
'message' => 'ユーザーが正常に作成されました',
'data' => [
'user' => $user,
'token' => $user->createToken('api-token')->plainTextToken
]
], 201);
}
/**
* バッチバリデーション(複数レコード)
*/
public function batchCreate(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
'users' => 'required|array|min:1|max:100',
'users.*.name' => 'required|string|max:255',
'users.*.email' => 'required|email|distinct',
'users.*.role' => 'required|in:admin,user,guest'
]);
// カスタムバリデーション(メールの一意性チェック)
$validator->after(function ($validator) use ($request) {
if (!$validator->errors()->has('users.*.email')) {
$emails = collect($request->users)->pluck('email');
$existingEmails = User::whereIn('email', $emails)->pluck('email');
foreach ($request->users as $index => $user) {
if ($existingEmails->contains($user['email'])) {
$validator->errors()->add(
"users.{$index}.email",
"メールアドレス {$user['email']} は既に使用されています"
);
}
}
}
});
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'バリデーションエラー',
'errors' => $validator->errors(),
'failed_count' => $validator->errors()->count()
], 422);
}
// バッチ作成処理
$createdUsers = [];
foreach ($validator->validated()['users'] as $userData) {
$createdUsers[] = User::create($userData);
}
return response()->json([
'success' => true,
'message' => count($createdUsers) . '件のユーザーが作成されました',
'data' => [
'users' => $createdUsers,
'created_count' => count($createdUsers)
]
], 201);
}
}