この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:5
ページ更新者:atom
更新日時:2026-06-11 07:07:02

タイトル: 存在チェック
SEOタイトル: Laravel バリデーション exists 完全ガイド

この記事の要点
  • exists ルール: exists:users,email で DB に存在する値か検証
  • unique ルール: unique:users,email で重複禁止、編集時は ->ignore($id)
  • 高度な条件は Rule::exists / Rule::unique でクロージャ追加可能
  • FormRequest にまとめると Controller がスッキリ、メッセージカスタマイズも一元化
  • 大量データでは exists がインデックスを使えるか EXPLAIN で確認、N+1 回避にバッチ検証も検討

基本: exists ルール

「指定したテーブル・カラムに値が存在すること」を検証します。

$request->validate([
    // users テーブルの id カラムに存在
    'user_id' => 'required|exists:users,id',

    // email カラムに存在(ログイン時のメール存在チェック)
    'email' => 'required|email|exists:users,email',

    // カラム名を省略するとフィールド名と同じになる
    // 下記は 'category_id' = 'exists:categories,category_id' になるので注意
    'category_id' => 'required|exists:categories',  // 多くは 'exists:categories,id' が意図

    // 配列の全要素をチェック
    'role_ids' => 'required|array',
    'role_ids.*' => 'exists:roles,id',
]);

Rule::exists で条件付き検証

「アクティブなユーザーのみ」「特定の組織に属するレコードのみ」など、追加条件を付けるには Rule::exists() のクロージャを使います:

use Illuminate\Validation\Rule;

$request->validate([
    'user_id' => [
        'required',
        Rule::exists('users', 'id')->where(function ($query) {
            $query->where('status', 'active')
                  ->whereNull('deleted_at');
        }),
    ],

    // ログインユーザーの会社のメンバーであること
    'assignee_id' => [
        'required',
        Rule::exists('users', 'id')->where(
            fn ($q) => $q->where('company_id', auth()->user()->company_id)
        ),
    ],

    // 別 DB 接続
    'external_id' => [
        Rule::exists('mysql_legacy.customers', 'cust_id'),
    ],
]);

unique ルール (重複禁止)

新規登録時の「同じメールアドレスは登録不可」など:

$request->validate([
    'email' => 'required|email|unique:users,email',
    'username' => 'required|unique:users,username',
]);

編集時の unique: 自分自身を除外

編集画面で「メールアドレスは変更不可だが unique チェックが走ると自分自身に引っかかる」問題。ignore() で除外:

use Illuminate\Validation\Rule;

public function update(Request $request, User $user)
{
    $request->validate([
        'email' => [
            'required', 'email',
            // ★ 編集中のユーザー自身は除外
            Rule::unique('users')->ignore($user->id),
        ],
    ]);
}

// カラム名を id 以外で指定
Rule::unique('users')->ignore($user->uuid, 'uuid');

// ソフトデリート除外
Rule::unique('users')->whereNull('deleted_at')->ignore($user->id);

複合キーでの存在チェック

例: 「(user_id, role_id) の組み合わせが pivot に存在する」「同じ日に同じユーザーで予約が無いこと」

$request->validate([
    'date' => [
        'required', 'date',
        Rule::unique('reservations')
            ->where(fn ($q) => $q->where('user_id', $request->user_id))
            ->ignore($reservationId),
    ],

    'role_id' => [
        Rule::exists('role_user', 'role_id')
            ->where(fn ($q) => $q->where('user_id', $request->user_id)),
    ],
]);

FormRequest にまとめる

Controller が肥大化しないよう、php artisan make:request StoreUserRequest で専用クラスへ:

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class StoreUserRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', User::class);
    }

    public function rules(): array
    {
        $userId = $this->route('user')?->id;

        return [
            'name' => 'required|string|max:255',
            'email' => [
                'required', 'email',
                Rule::unique('users')->ignore($userId),
            ],
            'department_id' => [
                'required',
                Rule::exists('departments', 'id')->where(
                    fn ($q) => $q->where('active', 1)
                ),
            ],
        ];
    }

    public function messages(): array
    {
        return [
            'email.unique' => 'このメールアドレスは既に登録されています。',
            'department_id.exists' => '指定された部署は存在しないか無効です。',
        ];
    }
}
// Controller
public function store(StoreUserRequest $request)
{
    // 既にバリデーション済み
    $validated = $request->validated();
    User::create($validated);
}

カスタムメッセージとフィールド名

$request->validate([
    'user_id' => 'required|exists:users,id',
], [
    'user_id.exists' => '指定されたユーザーは存在しません。',
    'user_id.required' => 'ユーザー ID は必須です。',
], [
    'user_id' => 'ユーザー',  // :attribute 用の表示名
]);

グローバルメッセージは resources/lang/ja/validation.php:

return [
    'exists' => '選択された:attributeは正しくありません。',
    'unique' => 'この:attributeは既に使用されています。',

    'attributes' => [
        'email' => 'メールアドレス',
        'user_id' => 'ユーザー',
    ],
];

パフォーマンス: exists / unique はインデックスを使う

exists:users,email は内部で SELECT count(*) FROM users WHERE email = ? を発行します。email にインデックスが無いと全件スキャンになり、大量データで激重に:

// マイグレーションで明示的にインデックスを
Schema::table('users', function (Blueprint $t) {
    $t->index('email');
    // unique 制約があれば不要(unique 自体がインデックス)
    $t->unique('email');
});

// 確認
DB::select('EXPLAIN SELECT count(*) FROM users WHERE email = ?', ['a@b.c']);

配列で大量チェック時の N+1 問題

下記の exists:roles,id配列の要素ごとに 1 クエリ発行します:

// ❌ 100 個の role_ids で 100 クエリ
$request->validate([
    'role_ids' => 'array',
    'role_ids.*' => 'exists:roles,id',
]);

// ✅ カスタムルールで 1 クエリ
use Illuminate\Validation\Rule;
$request->validate([
    'role_ids' => ['array', function ($attr, $value, $fail) {
        $missing = collect($value)->diff(
            DB::table('roles')->whereIn('id', $value)->pluck('id')
        );
        if ($missing->isNotEmpty()) {
            $fail("存在しないロール: " . $missing->implode(','));
        }
    }],
]);

FAQ

Q: exists でソフトデリート済みも含めたい
A: デフォルトでは生 SQL で WHERE deleted_at IS NULL は付きません(exists ルールはモデルを使わないため)。逆に除外したいなら ->whereNull('deleted_at') を明示。

Q: Rule::exists のクロージャの中で auth() が使える?
A: 使えます。ただしクロージャ実行時の認証状態が反映されるため、ジョブ内で auth()->user() が null になるケースに注意。

Q: 存在しなければ作成、存在すれば更新(upsert)の検証は?
A: exists ではなく required+モデル側の updateOrCreateupsert を使う設計が一般的です。