Tp5.1也引入了中間件的功能
中間件使用
1.定義中間件類
框架可以使用它命令或者自己在application/http/middleware
目錄下面生成一個Check
中間件,格式一定要如下:必須有handle方法,第一個參數必須是Request對象,第二個是閉包。
class Check
{
public function handle(Request $request, \Closure $next)
{
$request->name = 'aaaaa';
return $next($request); //執行$next閉包函數,並且把這個閉包函數的結果return。
}
}
中間件的handle方法就是執行自己的代碼,然後調用執行下一個中間件的代碼。 結合後面的解析,我們知道$next就是通過Middleware::resolve()返回的閉包函數。這個閉包函數的執行就是從中間件隊列中彈出一箇中間件,然後通過call_fun_array()執行這個中間件的handle方法。所以中間件的return其實是執行最後一箇中間件的return結果。
後置中間件
class After
{
public function handle(Request $request, \Closure $next)
{
$response = $next($request);
//todo 執行後置中間件的代碼
return $response;
}
}
2.註冊中間件
1.在路由上註冊(只有這個路由纔會走中間件)
傳'Check',會自動在application/http/middleware目錄下找到Check類,也可以傳完整類名
Route::get('test', 'Demo/dbTest')->middleware('Check');
2.在對應的目錄模塊上註冊 (這個目錄下的控制器方法都會走中間件)tp5.1.8+
例如在application目錄下新建middleware.php,則全局都會走這個中間件,如果在application\admin目錄下建middleware.php,則只有admin模塊下的控制器纔會走中間件。
return [
'Check', //沒有寫類全名,默認在application/http/middleware下找中間件
];
3.在對應控制器註冊中間件(只有這個控制器的方法才走)tp5.1.17+
控制器需要繼承系統的think\Controller
類,然後在控制器中定義middleware
屬性,例如
namespace app\index\controller;
use think\Controller;
class Index extends Controller
{
protected $middleware = ['check'];
public function test(){}
}
中間件原理 (Middleware類)
think\Middleware類重要屬性
$queue = [],存放中間件的數組
$config = [],存放中間件的配置信息
think\Middleware類重要方法
1.buildMiddleware($middleware, $type = 'route'),$middleware可以是‘check’,check的類名,或者閉包。
//如果$middleware是閉包
if ($middleware instanceof \Closure) {
return [$middleware, isset($param) ? $param : null];
}
//如果是'check',會去拼接到全類名
return [[$this->app->make($middleware), 'handle'], isset($param) ? $param : null];
2.add()把解析好的中間件掛到屬性$queue. 一箇中間件元素的格式是[['中間件對象',‘handle’], $param];或者[閉包,$param];
$middleware = $this->buildMiddleware($middleware, $type);
if ($middleware) {
$this->queue[$type][] = $middleware;
}
並且以$type區分開,$type默認是‘route’;
3.dispatch(Request $request, $type = 'route')中間件調度,就是執行resolve返回的閉包函數
//就是按照一定順序執行中間件
public function dispatch(Request $request, $type = 'route')
{
return call_user_func($this->resolve($type), $request);
}
4.resolve($type),返回閉包函數,這個閉包函數就是handle方法中的$next。
protected function resolve($type = 'route')
{
return function (Request $request) use ($type) {
$middleware = array_shift($this->queue[$type]);
list($call, $param) = $middleware;
try {
$response = call_user_func_array($call, [$request, $this->resolve($type), $param]);
} catch (HttpResponseException $exception) {
$response = $exception->getResponse();
}
if (!$response instanceof Response) {
throw new LogicException('The middleware must return Response instance');
}
return $response;
};
}
如何執行中間件的handle方法的,call_user_func_array(),最終得到某個中間件的返回值。並且返回值必須是Response對象,不然會包錯。
//這個就是執行中間件的handle方法,而handler方法最後又會執行resolve返回的閉包方法,
//相當於又得到另外一個$call再次執行下面這段代碼。所以這是一個遞歸的過程。
//一直循環到某個中間件,沒有再調用$this->resolve($type)這個閉包參數。即在handle方法中沒有執行$next($resquest);而是直接返回Response對象。然後一層一層往上歸回來。
$response = call_user_func_array($call, [$request, $this->resolve($type), $param]);
$call就是[中間件對象,'handle']
$request就是handle的第一個參數
$this->resolve($type),就是handle的第二個參數,是一個閉包
$param是第三個參數
$response就是handle的返回值
TP5.1是如何使用Middleware類庫實現中間件的功能的
1.初始化模塊init(),會加載模塊目錄下的middleware.php文件
// 加載中間件文件,import沒有傳type,默認是route
if (is_file($path . 'middleware.php')) {
$middleware = include $path . 'middleware.php';
if (is_array($middleware)) {
$this->middleware->import($middleware);
}
}
初始化模塊方法init(),在系統初始化的時候調用一次,那時候還沒有解析路由,默認模塊就是Application模塊,然後根據訪問路徑得到模塊,然後會再次調用,表示初始化對應模塊。所以一個進程只會加載Application/和訪問模塊的下的middleware.php。
2. 路由文件導入,include rute目錄下的文件。
//get方法返回的是RuleItem實例,我們知道一條路由規則生成一個RuleItem實例。
Route::get('test', 'Demo/test')->middleware('Check');
middleware方法會把Check放到屬性option中,在創建調度對象Module實例前,會把中間件加進去
// 添加域名中間件
if (!empty($this->option['middleware'])) {
Container::get('middleware')->import($this->option['middleware']);
unset($this->option['middleware']);
}
3.調度器執行方法,作爲一個匿名函數,加入到中間件隊列中
//通過路由解析後,最終通過$dispatch->run()其實就是執行我們的控制器方法,然後返回Response對象。
//正常路由解析後,$data爲null。
$this->middleware->add(function (Request $request, $next) use ($dispatch, $data) {
return is_null($data) ? $dispatch->run() : $data;
});
//中間件調度,把註冊好的中間件按順序執行,直到執行到返回Response對象的那個中間件
$response = $this->middleware->dispatch($this->request);
上面三種方式,分別把middleware.php的中間件,自定義路由的中間件,控制器的執行分別加入到中間件隊列。
PHP的Closure類
我們創建一個閉包函數,var_dump這個閉包函數,發現他是一個Closure類。在PHP中定義一個閉包函數其實就是一個Closure類的實例。閉包函數作爲函數參數,可以看做是把這段函數代碼傳給函數。