Web 程序,簡明扼要地說,就是個吃網絡請求,並吐出響應的東西。 MVC 結構的框架,使用控制器來處理請求,並生成響應。 而 Laravel 框架,還使用流水線(Pipeline)來處理 傳入控制器前的請求,和控制器返回的響應。 流水線上的工人,是一個個中間件(Middleware)。 它們就像樂高積木,負責單一的行爲。 或負責控制 Cookie,管理 Session; 或負責控制用戶的登錄驗證;又或者控制訪問的頻率。 通過靈活的中間件註冊機制,我們可方便地對這些中間件組裝和復用。

運行機制

   如果我們馬上打開 Pipeline.php 一定會一頭霧水,滿腦子退堂鼓聲。 所以我們得先拋開具體的實現,從一個典型的中間件上,窺一窺流水線的運行機制。

class MyMiddleware
{
	public function handle( \Illuminate\Http\Request$request, callable$next )
	:\Illuminate\Http\Request
	{
		// do something to $request
		
		$response= $next( $request );
		
		// do something to $response
		
		return $response;
	}
}

這是一個典型的中間件類,其 handle 方法接收一個請求和一個叫“下一步”的函數, 並返回一個響應。

Laravel 允許我們的中間件返回 mixed,但筆者不推薦這麼做。 建議的做法是只允許其返回響應對象,並且是“下一步”所返回的那個, 而不要新建響應對象。遵循這樣的規範,可避免諸如 header 丟失這樣的意外發生。

這個“下一步”函數,即執行流水線上的下一步,可能是執行下一個中間件, 也可能是執行最終的控制器。總之也是個吃請求,吐響應的東西。 從代碼的結構看,流水線的上下游,是嵌套的關係。可以形象地表示爲:

<Middleware-0>
	<Middleware-1>
		<Middleware-2>
			<Middleware-3>
				<Controller />
			</Middleware-3>
		</Middleware-2>
	</Middleware-1>
</Middleware-0>

一層層的中間件,就像一層層的門衛。 越上游的中間件,就可以越早地接觸請求,也能越後手地處理響應,總之權利越大。 這就是流水線的運行機制,層層向內,再層層向外。

中間件的註冊

   註冊中間件有三種方式,其零是全局註冊,其一是在路由上註冊,其二是在控制器中註冊。 不同的情況,應當採取不同的方式。全局註冊,顯而易見,適合那些統攬全局的中間件。 例如 Laravel 默認的系統維護中間件,當系統處於維護狀態時,它將攔截所有請求。 再如強制 HTTPS 的中間件,攔截所有 HTTP 請求,跳轉到對應的 HTTPS 請求。 而那些作用於一定範圍的中間件,則應當在路由上註冊。 例如登錄判斷中間件,應當註冊於一個路由組上,這個路由組包含了所有用戶私有的路由。 而不需要登錄即可訪問的路由,例如登錄頁面,應放在此組之外。 而那些應用於具體個例的中間件,則要具體分析。屬於路由的,要在路由上註冊。 耦合於控制器的,則應在控制器上註冊。我們可以如此假設: 若該控制器同時註冊在多個路由上,此中間件是否都必須要註冊。 例如一個上傳頭像的控制器,上面要註冊一個內容讀取中間件, 以讀取上傳的內容,並彌合 PHP 對 POST 和 PUT 請求的差異; 還要註冊一個訪問頻率限制中間件,來避免頻繁上傳耗費過多服務器帶寬。 我們假設它同時註冊在用戶和管理員的路由組下。 對於內容讀取中間件,不論在哪上傳,必然都需要,因此要在控制器中註冊。 而對於頻率限制中間件,則兩處可能不同,對管理員的限制可能更寬鬆。 因此要註冊在路由上。總地來說,只有少部分中間件,應註冊在全局或控制器上, 而大部分都適合註冊在路由上。

控制中心

   每個 Laravel 項目中都有一個 \App\Http\Kernel 類, 這便是中間件的控制中心。 全局中間件在這裏註冊;中間件權利順序由這裏掌管; 在這裏,你能爲中間件起一個簡短的別名, 還能將若干中間件打包成一個組,方便一起註冊。

namespace App\Http;

class Kernel extends \Illuminate\Foundation\Http\Kernel
{
	protected $middleware= [
		// 在此註冊全局中間件
	];
	
	protected $middlewarePriority= [
		// 在此定中間件的義優先級,越靠前的中間件權利越大
	];
	
	protected $routeMiddleware= [
		// 在此定義中間件的別名($routeMiddleware 這個名字不太合適,別名可不僅在路由中使用)
	];
	
	protected $middlewareGroups= [
		// 在此定義中間件組
		'group_name'=> [
			// 組內中間件
		],
	];
}

一個初始化的 Laravel 項目中,$routeMiddleware 裏, 官方爲我們準備了一些常用的中間件。我們大可奉行拿來主義。 而在 $middlewareGroups 中,則預設了 webapi 兩個組。 它們在 \App\Providers\RouteServiceProvider 中使用, 分別被註冊在各自的路由組上。這只是官方提供的一個範式, 根據項目結構不同,我們大可大刀闊斧地改動這個文件, 而不必拘泥於 web 與 api 二分天下的固有結構。

小推薦

   末了,推薦一個包 Laroute, 是一個用於 Laravel 的路由工具,同時也是一種路由的語言。可以大幅降低路由文件的語法噪音。 作者即是“王婆”本人了。讓我們感受一下 Laroute 帶來的清爽:

// Routing with PHP
Route::group( [ 'middleware'=>[ 'middleware0', 'middleware1:param', ], ],function(){
	Route::get( '/', 'Home@home' )
		->middleware( 'middleware2' )
		->middleware( 'middleware3:1,60' )
		->name( 'home' )
	;
} );
# Routing with Laroute
: >middleware0 >middleware1:param
	GET / home
		>middleware2
		>middleware3:1,60
		>>Home@home