「LaravelのFormRequestでDBに依存したバリデーション書いているやついる?」「いねえよなぁ!!?」

2021/09/19に公開

お詫び

タイトルは釣りです。申し訳ございません。。

結論

FormRequestではDBに似依存した処理は書かないほうがいいなって思いました。後々辛くなるからです。

どうしてやらないほうがいいのか

かけるけど、あまり推奨したくない例

<?php

namespace App\Http\Requests\Account;

use Illuminate\Foundation\Http\FormRequest;

class BasicSampleRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules(): array
    {
        return [
            'email' => ['unique:App\Models\User', 'email_address']
        ];
    }
}

考えられるデメリット

  • email_address というカラムが設定されているがこのカラム名が仮に変更された際に、FormRequestの処理にも修正が入ってしまう。
  • App\Models\User が App\Models\Adminや、App\Models\Guestに変わったときにここも変更する必要がある。
  • 単体テストをするときにDBのデータを準備する必要が出てくる。
  • 要するに、DBやEloquentの実装変更に引きずられてしまう。

対応策

FormRequest

<?php

namespace App\Http\Requests\Account;

use Illuminate\Foundation\Http\FormRequest;

class BasicSampleRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules(): array
    {
        return [
            'email' => ['email:rfc', 'dns'],
        ];
    }
}
  • 入力値に対してLaravelの機能を単純に使う。

でもDBのデータでバリデーションしたい

そういう要望もあるかと思います!その際はEloquentモデルで処理を書いたほうが良いです。
その場合はEloquent層でバリデーションメソッドを作るのが1つの案かなと思ってます。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Validation\ValidationException;

class User extends Authenticatable
{
    use HasFactory, Notifiable, SoftDeletes;

    protected $table = 'users';

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    /**
     * @param string $email
     */
    public function validateEmailIsUnique(string $email): void
    {
        $result = self::query()
            ->where('email', '=', $email)
            ->exists();

        if ($result) {
            throw ValidationException::withMessages([
                'email' => ['メールアドレスが既に存在します'],
            ]);
        }
    }
}

メリットとしてはいかが挙げられます。

  • Eloquentにメソッドおいたほうが、責務としては自然
  • カラム名の変更もこのクラス内に閉じ込められる
  • FormRequestの単体テストが容易になる。

ただ以下の考えるべきこともあります

  • Validationエラーを返すほうがいいのかは検討の余地はある。
  • 本当はDBのデータをチェックしてその結果をBooleanで返すだけにとどめたほうがいいかもしれない。。
  • コントローラーでレスポンスの形式は生成したほうが良いかもしれない

改めて伝えたいこと

クラスの役割を常に意識しよう! ということです。

tyamahoriの主張としては、

  • FormRequestは入力値が意図通りかをチェックする場所であるがDBのデータまで扱うのは違うのかなと思います。
  • ElqouentではDBに関する値をチェックする役割は適切ではありますが、クライアントへのレスポンス生成までの責務は適切ではないと考えます。
  • クライアントへのレスポンスはコントローラーが適切かなとは思います。

Discussion