はじめに
仕事で Laravel 内でのファイルの数とサイズの扱いに関して調べましたので、備忘録代わりにこちらへ記載しておきます。
バージョン情報
今回調査を行った対象のバージョンを以下に記載しておきます。
Laravel:11
PHP:8.4
ファイルの数の対する検証
基本的なルール設定
複数ファイルの入力に対してその数をバリデーションする場合、以下のようなルール設定になるかと思います。(例:最大5ファイルまで)
$validator = validator($request->all(), [
'files' => 'nullable|array|max:5',
'files.*' => 'required|file',
]);
入力の配列側の方にその個数に関するルールを設定する形となり、max であれば上限数を超えたときにバリデーションエラーとなり、「指定数以下にしてください」といったようなエラーが返される想定となります。
php.ini 側での注意点
上記のルール設定の場合、実際には laravel が動作する php 側の設定に存在する max_file_uploads 設定についても気を付ける必要があります。
この設定はその名前の通り、php 側でリクエストを処理する際の入力ファイルの最大値を設定しているものです。(デフォルト:20)
この設定の注意点としては、設定値以上のファイルを入力した際、 設定値までの個数のファイルだけを入力として扱い、それ以降のファイルは処理せず存在が抹消されるという挙動をする点があります。
このため、max のバリデーションを設定していたとしても、その設定値が「max_file_uploads 側の設定値がバリデーション側の設定値よりも小さい」となる場合、実際のフロントエンドでは実際の上限数より多くファイルを指定していたとしても、laravel の処理の段階ではファイル数が減らされているため「max 以下のファイルが入力されているので OK」という判定になります。
対策
正直なところ、 「使用するバリデーション側の設定値よりも max_file_uploads 側の値が大きく or 小さくなるようにする」 以外のバックエンドの laravel 側にできる簡単な対応はありません。
この対応も max, size, between のようなルールであれば一応は大丈夫ですが、min の場合は結局どこかの段階で「min 以上のファイル数ではあるが max_file_uploads 側の設定値で足切り」という事態を招きます。
その為、現実的な対応としては、フロントエンドの javascript コードでファイル数のチェックを行う という対応が望ましいです。
余談
複数ファイルアップロードの input が複数存在する場合、max_file_uploads の処理は リクエストの総ファイル数 に対して処理が行われるため、max_file_uploads の設定値を迎えた input のところでファイルの処理は終了します。
例:max_file_uploads=5 に対して、files1[], files2[], files3[] にそれぞれ 3 ファイルを入力
結果:files1 は 3 ファイル、files2 は 2 ファイル、files3 は 0 ファイル(=files3 自体が無視)の入力とみなされる
ファイルのサイズに対する調査
基本的なルール設定
複数ファイルの入力に対して各ファイルのサイズをバリデーションする場合、以下のようなルール設定になるかと思います。(例:最大500キロバイトまで)
$validator = validator($request->all(), [
'files' => 'nullable|array',
'files.*' => 'required|file|max:500',
]);
入力の各要素側の方にサイズに関するルールを設定する形となり、max であれば上限サイズを超えたときにバリデーションエラーとなります。
php.ini 側での注意点
上記のルール設定の場合、実際には laravel が動作する php 側の設定に存在する upload_max_filesize 設定と post_max_size 設定についても気を付ける必要があります。
upload_max_filesize
upload_max_filesize は php 側でリクエストを処理する際の入力ファイル 単体 の最大サイズ値を設定しているものです。(デフォルト:2M)
この設定の値を超えるサイズのファイルを投入した場合、$request から取得した場合のファイルクラスの内容は以下のようになります。
(画像上側の「big_dummy_01.pdf」がサイズ超過ファイル)
画像を見てわかる通り、ファイルクラスの各種値が false 等になっており、ファイルの存在自体は確認できているものの、各種特徴は取得していないエラー対象であることがわかります。
この内容のままバリデーションに通した場合、各要素のルール側に required や file といった実在性の担保が求められるルールを置いていると uploaded という特殊なルールに違反した扱いとなります。
エラーメッセージを見てもわかる通り、この uploaded は専用のバリデーションエラーメッセージが設定されています。
post_max_size
post_max_size は php 側でリクエストを処理する際の POST 内容の 合計 の最大サイズ値を設定しているものです。(デフォルト:8M)
POST 内容のサイズがこの設定の値を超えた場合、laravel の標準的な挙動では Illuminate/Http/Middleware/ValidatePostSize ミドルウェアにより PostTooLargeException エラーが渡されます。
ValidatePostSize ミドルウェアは Illuminate\Foundation\Configuration\Middleware の getGlobalMiddleware() にて指定されており、指定の設定を変更する場合は、laravel 11 であれば /bootstrap/app.php より変更を行う必要があります。
(詳しい変更方法などは公式ドキュメントをご確認ください。また、laravel のバージョンが違う場合はデフォルトのミドルウェア設定の場所から違う可能性があります。)
対策
基本的には、upload_max_filesize < post_max_size (< memory_limit) となるように余程のことがなければ投入されないようなサイズを設定することが妥当な対応です。
upload_max_filesize に関してより完璧に対応する場合は、uploaded バリデーションエラーのメッセージを整備することでおおむね解決できるかと思います。
post_max_size に関してより完璧に対応する場合は、ValidatePostSize ミドルウェアの対応を検討する必要があります。
システム側のエラー画面を出すとしても汎用の 500 エラー画面で良いのか、専用のエラー画面を出せるように追加のエラーハンドリングを用意するかを考慮する必要があります。
POST 元の画面にエラーメッセージを返す場合は 別のミドルウェアやエラーハンドラーを通じてフラッシュセッション経由でコントローラまで何らかの引数を渡し、画面側もその引数をもとにメッセージを出せるようにする必要があります。
また、フロントエンドの javascript ではファイルの数の他にサイズも見ることができるので フロントエンドの javascript コードでファイルの単体及び合計のサイズチェックを行う という対応も有力な対応です。
余談
複数ファイルアップロード input の合計サイズをバリデーションでチェックする場合、入力の配列側にルールを設定する必要がありますが、laravel 備え付けのルールではこのような状況に対応できないので、独自ルールの定義が必要になります。
この際、リクエスト内容から UploadedFile クラスを見てサイズの計算をしていく訳ですが、upload_max_filesize 超過によるエラーファイルを考慮した対応が必要です。
更に、複数ファイルアップロードの input が複数存在する状況で、複数の input を横断して合計サイズをバリデーションでチェックする場合、基本的なバリデーションを行った後でinput 横断を想定した別のバリデーションを行うといったクフが亜必要となります。