メインコンテンツへスキップ

Documentation Index

Fetch the complete documentation index at: https://kawax.biz/llms.txt

Use this file to discover all available pages before exploring further.

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

Laravelには豊富な組み込みバリデーションルールが用意されていますが、アプリケーション固有の検証ロジックが必要な場面もあります。カスタムバリデーションルールを使うと、再利用可能な検証ロジックをクラスやクロージャとして定義し、標準のルールと同じように使えます。 カスタムルールを定義する方法は主に2つあります。
  • ルールオブジェクト — 再利用性が高く、テストしやすい
  • クロージャ — 一度だけ使うシンプルなルールに向いている

ルールオブジェクト

ルールクラスを生成する

make:rule Artisanコマンドで新しいルールクラスを生成します。生成されたクラスは app/Rules ディレクトリに配置されます。
php artisan make:rule Uppercase

ValidationRuleインターフェースを実装する

生成されたクラスに validate メソッドを実装します。このメソッドはバリデーション失敗時に $fail クロージャを呼び出します。
<?php

namespace App\Rules;

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

class Uppercase implements ValidationRule
{
    /**
     * バリデーションルールを実行する
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (strtoupper($value) !== $value) {
            $fail('The :attribute must be uppercase.');
        }
    }
}
$fail クロージャに渡す文字列には :attribute プレースホルダーを使えます。Laravelがフィールド名に置き換えます。

ルールオブジェクトを適用する

ルールオブジェクトのインスタンスをバリデーション配列に渡します。
use App\Rules\Uppercase;

$request->validate([
    'name' => ['required', 'string', new Uppercase],
]);
フォームリクエストの rules() メソッドでも同様に使えます。
public function rules(): array
{
    return [
        'name' => ['required', 'string', new Uppercase],
    ];
}

翻訳キーを使ったエラーメッセージ

エラーメッセージをハードコードする代わりに、翻訳キーを使うこともできます。
public function validate(string $attribute, mixed $value, Closure $fail): void
{
    if (strtoupper($value) !== $value) {
        $fail('validation.uppercase')->translate();
    }
}
翻訳ファイル lang/ja/validation.php にメッセージを追加します。
return [
    'uppercase' => ':attributeは大文字で入力してください。',
    // ...
];

複数のエラーメッセージを追加する

一つのフィールドに複数のエラーを報告するには、$fail を複数回呼び出します。
public function validate(string $attribute, mixed $value, Closure $fail): void
{
    if (! is_string($value)) {
        $fail('The :attribute must be a string.');
        return;
    }

    if (strlen($value) < 8) {
        $fail('The :attribute must be at least 8 characters.');
    }

    if (! preg_match('/[A-Z]/', $value)) {
        $fail('The :attribute must contain at least one uppercase letter.');
    }
}

クロージャベースのルール

アプリケーション内で一度だけ使うシンプルなルールは、クラスを作らずクロージャで定義できます。
use Illuminate\Support\Facades\Validator;
use Closure;

$validator = Validator::make($request->all(), [
    'title' => [
        'required',
        'max:255',
        function (string $attribute, mixed $value, Closure $fail) {
            if ($value === 'foo') {
                $fail("The {$attribute} is invalid.");
            }
        },
    ],
]);
クロージャルールはインラインで定義するため手軽ですが、再利用や独立したテストが難しいため、複数箇所で使うロジックはルールオブジェクトに切り出すことをおすすめします。

Implicitルール(値が空でも実行する)

デフォルトでは、フィールドが空または存在しない場合、カスタムルールは実行されません。空の値に対してもルールを実行したい場合は、--implicit オプションを付けてクラスを生成します。
php artisan make:rule Uppercase --implicit
生成されたクラスは ImplicitRule インターフェースを実装しています。このインターフェース自体は追加のメソッドを持たず、Laravelへのシグナルとして機能します。
<?php

namespace App\Rules;

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

class RequiredIfJapanese implements ValidationRule, ImplicitRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (empty($value)) {
            $fail('The :attribute is required.');
            return;
        }

        if (! preg_match('/^[\p{Hiragana}\p{Katakana}\p{Han}ー]+$/u', $value)) {
            $fail('The :attribute must contain only Japanese characters.');
        }
    }
}
ImplicitRule は「属性が必須である」ことをLaravelに示すだけです。値が空のときに実際にバリデーションを失敗させるかどうかは、validate メソッドの実装次第です。

データアクセス

DataAwareRule — フォーム全体のデータにアクセスする

他のフィールドの値に基づいてバリデーションしたい場合は、DataAwareRule インターフェースを実装します。setData メソッドがバリデーション開始前に自動で呼び出されます。
<?php

namespace App\Rules;

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

class UniqueForTenant implements DataAwareRule, ValidationRule
{
    /**
     * バリデーション対象の全データ
     *
     * @var array<string, mixed>
     */
    protected array $data = [];

    public function __construct(
        protected string $table,
        protected string $column = 'value',
    ) {}

    /**
     * バリデーションデータをセットする
     *
     * @param  array<string, mixed>  $data
     */
    public function setData(array $data): static
    {
        $this->data = $data;

        return $this;
    }

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

        $exists = DB::table($this->table)
            ->where('tenant_id', $tenantId)
            ->where($this->column, $value)
            ->exists();

        if ($exists) {
            $fail('The :attribute has already been taken for this tenant.');
        }
    }
}
使用例:
$request->validate([
    'tenant_id' => 'required|integer',
    'email' => ['required', 'email', new UniqueForTenant('users', 'email')],
]);

ValidatorAwareRule — バリデーターインスタンスにアクセスする

バリデーターが持つすべての情報(失敗したルール、カスタムメッセージなど)にアクセスするには、ValidatorAwareRule インターフェースを実装します。
<?php

namespace App\Rules;

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

class ConditionalFormat implements ValidationRule, ValidatorAwareRule
{
    protected Validator $validator;

    public function setValidator(Validator $validator): static
    {
        $this->validator = $validator;

        return $this;
    }

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        // 他のフィールドがすでにバリデーション失敗している場合はスキップ
        if ($this->validator->errors()->has('type')) {
            return;
        }

        $type = $this->validator->getData()['type'] ?? null;

        if ($type === 'phone' && ! preg_match('/^\+?[0-9\-\s]+$/', $value)) {
            $fail('The :attribute must be a valid phone number.');
        }

        if ($type === 'email' && ! filter_var($value, FILTER_VALIDATE_EMAIL)) {
            $fail('The :attribute must be a valid email address.');
        }
    }
}

実践的なユースケース

日本語文字チェック

全角・半角の文字種を検証するルールです。
php artisan make:rule JapaneseOnly
<?php

namespace App\Rules;

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

class JapaneseOnly implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        // ひらがな・カタカナ・漢字・長音符のみ許可
        if (! preg_match('/^[\p{Hiragana}\p{Katakana}\p{Han}ー\s]+$/u', $value)) {
            $fail(':attributeは日本語(ひらがな・カタカナ・漢字)で入力してください。');
        }
    }
}
$request->validate([
    'name_kana' => ['required', 'string', new JapaneseOnly],
]);

電話番号フォーマット検証

日本の電話番号形式を検証するルールです。
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
    {
        // ハイフンあり・なし両対応、国際形式も許可
        $normalized = preg_replace('/[\s\-\(\)]/', '', $value);

        $patterns = [
            '/^0\d{9,10}$/',        // 一般的な固定・携帯電話番号
            '/^\+81\d{9,10}$/',     // 国際形式
        ];

        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $normalized)) {
                return;
            }
        }

        $fail(':attributeは有効な電話番号の形式で入力してください。');
    }
}

テナントIDつきのユニーク制約

マルチテナントアプリケーションでよくある、テナントスコープ内でのユニーク制約です。
1

ルールクラスを作成する

php artisan make:rule TenantUnique
<?php

namespace App\Rules;

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

class TenantUnique implements DataAwareRule, ValidationRule
{
    protected array $data = [];

    public function __construct(
        protected string $table,
        protected string $column,
        protected ?int $ignoreId = null,
    ) {}

    public function setData(array $data): static
    {
        $this->data = $data;

        return $this;
    }

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

        $query = DB::table($this->table)
            ->where('tenant_id', $tenantId)
            ->where($this->column, $value);

        if ($this->ignoreId !== null) {
            $query->where('id', '!=', $this->ignoreId);
        }

        if ($query->exists()) {
            $fail(':attributeはすでに使用されています。');
        }
    }
}
2

フォームリクエストで使用する

<?php

namespace App\Http\Requests;

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

class CreateProjectRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'tenant_id' => 'required|integer|exists:tenants,id',
            'name' => [
                'required',
                'string',
                'max:100',
                new TenantUnique('projects', 'name'),
            ],
        ];
    }

    public function authorize(): bool
    {
        return true;
    }
}
3

更新時はIDを除外する

既存レコードを更新する場合は、自分自身のIDを除外して重複チェックします。
class UpdateProjectRequest extends FormRequest
{
    public function rules(): array
    {
        $projectId = $this->route('project');

        return [
            'tenant_id' => 'required|integer|exists:tenants,id',
            'name' => [
                'required',
                'string',
                'max:100',
                new TenantUnique('projects', 'name', ignoreId: $projectId),
            ],
        ];
    }
}

サービスプロバイダーでのルール登録

Validator::extend() でルールを追加する

Validator::extend() を使うと、文字列形式('rule_name')でカスタムルールを使えるようになります。AppServiceProviderboot() メソッドで登録します。
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Validator as ValidatorInstance;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Validator::extend('japanese_only', function (string $attribute, mixed $value, array $parameters, ValidatorInstance $validator): bool {
            return preg_match('/^[\p{Hiragana}\p{Katakana}\p{Han}ー\s]+$/u', $value) === 1;
        });

        Validator::replacer('japanese_only', function (string $message, string $attribute): string {
            return str_replace(':attribute', $attribute, ':attributeは日本語で入力してください。');
        });
    }
}
Validator::extend() で登録したルールは文字列として指定できます。
$request->validate([
    'name_kana' => 'required|japanese_only',
]);
Validator::extend() はルールオブジェクトより古い登録方式です。新規開発では ValidationRule インターフェースを実装したルールオブジェクトを使うことを推奨します。

Rule クラスにスタティックメソッドとして追加する

Rule ファサードにマクロを追加することで、Rule::myRule() のような流暢なAPI(Fluent API)を提供できます。
use Illuminate\Validation\Rule;

Rule::macro('tenantUnique', function (string $table, string $column, ?int $ignoreId = null) {
    return new \App\Rules\TenantUnique($table, $column, $ignoreId);
});
// 使用例
$request->validate([
    'name' => ['required', Rule::tenantUnique('projects', 'name')],
]);

内部実装の詳細

Illuminate\Validation\Validator がカスタムルールを呼び出す仕組みを見てみましょう。 バリデーター内では validateAttribute() が各フィールドを処理します。ルールが ValidationRule インターフェースを実装している場合、validateUsingCustomRule() メソッドが呼び出されます。
// Illuminate\Validation\Validator::validateUsingCustomRule() の簡略版
protected function validateUsingCustomRule($attribute, $value, $rule)
{
    // DataAwareRule の場合、全データを注入する
    if ($rule instanceof DataAwareRule) {
        $rule->setData($this->getData());
    }

    // ValidatorAwareRule の場合、バリデーター自身を注入する
    if ($rule instanceof ValidatorAwareRule) {
        $rule->setValidator($this);
    }

    // validate() を呼び出す
    $rule->validate($attribute, $value, function ($message, $translate = false) use ($attribute, $rule) {
        // $fail クロージャ — エラーメッセージを追加する
        $this->errors()->add($attribute, $this->makeReplacements(
            $message,
            $attribute,
            get_class($rule),
            [], // 追加のプレースホルダー置換(例: ['min' => '8'] など)
        ));
    });
}
ImplicitRule の処理は isImplicit() メソッドで判断され、値が空の場合でもルールのチェックを実行します。
// 空の値に対してルールを実行するかどうかを判断する
protected function isImplicit($rule): bool
{
    return $rule instanceof ImplicitRule
        || in_array($rule, $this->implicitRules);
}
DataAwareRuleValidatorAwareRule を同時に実装することもできます。両インターフェースを持つクラスでは、どちらの注入も行われます。

関連ページ

バリデーション(入門)

コントローラーやフォームリクエストでの標準的なバリデーション方法を確認します。
Last modified on March 29, 2026