TP5.1中間件的使用

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類的實例。閉包函數作爲函數參數,可以看做是把這段函數代碼傳給函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章