株式会社WR

株式会社WR

WEB TOTAL CONSULTING

Laravelのフォームバリデーション完全ガイド——FormRequestとカスタムルール
ブログ一覧へ
技術ブログ

Laravelのフォームバリデーション完全ガイド——FormRequestとカスタムルール

LaravelのFormRequestクラスを使うと、コントローラーを汚さずにバリデーションロジックを分離できます。カスタムバリデーションルールの作成方法も解説します。

バリデーションをコントローラーに書くと何が問題か

Laravelではコントローラー内で $request->validate([...]) を呼ぶことも可能ですが、フォームが複雑になると以下の問題が生じます。

  • コントローラーのメソッドが長くなり、単一責任の原則に違反する
  • 同じバリデーションロジックを複数のコントローラーで書き直すことになる
  • テストが書きにくくなる

FormRequestを使うことで、バリデーションロジックを独立したクラスに分離し、再利用性・テスト性・可読性を高めることができます。


FormRequestの作成

php artisan make:request ContactRequest

生成されたクラス(app/Http/Requests/ContactRequest.php)を編集します。

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ContactRequest extends FormRequest
{
    /**
     * このリクエストを実行できるか(認証チェック)
     */
    public function authorize(): bool
    {
        return true; // 全ユーザーに許可
    }

    /**
     * バリデーションルール
     */
    public function rules(): array
    {
        return [
            'name'    => ['required', 'string', 'max:100'],
            'email'   => ['required', 'email:rfc,dns', 'max:255'],
            'phone'   => ['nullable', 'regex:/^[0-9\-\+]{10,15}$/'],
            'subject' => ['required', 'string', 'max:200'],
            'body'    => ['required', 'string', 'min:10', 'max:5000'],
            'agreed'  => ['required', 'accepted'],
        ];
    }

    /**
     * 日本語エラーメッセージ
     */
    public function messages(): array
    {
        return [
            'name.required'    => 'お名前は必須です',
            'name.max'         => 'お名前は100文字以内で入力してください',
            'email.required'   => 'メールアドレスは必須です',
            'email.email'      => '有効なメールアドレスを入力してください',
            'phone.regex'      => '電話番号は半角数字・ハイフンで入力してください',
            'subject.required' => '件名は必須です',
            'body.required'    => 'お問い合わせ内容は必須です',
            'body.min'         => 'お問い合わせ内容は10文字以上入力してください',
            'agreed.accepted'  => 'プライバシーポリシーへの同意が必要です',
        ];
    }

    /**
     * 属性名の日本語化
     */
    public function attributes(): array
    {
        return [
            'name'    => 'お名前',
            'email'   => 'メールアドレス',
            'phone'   => '電話番号',
            'subject' => '件名',
            'body'    => 'お問い合わせ内容',
        ];
    }
}

コントローラーはシンプルになる

FormRequestを使うと、コントローラーは型ヒントを指定するだけで自動的にバリデーションが実行されます。

class ContactController extends Controller
{
    public function store(ContactRequest $request): RedirectResponse
    {
        // ここに来た時点でバリデーション済み
        $validated = $request->validated();

        Contact::create($validated);

        // メール送信
        Mail::to(config('mail.admin_address'))
            ->send(new ContactReceived($validated));

        return redirect()->route('contact.complete')
                         ->with('success', 'お問い合わせを受け付けました');
    }
}

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

独自のビジネスロジックに基づくバリデーションはカスタムルールとして定義します。

php artisan make:rule JapanesePhone
<?php

namespace App\Rules;

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

class JapanesePhone implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        // 日本の固定電話・携帯・フリーダイヤルに対応
        if (!preg_match('/^(0[0-9]{1,4}-[0-9]{1,4}-[0-9]{4}|0[789]0-[0-9]{4}-[0-9]{4})$/', $value)) {
            $fail(':attributeの形式が正しくありません(例:03-1234-5678)');
        }
    }
}
// FormRequestで使用
'phone' => ['nullable', new JapanesePhone],

条件付きバリデーション

状況に応じてルールを変えたい場合は sometimeswhen を使います。

public function rules(): array
{
    return [
        'type'    => ['required', Rule::in(['personal', 'corporate'])],
        'company' => [
            // type が corporate のときのみ必須
            Rule::when($this->input('type') === 'corporate', ['required', 'string', 'max:100']),
        ],
        'tax_id'  => [
            Rule::when($this->input('type') === 'corporate', ['required', 'regex:/^[0-9]{13}$/']),
        ],
    ];
}

バリデーション後の処理フック

prepareForValidation でバリデーション前にデータを整形できます。

protected function prepareForValidation(): void
{
    // 全角スペースを半角に変換、前後の空白を除去
    $this->merge([
        'name'  => trim(mb_convert_kana($this->name ?? '', 's')),
        'email' => strtolower(trim($this->email ?? '')),
        'phone' => preg_replace('/[^\d\-]/', '', $this->phone ?? ''),
    ]);
}

テスト

FormRequestはユニットテストが書きやすいのも利点です。

class ContactRequestTest extends TestCase
{
    use RefreshDatabase;

    public function test_有効なリクエストはバリデーションを通過する(): void
    {
        $request = ContactRequest::create('/contact', 'POST', [
            'name'    => '山田太郎',
            'email'   => 'yamada@example.com',
            'subject' => 'お見積り相談',
            'body'    => 'Webシステムの開発についてご相談したいです。',
            'agreed'  => '1',
        ]);

        $validator = Validator::make($request->all(), (new ContactRequest)->rules());
        $this->assertFalse($validator->fails());
    }

    public function test_メールアドレスが不正な場合はバリデーションエラー(): void
    {
        $validator = Validator::make(
            ['email' => 'invalid-email'],
            ['email' => ['required', 'email:rfc,dns']]
        );
        $this->assertTrue($validator->fails());
    }
}

まとめ

FormRequestを使うことで、バリデーションロジックの分離・再利用・テストが容易になります。カスタムルール・条件付きバリデーション・前処理フックを組み合わせることで、複雑なビジネスロジックにも柔軟に対応できます。

LaravelによるWebシステム開発のご相談はお気軽にどうぞ。

Category 技術ブログ

Related Posts

関連記事

開発・技術のご相談はお気軽に

お見積りは無料です。まずはお気軽にご相談ください。

お問い合わせ →