【Laravel】Middlewareでpost_max_sizeを超えたリクエストの処理をする

post_max_sizeを超えたリクエストを処理する

Laravelでフォームからアップロードしたファイルの大きさがphp.iniファイルのpost_max_sizeの設定値を超えた場合に、
例外をスローさせずにハンドリングする方法をメモします。

環境 : Laravel Framework 5.8.35 、Mac OS 10.13.6

PostTooLargeExceptionが発生

フォームからポストしたファイルの大きさが、post_max_sizeの値よりも大きかった場合
例外、Illuminate \ Http \ Exceptions \ PostTooLargeExceptionがスローされる

ファイルサイズのバリデーションが効かない

コントローラのアクションで引数に指定している自作のRequestクラスのバリデーションルール

    public function rules()
    {
        return [                                             
            'upfile' => 'required | mimes:jpeg,png,gif | size:10',
        ];
    }
    public function messages()
    {
        return [
            'upfile.size' => 'ファイルサイズが大きすぎます。',
        ];
    }

PHPでは設定したpost_max_sizeの値よりポストしたファイルサイズが大きかった場合、
リクエスト内容そのものが破棄される為、
上記の様にリクエストに対してファイルサイズの大きさを制限するバリデーション(size)を使用しても、
目的の処理を行う事が出来ません。

LaravelにおいてURLルーティングされたコントローラクラスよりも先にリクエストを受け取る事ができる、
Middleware(ミドルウェア)という仕組みを利用して解決していきます。

Middlewareの作成

artisanコマンドでMiddlewareを作成する

php artisan make:middleware UploadSizeCheckMiddleware(ミドルウェアの名前)

上記コマンドを実行すると、App\Http\MiddlewareディレクトリにUploadSizeCheckMiddlewareクラスが作成されます。

作成されたクラスのhandleメソッド内へ行いたい処理の内容を記述していきます。

UploadSizeCheckMiddleware.php

<?php

namespace App\Http\Middleware;

use Closure;

class UploadSizeCheckMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {         
        if(isset($_SERVER['CONTENT_LENGTH'])){
            
            $post_max_size = $this->return_bytes(ini_get('post_max_size'));
            $uploaded_size = intval($_SERVER['CONTENT_LENGTH']);                          
            
            if($post_max_size < $uploaded_size ){
                return redirect('/')->with('message', 'ファイルサイズが大きすぎます。');                                                
            }
        }                
        return $next($request);
    }
    
    public function return_bytes($val) {
        
        $val = trim($val);        
        $unit = $val[strlen($val)-1];
        $_val = substr($val, 0, strlen($val)-1);        
        
        if($unit == 'G') 
        return intval($_val) * 1024 * 1024 * 1024;
        
        if($unit == 'M') 
        return intval($_val) * 1024 * 1024;
        
        if($unit == 'K') 
        return intval($_val) * 1024;
    }
}

PHPのスーパーグローバル変数である $_SERVER[‘CONTENT_LENGTH’] を使用し、
ポストされたファイルの大きさを取得して※post_max_sizeの設定値と比較します。
(※ini_get関数でphp.iniの設定値を取得出来る)

ポストされたファイルサイズがpost_max_sizeよりも大きければ、指定のページへリダイレクトさせる処理となっています。

作成したMiddlewareをKernel.phpへ登録する

ミドルウェアの作成が完了したらApp\Http内のKernel.phpというファイルへミドルウェアを使用する為の登録記述を行います。

Kernel.php

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        //追加
        \Illuminate\Session\Middleware\StartSession::class, 
        //追加               
        \App\Http\Middleware\UploadSizeCheckMiddleware::class,
        
        \App\Http\Middleware\TrustProxies::class,
        \App\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];
(略)

Kernelクラスの配列のメンバーとなっている$middlewareの箇所へ、
今回作成したUploadSizeCheckMiddlewareクラスを上記のように追加します。

順番が重要で、前述したPostTooLargeExceptionをスローする
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class
よりも先に書くようにします。
(先頭に追加して行けば問題ないかと思います)

セッションを利用するために

本例ではリダイレクト先にセッションを利用してメッセージを飛ばす為、
セッションに関するミドルウェアの\Illuminate\Session\Middleware\StartSession::classを同時にmiddlewareへ記述しています。

(注意)
同Kernel.php内、$middlewareよりも下にある$middlewareGroupsの方の
\Illuminate\Session\Middleware\StartSession::classを以下のようにコメントアウト(または削除)しておかないと、
ミドルウェアからコントローラへ処理が流れた時、他のバリデーション結果を保存する$errors変数の内容が消えてしまい取得出来ませんでした。

    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            // コメントアウト
            // \Illuminate\Session\Middleware\StartSession::class,            
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

Follow me!