【Laravel】バリデーションのアクションを指定する


やりたいこと

Laravel はMVCベースのフレームワークにもかかわらず、モデルの扱いは良い意味で適当で7.x以前までは配置するディレクトリすらも適当でした。そのせいもあってか、4.xまではバリデーションはコントローラにアドホックに記述するしかなくロジックが分散しがちでした。5.xからはフォームリクエストバリデーションという仕組みが導入され、リクエストにバリデーションロジックを付加しカプセル化することが可能になりました。Ruby on Rails等のフレームワークに親しい人はこの仕組を用いて、例えばUserRequestとかArticleRequestといった具合にモデル毎にバリデーションロジックをまとめる事ができるようになって安堵したことでしょう。

バリデーションロジックをまとめたことで、次に出てくる当然の要求はRuby on Railsのバリデーションのonオプションの様にアクションによってそのバリデータを使い分けたいということです。つまり、例えばArticleモデルでは記事のタイトル(title)はstoreの時点では必須(required)としたいが、updateのタイミングでは空でも良い(つまりタイトルは更新しないことを意味する)といった状況です。これはRuby on Railsでは

validates: title, presence: { on: create }

のように、バリデータpresenceonオプションでcreate(Laravelでいうところのstore)時に限定しています。これです。これが Laravel でもやりたいのです。

結論

Laravelには(少なくとも9.xまでには)Railsのonオプションのような便利な機能はなさそうです。でも殆どのバリデータは共通なのに、そのうちの限られたフィールドに関するバリデータが異なるからといってArticleStoreRequestArticleUpdateRequestのようにアクション毎にフォームリクエストを分けていては本末転倒です。(それならコントローラに直接バリデーションを記述した方がよっぽどわかりやすいと筆者は考えます。)

Laravelでこのstore時のみもしくはupdate時のみに作用するバリデータを記述するには、Routeファサードを使用するのが便利だと思います。書き方は以下のようになります。

return [
    'title' => \Route::uses('*store') ? 'required' : 'filled',
];

もちろんフォームリクエストのrulesメソッドの返り値として記述することを想定しています。RouteファサードのusesメソッドはApp\Http\Controllers\ArticleController@storeのような「コントローラ@アクション」という文字列に対してStr::isヘルパで検査をしますのでワイルドカード(*)が使用できます。詳しくはAPIを確認してください。

ちなみに上記のfilledバリデータは、そのフィールドが存在しなくてもOKだが存在するならば空であってはならないという検証を実行します。つまり上記のようなルールを設定すると、新規作成時にはtitleフィールドは指定されなければならず、さらに空文字や空白のみで構成されるような文字列も却下しますが、更新時にはtitleフィールドは指定されなくても許容します。ただし、指定された時にはそれは空文字や空白のみの文字列では許されません。

参考文献