千株式会社の初 Advent Calendar、私の初 Qiita !
追記:強化版ができました
Laravel のバリデーションルール exists に Eloquent を使う v2
背景
Laravel の DB レコード存在チェックをするバリデーションルールとして exists がある
しかしハードコーディングな要素が多いので、Eloquent を使って要素を意識させないようにしたい
例えば、ソフトデリートを考慮するには、
- テーブル名
- 検索するカラム名
- ソフトデリートのカラム名
- ソフトデリートの削除されていないときの値
を知って、コントローラかフォームリクエストに書く
'id' => [
    'required',
    'integer',
    'exists:hoges,id,deleted_at,null', // これ
],
Eloquent を使っていると、グローバルスコープでカラム名やその値を意識せずに書けるので、バリデーションもそうしたい
解決策
カスタムバリデーションルールのなかでは、ルールオブジェクトがシンプルに書けるので使う
ルールオブジェクトのクラスのソースコードが次
コンストラクタが使われていないので、そこでモデルクラスを渡す
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class Exists implements Rule
{
    protected $modelName;
    protected $propertyName;
    public function __construct(string $modelName, ?string $propertyName = null)
    {
        $this->modelName = $modelName;
        $this->propertyName = $propertyName;
    }
    public function passes($attribute, $value)
    {
        if ($this->propertyName === null) {
            $this->propertyName = $attribute;
        }
        return $this->modelName::where($this->propertyName, $value)->exists();
    }
    public function message()
    {
        return ':attribute はデータが存在しません';
    }
}
使い方
リクエストのフィールド名と同じカラム名で探すなら
use App\Hoge;
use App\Rules\Exists;
'id' => [
    'required',
    'integer',
    new Exists(Hoge::class), // これ
],
と書けば、ソフトデリートなどのグローバルスコープも考慮してくれる
リクエストのフィールド名と異なるなら、カラム名を指定できる
'hoge_id' => [
    'required',
    'integer',
    new Exists(Hoge::class, 'id'), // これ
],
すっきり!
追伸
使ってみるとカラム名を指定することが多い&主キーで探すことが多いので、指定がないときはフィールド名と同じではなく主キーのほうがより良さそう
あとで変えるかも