Laravelのバリデーション【フォーム入力を安全に】

はじめに

ウェブアプリケーション開発において、ユーザーからの入力データを適切に検証することは非常に重要です。不正な入力はセキュリティリスク、データの不整合、さらにはアプリケーションのクラッシュを引き起こす可能性があります。LaravelはPHPフレームワークとして、強力かつ柔軟なバリデーション機能を提供しており、開発者が簡単に堅牢なフォーム検証を実装できるようになっています。

この記事では、Laravelにおけるバリデーションの基本から応用まで、実践的なコード例とともに解説します。

Laravelバリデーションの基本

Laravelのバリデーションは、主に以下の方法で実装できます:

  1. コントローラーメソッド内でのバリデーション
  2. フォームリクエストクラスを使用したバリデーション
  3. バリデータファサードを直接使用するバリデーション

まずは、最も基本的なコントローラーでのバリデーション方法から見ていきましょう。

コントローラーでのバリデーション

public function store(Request $request)
{
    $validated = $request->validate([
        'title' => 'required|max:255',
        'body' => 'required',
        'publish_at' => 'nullable|date',
    ]);
    
    // バリデーションが成功した場合のみ、ここが実行される
    Article::create($validated);
    
    return redirect()->route('articles.index')
        ->with('success', '記事が作成されました');
}

validateメソッドは入力値が条件を満たさない場合、自動的に元のページにリダイレクトし、エラーメッセージをフラッシュデータとして保存します。これにより、ビュー側では $errors 変数を使用してエラーメッセージを表示できます。

ビューでのエラー表示

<form method="POST" action="{{ route('articles.store') }}">
    @csrf
    
    <div class="form-group">
        <label for="title">タイトル</label>
        <input type="text" class="form-control @error('title') is-invalid @enderror" 
               id="title" name="title" value="{{ old('title') }}">
        
        @error('title')
            <div class="invalid-feedback">{{ $message }}</div>
        @enderror
    </div>
    
    <!-- 他のフォーム要素 -->
    
    <button type="submit" class="btn btn-primary">保存</button>
</form>

フォームリクエストを使ったバリデーション

複雑なバリデーションルールや、複数のコントローラーで再利用するバリデーションロジックは、フォームリクエストクラスに分離するのが効果的です。

フォームリクエストの作成

php artisan make:request StoreArticleRequest

フォームリクエストの実装

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreArticleRequest extends FormRequest
{
    /**
     * リクエストの認可判定
     */
    public function authorize()
    {
        return true; // 必要に応じて認可ロジックを追加
    }

    /**
     * バリデーションルール
     */
    public function rules()
    {
        return [
            'title' => 'required|max:255',
            'body' => 'required',
            'category_id' => 'required|exists:categories,id',
            'tags' => 'nullable|array',
            'tags.*' => 'exists:tags,id',
            'publish_at' => 'nullable|date|after:today',
        ];
    }
    
    /**
     * バリデーションメッセージのカスタマイズ
     */
    public function messages()
    {
        return [
            'title.required' => 'タイトルは必須です',
            'title.max' => 'タイトルは255文字以内で入力してください',
            'category_id.exists' => '選択されたカテゴリーは存在しません',
            'publish_at.after' => '公開日は明日以降の日付を選択してください',
        ];
    }
    
    /**
     * バリデーション属性名のカスタマイズ
     */
    public function attributes()
    {
        return [
            'title' => '記事タイトル',
            'body' => '本文',
            'category_id' => 'カテゴリー',
            'publish_at' => '公開日',
        ];
    }
}

コントローラーでのフォームリクエストの使用

use App\Http\Requests\StoreArticleRequest;

public function store(StoreArticleRequest $request)
{
    // バリデーション済みのデータを取得
    $validated = $request->validated();
    
    // データ保存など
    Article::create($validated);
    
    return redirect()->route('articles.index')
        ->with('success', '記事が作成されました');
}

バリデーションルールの種類

Laravelには様々なバリデーションルールが用意されています。代表的なものをいくつか紹介します。

基本的なルール

  • required: 必須項目
  • nullable: NULL値を許可
  • sometimes: 条件付き必須
  • required_if:field,value: 特定の条件下で必須

文字列のルール

  • string: 文字列であること
  • min:value: 最小文字数
  • max:value: 最大文字数
  • size:value: 正確な文字数
  • email: 有効なメールアドレス形式
  • url: 有効なURL形式
  • alpha: アルファベットのみ
  • alpha_num: アルファベットと数字のみ
  • alpha_dash: アルファベット、数字、ダッシュ、アンダースコアのみ

数値のルール

  • numeric: 数値であること
  • integer: 整数であること
  • decimal:min,max: 小数点以下の桁数制限
  • between:min,max: 指定範囲内の値
  • min:value: 最小値
  • max:value: 最大値

日付のルール

  • date: 有効な日付形式
  • date_format:format: 指定形式の日付
  • after:date: 指定日付より後
  • before:date: 指定日付より前
  • after_or_equal:date: 指定日付以降
  • before_or_equal:date: 指定日付以前

ファイルのルール

  • file: ファイルであること
  • image: 画像ファイルであること
  • mimes:jpeg,png,...: 特定のMIMEタイプのファイル
  • mimetypes:video/avi,...: MIMEタイプによる制限
  • max:value: 最大ファイルサイズ(KB)

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

独自のバリデーションロジックが必要な場合、カスタムバリデーションルールを作成できます。

クロージャを使用したカスタムルール

$validator = Validator::make($request->all(), [
    'username' => [
        'required',
        function ($attribute, $value, $fail) {
            if (strtolower($value) === 'admin') {
                $fail('ユーザー名に "admin" は使用できません。');
            }
        },
    ],
]);

カスタムルールオブジェクトの作成

php artisan make:rule Uppercase
<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class Uppercase implements Rule
{
    public function passes($attribute, $value)
    {
        return strtoupper($value) === $value;
    }
    
    public function message()
    {
        return ':attributeは大文字で入力してください。';
    }
}

カスタムルールの使用

use App\Rules\Uppercase;

$request->validate([
    'product_code' => ['required', new Uppercase],
]);

条件付きバリデーション

特定の条件に基づいてバリデーションルールを適用したい場合は、条件付きバリデーションを使用できます。

基本的な条件付きバリデーション

$request->validate([
    'email' => 'required|email',
    'name' => 'required_with:email',
    'phone' => 'required_without:email',
]);

複雑な条件付きバリデーション

$validator = Validator::make($request->all(), [
    'subscription_type' => 'required|in:basic,premium,enterprise',
    'payment_method' => 'required',
]);

if ($request->subscription_type === 'premium') {
    $validator->sometimes('payment_method', 'in:credit_card,bank_transfer', function ($input) {
        return $input->subscription_type === 'premium';
    });
}

if ($request->subscription_type === 'enterprise') {
    $validator->sometimes('company_name', 'required|string|max:100', function ($input) {
        return $input->subscription_type === 'enterprise';
    });
}

APIバリデーション

APIリクエストの場合、リダイレクトではなくJSON形式でのエラーレスポンスが必要です。

API用バリデーションの実装

public function store(Request $request)
{
    try {
        $validated = $request->validate([
            'title' => 'required|max:255',
            'body' => 'required',
        ]);
    } catch (ValidationException $e) {
        return response()->json([
            'message' => 'バリデーションエラー',
            'errors' => $e->errors(),
        ], 422);
    }
    
    $article = Article::create($validated);
    
    return response()->json([
        'message' => '記事が作成されました',
        'data' => $article,
    ], 201);
}

より簡潔に書くなら、以下のようにもできます:

public function store(Request $request)
{
    $validated = $request->validate([
        'title' => 'required|max:255',
        'body' => 'required',
    ]);
    
    $article = Article::create($validated);
    
    return response()->json([
        'message' => '記事が作成されました',
        'data' => $article,
    ], 201);
}

LaravelはAccept: application/jsonヘッダーが付いているリクエストの場合、バリデーションエラー時に自動的にJSON形式でレスポンスを返します。

バリデーション後のデータの加工

バリデーション後、保存前にデータを加工したい場合があります。フォームリクエストのprepareForValidationpassedValidationメソッドを使用すると便利です。

class StoreArticleRequest extends FormRequest
{
    /**
     * バリデーション前のデータ加工
     */
    protected function prepareForValidation()
    {
        $this->merge([
            'slug' => Str::slug($this->title),
        ]);
    }
    
    /**
     * バリデーション後のデータ加工
     */
    protected function passedValidation()
    {
        $this->replace(['content' => clean($this->content)]);
    }
    
    // rules(), authorize() などの他のメソッド
}

セキュリティ対策

バリデーションは機能性だけでなく、セキュリティ対策としても重要です。

入力サニタイズ

// HTMLパージライブラリを使用した例(事前にインストールが必要)
use Stevebauman\Purify\Facades\Purify;

$cleanData = Purify::clean($request->input('content'));

CSRFトークン

Laravelではフォームに自動的にCSRFトークンが含まれるため、クロスサイトリクエストフォージェリ攻撃から保護されます。

<form method="POST" action="/profile">
    @csrf
    <!-- フォームフィールド -->
</form>

一括代入保護

Eloquentモデルでは$fillableまたは$guarded属性を使用して、一括代入から保護することが重要です。

class Article extends Model
{
    /**
     * 一括代入可能な属性
     */
    protected $fillable = [
        'title', 'body', 'category_id', 'user_id', 'published_at'
    ];
    
    // または
    
    /**
     * 一括代入から保護する属性
     */
    protected $guarded = ['id', 'created_at', 'updated_at'];
}

まとめ

Laravelのバリデーション機能は、使いやすさと柔軟性を兼ね備えており、あらゆるシナリオでのデータ検証に対応できます。基本的なルールからカスタムルールまで、豊富なオプションを活用することで、堅牢なアプリケーション開発が可能になります。

セキュリティ対策としても、入力データの検証は不可欠です。フォームリクエストを活用したり、適切なサニタイズ処理を行うことで、より安全なアプリケーションを構築しましょう。

バリデーションはユーザーエクスペリエンスにも大きく影響します。わかりやすいエラーメッセージの表示や、使いやすいフォームデザインと組み合わせることで、ユーザーフレンドリーなアプリケーション開発を目指しましょう。

コメント

タイトルとURLをコピーしました