【Laravel】Middleware は設定する場所によって実行される順番が異なる

# はじめに
以前Laravelのミドルウェアを用いてLaravel内部で作成したAPIのレスポンスを返す前にログをDBへ記録しようとした際に、ミドルウェアの順番の問題によってかなり手間取ってしまったことがありました。
そのため一度Laravelのミドルウェアの順番に関して把握しておくべきだと考え、調べてみました。
# 環境
OS: Ubuntu-20.04 (Windows 10 の WSL2 上に設置)
PHP: 8.0.10
Laravel: 8.61.0
# 検証に使用するミドルウェアの中身
Laravelのミドルウェアは、リクエストがアプリケーションによって処理される**前**にタスクを実行するものと、リクエストがアプリケーションによって処理された**後**にタスクを実行するものの2種類があります。

今回はミドルウェアの順番を調べるため、storage/logs/laravel.logにデバッグログを出力するミドルウェアを作成します。ログの内容はミドルウェア名とします。

## リクエストがアプリケーションによって処理される前にログを記録するミドルウェアの例
```app/Http/Middleware/PreGlobalMiddleware.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class PreGlobalMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        Log::debug("PreGlobalMiddleware");

        return $next($request);
    }
}
```
## リクエストがアプリケーションによって処理された後にログを記録するミドルウェアの例
```app/Http/Middleware/AfterGlobalMiddleware.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class AfterApiRouteMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $response = $next($request);

        Log::debug("AfterApiRouteMiddleware");

        return $response;
    }
}
```
# 検証に使用するミドルウェア一覧
Laravelのミドルウェアには、グローバルミドルウェア、ミドルウェアグループ、およびルートに対するミドルウェアの3種類があります。また、デフォルトではミドルウェアグループの対象として、webとapiの2つがあります。

今回はミドルウェアの順番を検証するために以下のミドルウェアを作成します。
リストの「前」は「リクエストがアプリケーションによって処理される前にログを記録するミドルウェア」を、「後」は「リクエストがアプリケーションによって処理された後にログを記録するミドルウェア」を示しています。
- グローバルミドルウェア
    - 前
        - PreGlobalMiddleware
        - PreGlobalMiddleware2
    - 後
        - AfterGlobalMiddleware
        - AfterGlobalMiddleware2
- ミドルウェアグループ
    - web
        - 前
            - PreWebRouteMiddleware
            - PreWebRouteMiddleware2
        - 後
            - AfterWebRouteMiddleware
            - AfterWebRouteMiddleware2
    - api
        - 前
            - PreApiRouteMiddleware
            - PreApiRouteMiddleware2
        - 後
            - AfterApiRouteMiddleware
            - AfterApiRouteMiddleware2
- ルートに対するミドルウェア
    - 前
        - pre.test: PreTestRouteMiddleware
        - pre.test2: PreTestRouteMiddleware2
    - 後
        - after.test: PreTestRouteMiddleware
        - after.test2: PreTestRouteMiddleware2

ミドルウェアを設定するKernelは以下のようにします。
```app/Http/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 = [
        // \App\Http\Middleware\TrustHosts::class,
        \App\Http\Middleware\TrustProxies::class,
        \Fruitcake\Cors\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\PreGlobalMiddleware::class,
        \App\Http\Middleware\PreGlobalMiddleware2::class,
        \App\Http\Middleware\AfterGlobalMiddleware::class,
        \App\Http\Middleware\AfterGlobalMiddleware2::class
    ];

    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    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,
            \App\Http\Middleware\PreWebRouteMiddleware::class,
            \App\Http\Middleware\PreWebRouteMiddleware2::class,
            \App\Http\Middleware\AfterWebRouteMiddleware::class,
            \App\Http\Middleware\AfterWebRouteMiddleware2::class
        ],

        'api' => [
            // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
            \App\Http\Middleware\PreApiRouteMiddleware::class,
            \App\Http\Middleware\PreApiRouteMiddleware2::class,
            \App\Http\Middleware\AfterApiRouteMiddleware::class,
            \App\Http\Middleware\AfterApiRouteMiddleware2::class
        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'pre.test' => \App\Http\Middleware\PreTestRouteMiddleware::class,
        'pre.test2' => \App\Http\Middleware\PreTestRouteMiddleware2::class,
        'after.test' => \App\Http\Middleware\AfterTestRouteMiddleware::class,
        'after.test2' => \App\Http\Middleware\AfterTestRouteMiddleware2::class
    ];
}
```
また、webとapiのルートにそれぞれ専用のミドルウェアを以下のように設定します。
※WebページとAPIのレスポンス内容の記載は省略します。Webページは無地のページを、APIはJSONでHTTPコード200を返す処理をしています。
```route/web.php
<?php

use App\Http\Controllers\Front\TopPageController;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', [TopPageController::class, 'index'])
    ->name('top_page')
    ->middleware(['pre.test', 'pre.test2', 'after.test', 'after.test2']);
```
```route.api.php
<?php

use App\Http\Controllers\TestApiController;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::apiResource('test', TestApiController::class)
    ->middleware(['pre.test', 'pre.test2', 'after.test', 'after.test2']);
```
# 検証方法
Webページ (http://localhost) とAPI (http://localhost/api/test) にアクセスして、storage/logs/laravel.logに登録されたログを確認します。
# 検証結果
# Web
```laravel.log
[2022-06-11 13:23:02] local.DEBUG: PreGlobalMiddleware  
[2022-06-11 13:23:02] local.DEBUG: PreGlobalMiddleware2  
[2022-06-11 13:23:02] local.DEBUG: PreWebRouteMiddleware  
[2022-06-11 13:23:02] local.DEBUG: PreWebRouteMiddleware2  
[2022-06-11 13:23:02] local.DEBUG: PreTestRouteMiddleware  
[2022-06-11 13:23:02] local.DEBUG: PreTestRouteMiddleware2  
[2022-06-11 13:23:02] local.DEBUG: AfterTestRouteMiddleware2  
[2022-06-11 13:23:02] local.DEBUG: AfterTestRouteMiddleware  
[2022-06-11 13:23:02] local.DEBUG: AfterWebRouteMiddleware2  
[2022-06-11 13:23:02] local.DEBUG: AfterWebRouteMiddleware  
[2022-06-11 13:23:02] local.DEBUG: AfterGlobalMiddleware2  
[2022-06-11 13:23:02] local.DEBUG: AfterGlobalMiddleware  
```
# API
``` laravel.log
[2022-06-11 13:24:03] local.DEBUG: PreGlobalMiddleware  
[2022-06-11 13:24:03] local.DEBUG: PreGlobalMiddleware2  
[2022-06-11 13:24:03] local.DEBUG: PreApiRouteMiddleware  
[2022-06-11 13:24:03] local.DEBUG: PreApiRouteMiddleware2  
[2022-06-11 13:24:03] local.DEBUG: PreTestRouteMiddleware  
[2022-06-11 13:24:03] local.DEBUG: PreTestRouteMiddleware2  
[2022-06-11 13:24:03] local.DEBUG: AfterTestRouteMiddleware2  
[2022-06-11 13:24:03] local.DEBUG: AfterTestRouteMiddleware  
[2022-06-11 13:24:03] local.DEBUG: AfterApiRouteMiddleware2  
[2022-06-11 13:24:03] local.DEBUG: AfterApiRouteMiddleware  
[2022-06-11 13:24:03] local.DEBUG: AfterGlobalMiddleware2  
[2022-06-11 13:24:03] local.DEBUG: AfterGlobalMiddleware  
```
# 検証結果より
ミドルウェアが実行される順番は以下の通りになることが確認できました。
1. グローバルミドルウェアの前処理
    1. 1つ目に登録したもの
    2. 2つ目に登録したもの
2. ミドルウェアグループの前処理
    1. 1つ目に登録したもの
    2. 2つ目に登録したもの
3. ルートに設定したミドルウェアの前処理
    1. 1つ目に登録したもの
    2. 2つ目に登録したもの
4. ルートに設定したミドルウェアの後処理
    1. 2つ目に登録したもの
    2. 1つ目に登録したもの
5. ミドルウェアグループの後処理
    1. 2つ目に登録したもの
    2. 1つ目に登録したもの
6. グローバルミドルウェアの後処理
    1. 2つ目に登録したもの
    2. 1つ目に登録したもの

ミドルウェアの後処理はルート→グループ→グローバルの順になっていることに注意です。
# 最後に
Laravelでミドルウェアを用いる際は、処理の順番に気を付けて実装しましょう。