Laravel - 23. ミドルウェア
Laravelの特徴的な仕組みの1つにミドルウェアがあります。ミドルウェアとはHTTPリクエストやレスポンスを透過的に処理する仕組みです。Laravelフレームワークの中でもデフォルトでいくつかのミドルウェアが実装されています。ミドルウェアはユーザーの認証処理やバリデーション、セッション管理など様々な用途に利用されています。ここではLaravelのミドルウェアの仕組みについて学習します。
ミドルウェアの管理
Laravelのミドルウェアと呼ばれるプログラムは app/Http/Kernel.php ファイルで管理されています。テキストエディタで開くと次のように表示されるでしょう。
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
    protected $middleware = [
        \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,
    ];
    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,
        ],
        'api' => [
            'throttle:60,1',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::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,
    ];
    protected $middlewarePriority = [
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\Authenticate::class,
        \Illuminate\Routing\Middleware\ThrottleRequests::class,
        \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Illuminate\Auth\Middleware\Authorize::class,
    ];
}
上記のように app/Http/Kernel.php ファイルには様々なミドルウェアが定義されているのがわかります。また app/Http/Kernel.php ファイルには4つの変数が定義されています。
| 変数名 | 意味 | 
|---|---|
$middleware | 
ミドルウェア。すべてのルートに対して有効となる | 
$middlewareGroups | 
ミドルウェアグループ。 'web' 、 'api' などグループ化したルートに対応する | 
$routeMiddleware | 
ルートミドルウェア。特定のルートに対応する | 
$middlewarePriority | 
ミドルウェアプライオリティ。ミドルウェアの実行優先順序を決定する | 
このようにミドルウェアはルーティングに合わせてカスタマイズできるようになっています。
ミドルウェアの作成
ここでは新たにコントローラの処理時間を計測する LoggingMiddleware クラスを作成することにします。ミドルウェアの作成には php artisan make:middleware コマンドを使います。
$ php artisan make:middleware ミドルウェア名
ミドルウェア名に LoggingMiddleware と指定してコマンドを実行してみましょう。
$ php artisan make:middleware LoggingMiddleware
Middleware created successfully.
コマンドが成功すると app/Http/Middleware/LoggingMiddleware.php ファイルが生成されます。
<?php
namespace App\Http\Middleware;
use Closure;
class LoggingMiddleware
{
    public function handle($request, Closure $next)
    {
        return $next($request);
    }
}
生成された LoggingMiddleware クラスには handle メソッドが定義されています。この handle メソッドを実装してコントローラの処理時間を計測するようにします。
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Carbon;
class LoggingMiddleware
{
    public function handle($request, Closure $next)
    {
        $start = Carbon::now();
        $response = $next($request);
        $end = Carbon::now();
        $diff = $end->diffInMilliseconds($start);
        $path = $request->path();
        Log::info($path . ": " . $diff . " ms");
        return $response;
    }
}
handle メソッドは2つの引数を受け取ります。第1引数はミドルウェアの処理しているリクエストを示す $request 、第2引数は次のレイヤーのミドルウェア(あるいはコントローラ)を示す Closure 型の $next です。
$response = $next($request);
上記のように $next 変数を関数として呼び出すことで、次のレイヤーのミドルウェア(終端の場合はコントローラ)を呼び出すことができます。
また handle メソッドはコントローラのアクションメソッドの前処理・後処理として利用できます。具体的には response = $next($request); より以前に記述しているコードは、アクションメソッド呼び出しの前処理として機能し、後に記述しているコードはアクションメソッド呼び出しの後処理として機能します。
今回のサンプルプログラムでは、アクションメソッドの前処理として $start = Carbon::now(); を呼び出しています。 Carbon は日時操作のライブラリです。 use Illuminate\Support\Carbon; によってプログラムの中で利用できるようになります。ここではコントローラのアクションメソッドを呼び出す前に現在日時を指定して Carbon インスタンスを生成しています。
その後、 $response = $next($request); を呼び出したあとに再度、Carbon インスタンスを生成して処理時間を算出しています。
        $end = Carbon::now();
        $diff = $end->diffInMilliseconds($start);
        $path = $request->path();
        Log::info($path . ": " . $diff . " ms");
        return $response;
2つの Carbon インスタンスを使って $diff = $end->diffInMilliseconds($start); とすることで処理時間(ms)を算出しています。それから Log::info($path . ": " . $diff . " ms"); とすることで、ログファイルにアクセスされたパスと処理時間を記録しています。また handle メソッドは戻り値にレスポンスを示す $response 変数を返します。これによって次のレイヤーのミドルウェアへと処理が進みます。
続いて作成した LoggingMiddleware クラスを app/Http/Kernel.php に追加してみましょう。ここでは $middlewareGroups 変数( 'web' グループ)と $middlewarePriority 変数の2箇所に LoggingMiddleware を追記します。
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
    # 省略...
    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\LoggingMiddleware::class,
        ],
        'api' => [
            'throttle:60,1',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];
    # 省略...
    protected $middlewarePriority = [
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\Authenticate::class,
        \Illuminate\Routing\Middleware\ThrottleRequests::class,
        \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Illuminate\Auth\Middleware\Authorize::class,
        \App\Http\Middleware\LoggingMiddleware::class,
    ];
}
上記のように修正することで 'web' グループに属するルートに LoggingMiddleware が機能するようになります。また実行の優先順序は最後としているのでコントローラのアクションメソッドの呼び出しの前後で機能することになります。
ミドルウェアの定義情報は php artisan route:list コマンドで確認できます。
$ php artisan route:list
+--------+----------+--------------+------+---------------------------------------------+--------------+
| Domain | Method   | URI          | Name | Action                                      | Middleware   |
+--------+----------+--------------+------+---------------------------------------------+--------------+
|        | GET|HEAD | /            |      | Closure                                     | web          |
|        | GET|HEAD | api/user     |      | Closure                                     | api,auth:api |
|        | GET|HEAD | hello        |      | App\Http\Controllers\HelloController@index  | web          |
|        | GET|HEAD | hello/create |      | App\Http\Controllers\HelloController@create | web          |
|        | POST     | hello/store  |      | App\Http\Controllers\HelloController@store  | web          |
+--------+----------+--------------+------+---------------------------------------------+--------------+
最終列の Middleware に web と表示されているルートに LoggingMiddleware が適用されるようになります。
動作確認
それでは LoggingMiddleware を追加したので、PHPのビルトインWebサーバを使ってWebアプリケーションを起動してみましょう。次のようにコマンドを入力します。
$ php artisan serve --host 0.0.0.0
Laravel development server started: http://0.0.0.0:8000
続いてブラウザを起動してWebアプリケーションにアクセスしてみましょう。
http://localhost:8000/hello

HelloController の index アクションの結果が表示されます。
もう一つアドレスバーから別のURLにアクセスしてみましょう。
http://localhost:8000/hello/create

この場合は HelloController の create アクションの結果が表示されます。
最後にログファイル( storage/logs/laravel.log )を確認してみましょう。
[2020-09-07 23:14:31] local.INFO: hello: 130 ms  
[2020-09-07 23:16:57] local.INFO: hello/create: 60 ms  
ログファイルには2つのアクションメソッドの呼び出しにかかった処理時間が出力されているのがわかります。
まとめ
- ミドルウェアはHTTPリクエスト(レスポンス)を透過的に処理する仕組み
 app/Http/Kernel.phpファイルによってミドルウェアを管理するphp artisan make:middlewareコマンドでミドルウェアを作成できる