thumbnail

Laravel Validatorの実装を追ってみたよ。

Date: 2021/08/15 13:04

Category: none

そもそも

PHPのWebFrameworkであるLaravelには、様々な便利な機能がある。
FormRequestもそのひとつである。
これを用いることで、フォームから送信された値にvalidationをかけることができる。
rules()にバリデーションルールを、messages()にどんなバリデーションエラーメッセージを表示するかを指定することができる。

そんなLaravelのFormRequestを用いたバリデーションで、アップロードファイルのバリデーションも行うことができる。たとえば、以下のようにすることで、ファイルをアップロードを2MB(2048KB)までに限定することができるはずである。

ところが、2MB以上のファイルをアップロードしてもバリデーションエラーになってくれなかった...
文法的にはまちがってないんだけどな...

Laravelのissueでバグ報告がないか調べると、1件だけ類似の報告があった。が、取り合ってもらえてない。

https://github.com/laravel/framework/issues/37933

バグの可能性も残りつつ、とりあえずコードを読んでみよう!ということで、Validator周りのコードを読むことになった。

Laravel Code Reading!!!

まず、FormRequestに定義したバリデーションルールがどのように読み込まれ、どのように実行されているのかを追うことにする。
バリデーション済みの値を取得するのは、validated()というメソッドである。ここからみていこう。

vendor/laravel/framework/src/Illuminate/Foundation/Http/FormRequest.php

この
$this->validator
は、

vendor/laravel/framework/src/Illuminate/Contracts/Validation/Validator.php

で定義されているinterface Validator class を想定している。
これの実装は、

vendor/laravel/framework/src/Illuminate/Validation/Validator.php

である。実装されたvalidated()を確認すると...

この$this->getRules()の中にはちゃんとFormRequestを継承して作ったRequestファイルに定義されたルールが確認できた。(ddで確認したら本当にruleが入っている連想配列だった)

$valueも同様にvar_dump()で値を確認したところ、少なくとも一時的なファイル名を表す記号列を確認できたのでここまでは多分大丈夫。

いや、ここではvalidate()が呼ばれてない、だと... ? Arr::setもdata_getもデータの形かえてるだけじゃん...

もしや、$this->invalid() お主か?

$this->passes()それっぽいのいるぞ!

念のため、$ruleをvar_dumpしてみると、以下のように設定したバリデーションルールが得られた。

ここら辺はよくわからないので、var_dump()を仕掛けるだけ仕掛けて、デバッグだ。

validateAttribute がvalidationを実行しているっぽいぞ。

ファイルアップロードに失敗したらここで弾かれるようだ。ここは通っている。

$methodをvar_dumpしたら呼び出されるvalidationメソッド名が得られた。ここではファイルサイズのバリデーションを疑っているので、validatedMaxを見に行くことにしよう。

なんだと、ここではvalidation していないだと?

他のフォームで文字数制限をmaxで指定している部分は、確かにここでvalidateMaxを呼び出している。

Validator最初で読み込んでいた。traitだから、ValidatorにvalidateMax()があるかのように振る舞えるんだな。

トレイトについて

vendor/laravel/framework/src/Illuminate/Validation/Concerns/ValidatesAttributes.php 1176~

ここで、var_dump($attribute);を行い、validateMax()がファイルアップロード時に呼ばれていないことを確認した。

throw new Exceptionすると、どこで何が呼ばれているのか追えてありがたいw

ここまでの状況をまとめると...
validateAttributeのvalidatableがFalseなので、validateMaxが呼ばれていないみたい。

validatableがなぜFalseなのかを確認する。
validatePresentに渡されるファイルの値がnullになっていた。

presentOrRuleIsImplicit($rule, $attribute, $value)

$valueの取得に失敗してそう。

form-file, form_file の違いで、正しく値を取得できていなかっただけだった。

バグを疑ってごめんなさい、Laravelさん...

まとめ

LaravelのFormRequestでアップロードファイルサイズのバリデーションが動かない... ということで、
コードを読んだ結果、formにつけているname名とこのFormRequestに定義しているname名が一致していないことが原因だった... この程度のミスで一体どれだけ時間を溶かしたんだ...

Hero

まさき。です。PHPエンジニアをやってます。

自分の課題を技術で乗り越えるの好きかもしれないです。

フロントエンドは苦手ですが、少しでもできるようになれたらな、ということでNextJSでこのブログサイトを作りました。