はじめに
引き続きLaravelの使い方を勉強しています。昔、別のフレームワークで「POSTされた文字列から改行コードのCRだけを削除する」、つまりwindowsから送信された場合は改行コードがCR+LFになるので、それをLFのみに変換するという機能を作ったことがありました。Laravelではどうやるのか調べて、middlewareという機能に行き当たりました。Laravelでは基本かつ頻繁に使われるもののようなので、少し掘り下げてみます。
環境はmacOS High Sierra+Laradockで構築し、LaravelのバージョンはLTS最新の5.5を使用します。
middlewareとは
どのwebアプリも「HTTPリクエストをコントローラに渡し、必要な処理を行ってレスポンスを返す」というのが基本のフローです。Laravelにおいてはこのフローの前後に挟まる処理をmiddlewareとして実装します。
以下はほんの一例で、他にも様々な要件がプロジェクトごとにあると思いますが、そうした要件の実装をコントローラに全て書いたりせず、コードを綺麗に分離することができそうです。
- ログイン済みの状態でログインページにアクセスしたら、特定のページにリダイレクトする
- フォームから送信された値をフィルタリングする
- 特定のIP以外からのアクセスに対してはHTTP 404を返す
- ステージング環境のみベーシック認証をかける
- レスポンスのjsonに値を付け足す
- レスポンスとして送るHTMLを最小化(minify)する
- レスポンスを返した後にログを保存する/Redis等のストレージにセッション情報を保存する
実装
middleware作成
artisan
コマンドを使用します。今回は前述の「POSTされた文字列から改行コードのCRだけを削除する」処理を実装しようと思います。
フォームから送信された値のフィルタリングを行うので
FormInputFilter
という名前にします。
1 2 | $ php artisan make:middleware FormInputFilter Middleware created successfully. |
app/Http/Middleware
以下にスケルトンが作成されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <?php namespace App\Http\Middleware; use Closure; class FormInputFilter { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { return $next($request); } } |
コード実装
handleメソッド内に記述します。
return $next($request);
より前のコードはコントローラに処理が渡る前に実行されます。
POST値を書き換えたい場合はrequestオブジェクトの
merge
あるいは
replace
メソッドを使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class FormInputFilter { public function handle($request, Closure $next) { $new_input = []; foreach ($request->input() as $key => $val) { $new_input[$key] = str_replace("\r", "", $val); } $request->replace($new_input); return $next($request); // $response = $next($request); // レスポンス返却前に実行したいコードを記述する // return $response; } public function terminate($request, $response) { // レスポンス返却後に実行したいコードを記述する } } |
コントローラでの処理の後に実行したいコードがある場合、レスポンスを返す前に実行したいのであればhandle()に記述します(今回の実装目的ではないのでコメントアウトしています)。
レスポンスを返した後に実行したいコードはterminate()に記述します。ロギング・セッション更新・メール送信といった、レスポンス返却とは関係がない処理をmiddlewareに追い出すことで、レスポンスの高速化や処理の分割ができそうです。
middleware定義
作成したmiddlewareを定義することでmiddlewareが呼び出されるようにします。様々な方法があり、要件ごとにやり方は異なりますが、いくつかのパターンをご紹介します。
常に呼び出す
全てのアクセスで呼び出したい場合は以下のように記述します。アクセス元IP確認やログイン認証などは毎回行いたい処理になると思いますので、ここで定義することになるのではないでしょうか。
app/Http/Kernel.php
の
$middleware
プロパティに記載することで、全てのリクエストで呼び出すmiddlewareとして定義されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?php namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { ... ... protected $middleware = [ ... ... \App\Http\Middleware\FormInputFilter::class, // ← artisanで作成したFormInputFilterを追記 ]; ... ... } |
特定のURLにアクセスした時のみ呼び出す
ルートを定義する箇所でmiddlewareの定義を同時に行えます。特定のアクションメソッドでのみ呼び出したいといった場合はこの定義になると思います。
「
http://localhost/register
というURLにPOSTされた時のみ呼び出す」と仮定した場合、
routes/web.php
で以下のように追記します。
1 | Route::post('register', 'Auth\RegisterController@register')->middleware('FormInputFilter'); |
コントローラ単位で呼び出す
どのアクションメソッドかは問わず、特定のコントローラに処理が渡る時のみ呼び出したい場合です。
middlewareはルート定義ファイル以外にも、コントローラで呼び出すことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <?php namespace App\Http\Controllers\Auth; use App\User; use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Validator; use Illuminate\Foundation\Auth\RegistersUsers; class RegisterController extends Controller { ... ... public function __construct() { $this->middleware('FormInputFilter'); } ... ... } |
ルート定義の確認
artisan
コマンドを実行すると、
Middleware
という項目で確認できます。
1 2 3 4 5 6 7 8 | $ php artisan route:list +--------+----------+----------+------+-------------------------------------------------------+---------------------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+----------+----------+------+-------------------------------------------------------+---------------------------+ | | GET|HEAD | / | | Closure | web | | | GET|HEAD | api/user | | Closure | api,auth:api | | | POST | register | | App\Http\Controllers\Auth\RegisterController@register | web,FormInputFilter,guest | +--------+----------+----------+------+-------------------------------------------------------+---------------------------+ |
今回定義したFormInputFilterが、
/register
にアクセスする時のMiddlewareに表示されています。
さいごに
リクエストの前後に処理を挟みたいというのはサーバサイドの実装では頻繁にあり、middlewareを使う方法はコードが切り離されて整理・把握しやすくなる印象を受けました。深く考えずに必要な処理をどんどんアクションメソッドに書き足してファットコントローラになることを防ぐためにも、こういったフレームワークの作法をしっかり学んでおきたいところです。
ただし、考えなしにmiddlewareを追加していってもそれはそれで全体の処理が重くなってしまいますので、キューによる非同期処理も考慮するなどして、全体的なレスポンス速度にも気を配りましょう。