# 中間件
## 介紹
中間件為過濾進入應用程序的HTTP請求提供了一種方便的機制。例如,Laravel 內置了一個中間件來驗證用戶的身份認證。如果用戶沒有通過身份認證,中間件會將用戶重定向到登錄界面。但是,如果用戶被認證,中間件將允許該請求進一步進入該應用。
當然,除了身份認證以外,還可以編寫另外的中間件來執行各種任務。例如:CORS 中間件可以負責為所有離開應用的響應添加合適的頭部信息;日志中間件可以記錄所有傳入應用的請求。
Laravel 自帶了一些中間件,包括身份驗證、CSRF 保護等。所有這些中間件都位于`app/Http/Middleware`目錄。
## 定義中間件
通過運行`make:middleware`Artisan 命令來創建新的中間件:
~~~php
php artisan make:middleware CheckAge
~~~
該命令會在`app/Http/Middleware`目錄下創建一個新的`CheckAge`類,在這個中間件中,我們僅允許`age`參數大于`200`的請求對此路由進行訪問,否則,將重定向到`home`。
~~~php
<?php
namespace App\Http\Middleware;
use Closure;
class CheckAge
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($request->age <= 200) {
return redirect('home');
}
return $next($request);
}
}
~~~
正如你所見,假如給定的`age`參數小于或等于`200`,這個中間件將返回一個HTTP重定向到客戶端;否則,請求將進一步傳遞到應用中。要讓請求繼續傳遞到應用程序中(即允許「通過」中間件驗證的),只需使用`$request`作為參數去調用回調函數`$next`。
最好將中間件想象為一系列 HTTP 請求,必須經過才能進入你應用的「層」。每一層都會檢查請求(是否符合某些條件),(如果不符合)甚至可以(在請求訪問你的應用之前)完全拒絕掉。
> {提示} 所有的中間件都是通過 \[服務容器\]「 /docs/laravel/5.7/container 」,因此,可以在你的中間件的構造函數中鍵入你需要的任何依賴。
### 前置 & 后置中間件
中間件是在請求之前或之后執行,取決于中間件本身。例如,下面的中間件將在應用處理請求**之前**執行某些任務:
~~~php
<?php
namespace App\Http\Middleware;
use Closure;
class BeforeMiddleware
{
public function handle($request, Closure $next)
{
// 執行動作
return $next($request);
}
}
~~~
而接下來的中間件將在應用請求**之后**執行某些任務:
~~~php
<?php
namespace App\Http\Middleware;
use Closure;
class AfterMiddleware
{
public function handle($request, Closure $next)
{
$response = $next($request);
// 執行動作
return $response;
}
}
~~~
## 注冊中間件
### 全局中間件
如果你希望中間件在應用處理每個 HTTP 請求期間運行。只需要在`app/Http/Kernel.php`中的`$middleware`屬性中列出這個中間件。
### 為路由分配中間件
假設你想為指定的路由分配中間件,首先應該在`app/Http/Kernel.php`文件內為該中間件分配一個鍵。默認情況下,該類中的`$routeMiddleware`屬性下包含了 Laravel 內置的中間件。若要加入自定義的中間件,只需把它附加到列表后并為其分配一個自定義鍵即可。例如:
~~~php
// 在 App\Http\Kernel 類中...
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,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
~~~
一旦在 HTTP 內核中定義好了中間件,就可以通過`middleware`方法將為路由分配中間件:
~~~php
Route::get('admin/profile', function () {
//
})->middleware('auth');
~~~
你也可以為路由分配多個中間件:
~~~php
Route::get('/', function () {
//
})->middleware('first', 'second');
~~~
分配中間件時,你還可以傳遞完整的類名:
~~~php
use App\Http\Middleware\CheckAge;
Route::get('admin/profile', function () {
//
})->middleware(CheckAge::class);
~~~
### 中間件組
某些時候你可能希望使用一個鍵把多個中間件打包成一個組,方便將他們應用到路由中。你可以使用 Http 核心的`$middlewareGroups`屬性。
Laravel 內置了開箱即用的`web`和`api`中間件組,其中包含你可能希望應用于 Web UI 和 API 路由的常用中間件:
~~~php
/**
* 應用程序的路由中間件組
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'auth:api',
],
];
~~~
可以使用與單個中間件相同的語法將中間件組分配給路由和控制器動作。 同樣,中間件組可以更方便地同時為路徑分配多個中間件:
~~~php
Route::get('/', function () {
//
})->middleware('web');
Route::group(['middleware' => ['web']], function () {
//
});
~~~
> {提示}`RouteServiceProvider`默認將`web`中間件組自動應用到`routes/web.php`。
## 中間件參數
中間件也可以接收額外的參數。例如,如果你的應用需要在執行一個特定操作之前驗證用戶是否為給定的「角色」,你可以創建一個`CheckRole`中間件,由它來接收「角色」名稱作為附加參數。
附加的中間參數應該在`$next`參數之后傳遞給中間件:
~~~php
<?php
namespace App\Http\Middleware;
use Closure;
class CheckRole
{
/**
* 處理傳入的參數。
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $role
* @return mixed
*/
public function handle($request, Closure $next, $role)
{
if (! $request->user()->hasRole($role)) {
// 重定向
}
return $next($request);
}
}
~~~
定義路由時通過一個`:`來隔開中間件名稱和參數來指定中間件參數。多個參數就使用逗號分隔:
~~~php
Route::put('post/{id}', function ($id) {
//
})->middleware('role:editor');
~~~
## Terminable 中間件
有時中間件可能需要在 HTTP 響應構建完畢之后處理一些工作。比如,Laravel內置的「session」中間件會在響應構建完畢之后將會話數據寫入存儲器中。如果你在中間件中定義了一個`terminate`方法,則會在響應構建完畢后,準備發往瀏覽器之前自動調用:
~~~php
<?php
namespace Illuminate\Session\Middleware;
use Closure;
class StartSession
{
public function handle($request, Closure $next)
{
return $next($request);
}
public function terminate($request, $response)
{
// 儲存 session 數據...
}
}
~~~
`terminate`方法應該同時接收請求和響應。定義了這個中間件之后,別忘了將它添加到路由列表或者`app/Http/Kernel.php`文件的全局中間件中。
在你的中間件上調用`terminate`方法時,Laravel 會從[服務容器]中解析出一個新的中間件實例。如果你想在相同的中間件實例中同時調用`handle`和`terminate`方法,請使用容器的`singleton`方法向容器中注冊中間件。