1. 程式人生 > 其它 >[譯]Laravel 5.0 之 Middleware (Filter-Style)

[譯]Laravel 5.0 之 Middleware (Filter-Style)

本文譯自 Matt Stauffer 的系列文章.


如果你有閱讀我之前的 Laravel 5.0 系列文章,你可能已經注意到路由過濾器(route filters)的變化:它們先是移到了單獨的目錄和類結構,然後就莫名其妙地消失了。你可能還留意到在原本應該是路由過濾器的地方,變成了對 Middleware 的引用。

實際上給 Laravel 應用新增自定義的 Middleware 在以前的版本中就有了。 Chris Fidao 的 HTTP Middleware in Laravel 4.1 對 middleware 做了全面的介紹,包括 middleware 在 Laravel 4.1 版本中的工作機制。

提示:過濾器在 Laravel 核心程式碼中依然存在,所以你依然可以使用。但是在需要對路由進行修飾時,更推薦採用的是 middleware.

Middleware 是什麼?

Middleware 有點不好理解。你可以先看看下面這張從 StackPHP 借來的圖。假設你的應用——路由,控制器,業務邏輯——是圖中的綠色部分,從圖中可以清晰地看到,使用者請求先經由多箇中間層才能到達你的應用,然後再經由更多的中間層進行處理。每個特定的中間層都可以在應用邏輯之前、之後進行處理,或者同時在應用邏輯之前和之後進行處理。

這就是 middleware 實現修飾模式的工作方式:它捕獲請求,做一些處理,然後把處理後的請求物件返回給下一個堆疊層。

Laravel 預設使用 middleware 來處理加密/解密和 cookies 佇列、讀取和寫入 sessions, 但除此之外你還可以用 middleware 來向請求/響應環中加入你需要的任何一種操作層。比如速率限制、自定義請求解析等。

怎麼編寫 middleware?

通過執行 artisan 命令:

$ php artisan make:middleware MyMiddleware

這條命令會生成一個簡單的 middleware 檔案,程式碼如下:

namespace AppHttpMiddleware;

use Closure;
use IlluminateContractsRoutingMiddleware;

class MyMiddleware implements Middleware {

    /**
    * 處理輸入請求
    *
    * @param  IlluminateHttpRequest  $request
    * @param  Closure  $next
    * @return mixed
    */
    public function handle($request, Closure $next)
    {
        //
    }

}

如你所見,所有 middleware 的基礎是 handle 方法,它接受兩個引數:

  • $request: Illuminate Request 物件
  • $next: Closure(匿名函式), 該函式把 request 物件傳遞給後續的 middleware.

還記得之前那個荒謬的“阻止奇數埠請求的 ValidatesWhenResolved 物件”的例子嗎?很好,現在再把它拿過來,改成 middleware 風格的:

namespace AppHttpMiddleware;

use Closure;
use IlluminateContractsRoutingMiddleware;

class MyMiddleware implements Middleware {

    /**
     * Handle an incoming request.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        // Test for an even vs. odd remote port
        if (($request->server->get('REMOTE_PORT') / 2) % 2 > 0)
        {
            throw new Exception("WE DON'T LIKE ODD REMOTE PORTS");
        }

        return $next($request);
    }

}

如何使用 Middleware?

在 Laravel 5 中有兩種主要的方法可以繫結 middleware. 兩種方法都從 AppHttpKernel 開始。

你可能注意到了,新的 Kernel 類有兩個屬性: $middleware$routeMiddleware. 這兩個屬性都是 middleware 為元素的陣列。在 $middleware 中的 middleware 會在每次請求時執行,而 $routeMiddleware 中的 middleware 必須被啟用才會執行。

在本文寫作時,$middleware 中預設包含的有五個 middlewares:

protected $middleware = [
        'IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode',
        'IlluminateCookieMiddlewareEncryptCookies',
        'IlluminateCookieMiddlewareAddQueuedCookiesToResponse',
        'IlluminateSessionMiddlewareStartSession',
        'IlluminateViewMiddlewareShareErrorsFromSession',
        'IlluminateFoundationHttpMiddlewareVerifyCsrfToken',
    ];

此外還有三個可選的 middlewares:

protected $routeMiddleware = [
        'auth' = 'AppHttpMiddlewareAuthenticate',
        'auth.basic' => 'IlluminateAuthMiddlewareAuthenticateWithBasicAuth',
        'guest' => 'AppHttpMiddlewareRedirectIfAuthenticated',
    ];

從上面的程式碼中可以看到, 在新版本中預設可用的可選路由 middleware 與舊版本中預設可用的可選過濾器(filter)是一樣的,除了一個例外——CSRF 表單保護在新版本中預設是對所有路由預設啟用的——這非常重要。

在每次請求時執行 middleware

下面,我們從讓自己的 middleware 在每次請求時都執行開始。很簡單,只要把它加到 $middleware 陣列中:

protected $middleware = [
        'AppHttpMiddlewareMyMiddleware', // 這是自定義的
        'IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode',
        'IlluminateCookieMiddlewareEncryptCookies',
        'IlluminateCookieMiddlewareAddQueuedCookiesToResponse',
        'IlluminateSessionMiddlewareStartSession',
        'IlluminateViewMiddlewareShareErrorsFromSession',
        'IlluminateFoundationHttpMiddlewareVerifyCsrfToken',
    ];

現在每次請求時它都會被執行了。

在特定的路由上執行 middleware

OK, 接下來把我們的自定義 middleware 移到可選堆疊,要給它指定一個 key:

protected $routeMiddleware = [
        'auth' = 'AppHttpMiddlewareAuthenticate',
        'auth.basic' => 'IlluminateAuthMiddlewareAuthenticateWithBasicAuth',
        'guest' => 'AppHttpMiddlewareRedirectIfAuthenticated',
        'absurd' => 'AppHttpMiddlewareMyMiddleware', // 這是自定義的
    ];

現在我們可以在 routes.php 檔案中或者在基礎控制器(BaseController)中用 $this->middleware() 方法來呼叫自定義的 middleware 了。

在控制器中呼叫:

...
use IlluminateRoutingController;

class AwesomeController extends Controller {

    public function __construct()
    {
        $this->middleware('csrf');
        $this->middleware('auth', ['only' => 'update'])
    }

}

在 routes.php 檔案中呼叫:

// Routes.php

// Single route
$router->get("/awesome/sauce", "AwesomeController@sauce", ['middleware' => 'auth']);

// Route group
$router->group(['middleware' => 'auth'], function() {
    // lots of routes that require auth middleware
});

如何通過 middleware 實現 beforeafter 過濾器?

我花了一些時間來研究這個問題,但 Taylor(譯註:Laravel 框架作者) 指出了 "before" middleware 和 "after" middleware 的區別在於 middleware 的行為是發生在它呼叫 $next() 之前還是之後:

...
class BeforeMiddleware implements Middleware {

    public function handle($request, Closure $next)
    {
        // Do Stuff
        return $next($request);
    }

}
...
class AfterMiddleware implements Middleware {

    public function handle($request, Closure $next)
    {
        $response = $next($request);
        // Do stuff
        return $response;
    }

}

如你所見, "before" middleware 先執行操作,然後把請求向堆疊傳遞。而 "after" middleware 是先呼叫 $next() 方法讓請求被堆疊處理,之後再對它執行操作。

寫在最後

如果你還不熟悉 middleware, 你的大腦可能會被它的概念糾纏一會兒。從我們考慮控制器及路由請求的常規思維方式的角度來說,過濾器(filter)會更容易理解一些。但 middleware ——這種在堆疊中傳遞單一請求,讓它一點一點被處理的概念——其實更整潔、更簡單、更靈活。

不僅如此,middleware 不只是在 Laravel 應用中處理請求的額外的一種強大而高效的手段,它在其它方面也能有很好的表現。Laravel 5.0 中的 middleware 語法與 StackPHP 的語法不完全相容。但如果你採用基於 middleware 的架構來組織你的請求/響應堆疊,這是在依賴關係分離方向上的一個進步。而且要修改一個 Laravel middleware 使之可以在單獨的 StackPHP 風格的語法下工作,也花不了多少工夫。

有任何意見,歡迎留下評論或者通過 Twitter 與 @stauffermatt 進行交流。