Laravel カスタムバリデーションルールの作成手順とRuleオブジェクトの使い方完全ガイド|ValidationRuleインターフェース・クロージャ・実務サンプル徹底解説

Laravel入門バリデーションルールリファレンス基本文法・構文ガイド実装・応用テクニック

Laravelには requiredemailmaxnumeric など、開発で頻繁に必要となる強力なバリデーションルールが標準で多数用意されています。

しかし、実際のWeb開発や業務システムでは、以下のような「標準ルールだけでは検証できない独自の要件」が必ず発生します。

  • 全角カタカナ(フリガナ)のみを許可したい
  • 日本の郵便番号形式(3桁-4桁 または ハイフンなし7桁)を判定したい
  • パスワードに英大文字・小文字・数字・記号を特定数以上含める独自セキュリティ基準を課したい
  • 複数のデータベースカラムや外部APIと連携した複雑な整合性チェックを行いたい

このようなケースで役立つのが「カスタムバリデーションルール(独自バリデーション)」です。Laravelでは、再利用性の高い専用クラスを作る「Ruleオブジェクト」と、コントローラー等で手軽に書ける「クロージャ(Closure)」の2種類の手法が用意されています。

📌 本記事で学べること:

  • Laravel 10 / 11 / 12 標準の ValidationRule インターフェースを使ったRuleクラスの作成手順
  • php artisan make:rule コマンドのオプションと生成コードの解説
  • 実務で頻出するカスタムルール4選(全角カタカナ・郵便番号・パスワード強度・複合DBチェック)の完全コード
  • 1回限りの処理に便利な「クロージャバリデーション」の書き方
  • FormRequestやControllerへのスマートな適用方法
  • エラーメッセージの多言語化(lang/ja/validation.php 連携)とトラブルシューティング

  1. 1. Laravelでカスタムバリデーションを作る2大手法(Ruleオブジェクト vs クロージャ)
  2. 2. 【基本】Ruleオブジェクトの作成手順(ValidationRuleインターフェース)
    1. ステップ1:ArtisanコマンドでRuleクラスを生成する
    2. ステップ2:validate メソッドの引数を理解する
    3. ステップ3:検証ロジックとエラー通知を実装する
  3. 3. 作成したRuleオブジェクトを適用する方法
    1. 方法①:FormRequestクラス内で適用する(推奨)
    2. 方法②:Controller内で直接適用する
  4. 4. 【実務で即戦力】現場でよく使うカスタムバリデーション 4選
    1. 実例1:全角カタカナ(フリガナ)バリデーション
    2. 実例2:郵便番号形式チェック(ハイフンあり・なし両対応 / 厳格モード)
    3. 実例3:安全なパスワード強度チェック(英大文字・小文字・数字・記号)
    4. 実例4:データベースとの複合重複チェック(別テーブルや条件付きユニーク)
  5. 5. 【手軽】クロージャ(Closure)を使ったインラインバリデーション
  6. 6. 【応用テクニック】カスタムRuleをさらに便利にする活用法
    1. ① エラーメッセージの多言語化(言語ファイルとの連携)
    2. ② 暗黙的ルール(Implicit Rule)の作成
    3. ③ 他のフォーム入力値を参照する(DataAwareRule)
  7. 7. よくあるエラー・トラブルと解決策
  8. 8. まとめ&実装チェックリスト
  9. 関連記事

1. Laravelでカスタムバリデーションを作る2大手法(Ruleオブジェクト vs クロージャ)

Laravelで独自の入力検証ロジックを実装する場合、主に「Ruleオブジェクト(クラス作成)」「クロージャ(匿名関数)」の2つのアプローチがあります。

手法 実装方法 メリット 適したユースケース
Ruleオブジェクト
(おすすめ)
php artisan make:rule で専用クラスを作成 ・複数の画面やFormRequestで再利用できる
・単体テスト(Pest/PHPUnit)が書きやすい
・ロジックが独立し保守性が高い
全角カナ、電話番号、郵便番号などプロジェクト全体で共通利用するルール
クロージャ(Closure) コントローラーやFormRequestに匿名関数を直接記述 ・ファイル作成不要で手軽に書ける
・周辺のローカル変数を use で取り込みやすい
特定の1画面・1エンドポイントのみで使う特殊な入力値チェック

チーム開発や中長期的な運用を見据える場合、Ruleオブジェクトとしてクラス化しておくのがベストプラクティスです。

2. 【基本】Ruleオブジェクトの作成手順(ValidationRuleインターフェース)

Laravel 10以降(Laravel 11 / 12含む)では、Illuminate\Contracts\Validation\ValidationRule インターフェースを実装した新しい形式のRuleクラスが標準となっています。

💡 Laravel 9以前との違い:
旧バージョンでは passes($attribute, $value)message() の2メソッドを定義していましたが、Laravel 10以降は validate(string $attribute, mixed $value, Closure $fail): void の単一メソッドに統合され、より柔軟で直感的にエラーを返せるようになりました。

ステップ1:ArtisanコマンドでRuleクラスを生成する

ターミナルで以下の make:rule コマンドを実行します。

php artisan make:rule Katakana

コマンドを実行すると、app/Rules/Katakana.php に以下のような雛形ファイルが生成されます。

<?php

namespace App\Rules;

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

class Katakana implements ValidationRule
{
    /**
     * バリデーションルールの実行
     *
     * @param  \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString  $fail
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        // ここに検証ロジックを記述
    }
}

ステップ2:validate メソッドの引数を理解する

validate メソッドに渡される3つの引数の役割は以下の通りです。

  • string $attribute:検証対象となっているリクエストのフィールド名(例: 'name_kana''postal_code'
  • mixed $value:ユーザーから送信された実際の入力値(文字列、数値、配列など)
  • Closure $fail:検証に失敗したときに呼び出すコールバック関数。引数にエラーメッセージを渡すと、バリデーションエラーとして記録されます。検証をパスした場合は何もしません(return で終了)。

ステップ3:検証ロジックとエラー通知を実装する

例えば「全角カタカナのみを許可する」ルールを実装してみましょう。

<?php

namespace App\Rules;

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

class Katakana implements ValidationRule
{
    /**
     * バリデーションルールを実行
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        // 全角カタカナ・全角スペース・長音符(ー)以外が含まれている場合はエラー
        if (!is_string($value) || !preg_match('/^[ァ-ヶー\s]+$/u', $value)) {
            $fail(':attribute は全角カタカナで入力してください。');
        }
    }
}

3. 作成したRuleオブジェクトを適用する方法

作成したRuleクラスは、FormRequest または Controller のバリデーションルール配列にインスタンス化して指定します。

方法①:FormRequestクラス内で適用する(推奨)

FormRequestの rules() メソッド内で、対象フィールドのルール配列に new Katakana() を追加します。

<?php

namespace App\Http\Requests;

use App\Rules\Katakana;
use Illuminate\Foundation\Http\FormRequest;

class RegisterRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'name'      => ['required', 'string', 'max:50'],
            'name_kana' => ['required', 'string', 'max:50', new Katakana()],
            'email'     => ['required', 'email', 'unique:users,email'],
        ];
    }

    public function attributes(): array
    {
        return [
            'name_kana' => 'フリガナ',
        ];
    }
}

$fail(':attribute は全角カタカナで入力してください。'):attribute プレースホルダーは、attributes() で定義した日本語項目名(「フリガナ」)に自動で置換され、「フリガナ は全角カタカナで入力してください。」と出力されます。

方法②:Controller内で直接適用する

コントローラーの $request->validate() メソッドでも同様に指定できます。

<?php

namespace App\Http\Controllers;

use App\Rules\Katakana;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'kana' => ['required', new Katakana()],
        ]);

        // 登録処理...
    }
}

4. 【実務で即戦力】現場でよく使うカスタムバリデーション 4選

実務で頻出する4つの代表的なカスタムバリデーションルールの完全実装例を紹介します。

実例1:全角カタカナ(フリガナ)バリデーション

名前や会員登録フォームのフリガナ入力チェックに最適です。

<?php

namespace App\Rules;

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

class Katakana implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        // 文字列チェック & 全角カタカナ・長音記号・全角半角スペースの正規表現
        if (!is_string($value) || !preg_match('/^[ァ-ヶー \s]+$/u', $value)) {
            $fail(':attribute には全角カタカナのみ入力してください。');
        }
    }
}

実例2:郵便番号形式チェック(ハイフンあり・なし両対応 / 厳格モード)

コンストラクタで「ハイフン必須かどうか」を制御できる柔軟なRuleクラスの例です。

<?php

namespace App\Rules;

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

class PostalCode implements ValidationRule
{
    /**
     * @param bool $requireHyphen ハイフンを必須とするかどうか
     */
    public function __construct(
        protected bool $requireHyphen = false
    ) {}

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (!is_string($value)) {
            $fail(':attribute は正しい形式で入力してください。');
            return;
        }

        if ($this->requireHyphen) {
            // 3桁-4桁の形式(例: 100-0001)
            $pattern = '/^\d{3}-\d{4}$/';
            $message = ':attribute は「000-0000」の形式で入力してください。';
        } else {
            // ハイフンあり(3桁-4桁)または ハイフンなし(7桁数値)
            $pattern = '/^\d{3}-?\d{4}$/';
            $message = ':attribute は7桁の郵便番号(例: 100-0001 または 1000001)で入力してください。';
        }

        if (!preg_match($pattern, $value)) {
            $fail($message);
        }
    }
}

使い方:

// ハイフン任意(どちらでもOK)
'postal_code' => ['required', new PostalCode()],

// ハイフン必須にする場合
'postal_code' => ['required', new PostalCode(requireHyphen: true)],

実例3:安全なパスワード強度チェック(英大文字・小文字・数字・記号)

Laravel標準の Password::min()->letters()->mixedCase()->numbers()->symbols() も強力ですが、独自のエラーメッセージや企業固有のポリシー(連続文字禁止、特定文字列除外など)を適用したい場合のRuleクラスです。

<?php

namespace App\Rules;

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

class StrongPassword implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (!is_string($value)) {
            $fail(':attribute の形式が不正です。');
            return;
        }

        // 8文字以上、半角英大文字・小文字・数字・記号をそれぞれ1文字以上含む
        $hasUppercase = preg_match('/[A-Z]/', $value);
        $hasLowercase = preg_match('/[a-z]/', $value);
        $hasNumbers   = preg_match('/[0-9]/', $value);
        $hasSymbols   = preg_match('/[\W_]/', $value);
        $hasMinLength = mb_strlen($value) >= 8;

        if (!$hasMinLength || !$hasUppercase || !$hasLowercase || !$hasNumbers || !$hasSymbols) {
            $fail(':attribute は8文字以上で、半角英大文字・小文字・数字・記号をすべて含める必要があります。');
        }
    }
}

実例4:データベースとの複合重複チェック(別テーブルや条件付きユニーク)

データベースを参照して、「同じ会社ID(company_id)内で部署名(department_name)が重複していないか」といった複合ユニーク検証を行う例です。

<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\DB;

class UniqueDepartmentInCompany implements ValidationRule
{
    public function __construct(
        protected int $companyId,
        protected ?int $ignoreDepartmentId = null
    ) {}

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $query = DB::table('departments')
            ->where('company_id', $this->companyId)
            ->where('name', $value);

        // 更新時は自分自身のIDを除外
        if ($this->ignoreDepartmentId) {
            $query->where('id', '!=', $this->ignoreDepartmentId);
        }

        if ($query->exists()) {
            $fail('指定された :attribute はこの企業内に既に登録されています。');
        }
    }
}

5. 【手軽】クロージャ(Closure)を使ったインラインバリデーション

「このコントローラーのこのメソッドだけでしか使わない」「専用クラスを作るほどではない」という場合は、配列内に無名関数を直接記述できます。

use Closure;
use Illuminate\Http\Request;

public function updateStatus(Request $request)
{
    $request->validate([
        'reason' => [
            'required',
            'string',
            function (string $attribute, mixed $value, Closure $fail) use ($request) {
                // 特定のステータス変更時のみ、理由に10文字以上を要求
                if ($request->input('status') === 'rejected' && mb_strlen($value) < 10) {
                    $fail(':attribute は却下時には10文字以上で詳細な理由を入力してください。');
                }
            },
        ],
    ]);

    // 処理...
}

クロージャでも引数はRuleオブジェクトの validate メソッドと同じ ($attribute, $value, $fail) となっており、非常にシンプルに扱えます。

6. 【応用テクニック】カスタムRuleをさらに便利にする活用法

① エラーメッセージの多言語化(言語ファイルとの連携)

エラーメッセージをRuleクラス内に直書きせず、lang/ja/validation.php から取得するように設定できます。

$fail コールバックは、返り値のオブジェクトで translate() メソッドをチェーンできます。

public function validate(string $attribute, mixed $value, Closure $fail): void
{
    if (!preg_match('/^[ァ-ヶー\s]+$/u', $value)) {
        // lang/ja/validation.php の 'katakana' キーを参照し、項目名を自動バインド
        $fail('validation.katakana')->translate([
            'attribute' => $attribute,
        ]);
    }
}

lang/ja/validation.php に以下を追記します:

return [
    // ...
    'katakana' => ':attribute には全角カタカナを入力してください。',
];

② 暗黙的ルール(Implicit Rule)の作成

Laravelの標準仕様では、入力値が空(null や空文字 "")の場合、required などの明示的な空チェックルールがない限りカスタムバリデーションはスキップされます。

「値が空であっても必ずRuleクラスの検証を実行させたい」場合は、Illuminate\Contracts\Validation\ImplicitRule インターフェースを実装するか、コマンド生成時に --implicit オプションを付与します。

php artisan make:rule RequiredIfCompany --implicit

③ 他のフォーム入力値を参照する(DataAwareRule)

「パスワード変更時、新しいパスワードが現在のパスワードと一致していないか」など、フォーム全体の入力データ(全フィールド)をRuleクラス内で参照したい場合は、Illuminate\Contracts\Validation\DataAwareRule インターフェースを実装します。

<?php

namespace App\Rules;

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

class DifferentFromCurrentPassword implements ValidationRule, DataAwareRule
{
    /**
     * 入力された全リクエストデータ
     * @var array
     */
    protected array $data = [];

    /**
     * バリデーション実行前に入力データをセット
     */
    public function setData(array $data): static
    {
        $this->data = $data;
        return $this;
    }

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $currentPassword = $this->data['current_password'] ?? null;

        if ($currentPassword && $value === $currentPassword) {
            $fail(':attribute に現在のパスワードと同じものは設定できません。');
        }
    }
}

7. よくあるエラー・トラブルと解決策

⚠️ トラブル1:入力が空のときにカスタムルールが実行されない

原因: Laravelは初期設定で空のフィールドに対するバリデーションをパスさせます。
解決策: 必須チェックを行いたい場合は 'required' ルールと併用するか、上記で紹介した --implicit(暗黙的ルール)を使用してください。

⚠️ トラブル2:パイプ記法('required|katakana')でRuleクラスを渡してしまう

原因: Ruleオブジェクトはインスタンスのため、文字列をパイプ(|)で連結する記法では渡せません。
解決策: 必ずルール定義を配列形式 ['required', new Katakana()] に変更してください。

⚠️ トラブル3::attribute が英語のまま表示されてしまう

原因: FormRequestの attributes()lang/ja/validation.phpattributes 配列に日本語名が設定されていません。
解決策: 項目名の日本語マッピングを追加し、php artisan config:clear を実行してください。

8. まとめ&実装チェックリスト

Laravelのカスタムバリデーションルールは、ValidationRule インターフェースを使うことで、保守性が高く美しいコードで自由自在に入力検証ロジックを組み立てることができます。

✅ カスタムバリデーション 実装チェックリスト:

  • [ ] 再利用する共通ルールは php artisan make:rule でクラス化しているか?
  • [ ] 1回限りの特殊なチェックはクロージャでスマートに記述しているか?
  • [ ] 失敗時のエラーメッセージで :attribute プレースホルダーを活用しているか?
  • [ ] ルール配列に [new CustomRule()] のように配列形式で指定しているか?
  • [ ] 空入力をチェックする必要がある場合は --implicit または required と併用しているか?
  • [ ] 他のフィールドを参照する場合は DataAwareRule を活用しているか?

実務要件に応じた適切なバリデーションを実装し、ユーザーにとっても開発チームにとっても安心・安全な堅牢なシステムを構築しましょう!

レン (Wren)

こんにちは。レンです。

Laravelのコードの森に住んでいる、小さな案内役です。
ルーティングの枝やクラスの影を歩きながら、コードの流れや仕組みを眺めています。

このサイトでは、Laravelの基本から実装のコツまで、開発で役立つポイントを静かに整理しています。
難しいことを増やすのではなく、コードの見通しが少し良くなるヒントを届けるのが役目です。

「この処理はどこに書くのがいいのか」
「Laravelではどう考えると整理できるのか」

そんな疑問に、小さなメモを残すような気持ちで記事を書いています。

コードを書いている途中で迷ったとき、
このサイトが少し立ち止まって整理できる場所になればうれしいです。

レン (Wren)をフォローする

コメント