您好,登錄后才能下訂單哦!
這篇文章主要介紹了ThinkPHP6.0如何實現管道模式與中間件,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
說明
ThinkPHP 6.0 RC5 開始使用了管道模式來實現中間件,比起之前版本的實現更加簡潔、有序。這篇文章對其實現細節進行分析。
首先我們從入口文件 public/index.php 開始,$http = (new App())->http;
獲得一個 http 類的實例后調用它的 run 方法:$response = $http->run();,然后它的 run 方法又調用了 runWithRequest 方法:
protected function runWithRequest(Request $request) { . . . return $this->app->middleware->pipeline() ->send($request) ->then(function ($request) { return $this->dispatchToRoute($request); }); }
中間件的執行都在最后的 return 語句中。
pipeline、through、send 方法
$this->app->middleware->pipeline() 的 pipeline 方法: public function pipeline(string $type = 'global') { return (new Pipeline()) // array_map將所有中間件轉換成閉包,閉包的特點: // 1. 傳入參數:$request,請求實例; $next,一個閉包 // 2. 返回一個Response實例 ->through(array_map(function ($middleware) { return function ($request, $next) use ($middleware) { list($call, $param) = $middleware; if (is_array($call) && is_string($call[0])) { $call = [$this->app->make($call[0]), $call[1]]; } // 該語句執行中間件類實例的handle方法,傳入的參數是外部傳進來的$request和$next // 還有一個$param是中間件接收的參數 $response = call_user_func($call, $request, $next, $param); if (!$response instanceof Response) { throw new LogicException('The middleware must return Response instance'); } return $response; }; // 將中間件排序 }, $this->sortMiddleware($this->queue[$type] ?? []))) ->whenException([$this, 'handleException']); }
through 方法代碼:
public function through($pipes) { $this->pipes = is_array($pipes) ? $pipes : func_get_args(); return $this; }
前面調用 through 是傳入的 array_map(...) 把中間件封裝為一個個閉包,through 則是把這些閉包保存在 Pipeline 類的 $pipes 屬性中。
PHP 的 array_map 方法簽名:
array_map ( callable $callback , array $array1 [, array $... ] ) : array
$callback 迭代作用于每一個 $array 的元素,返回新的值。所以,最后得到 $pipes 中每個閉包的形式特征是這樣的(偽代碼):
function ($request, $next) { $response = handle($request, $next, $param); return $response; }
該閉包接收兩個參數,一個是請求實例,一個是回調用函數,handle 方法處理后得到相應并返回。
through 返回一個 Pipeline 類的實例,接著調用 send 方法:
public function send($passable) { $this->passable = $passable; return $this; }
該方法很簡單,只是將傳入的請求實例保存在 $passable 成員變量,最后同樣返回 Pipeline 類的實例,這樣就可以鏈式調用 Pipeline 類的其他方法。
then,carry 方法
send 方法之后,接著調用 then 方法:
return $this->app->middleware->pipeline() ->send($request) ->then(function ($request) { return $this->dispatchToRoute($request); });
這里的 then 接收一個閉包作為參數,這個閉包實際上包含了控制器操作的執行代碼。
then 方法代碼:
public function then(Closure $destination) { $pipeline = array_reduce( //用于迭代的數組(中間件閉包),這里將其倒序 array_reverse($this->pipes), // array_reduce需要的回調函數 $this->carry(), //這里是迭代的初始值 function ($passable) use ($destination) { try { return $destination($passable); } catch (Throwable | Exception $e) { return $this->handleException($passable, $e); } }); return $pipeline($this->passable); }
carry 代碼:
protected function carry() { // 1. $stack 上次迭代得到的值,如果是第一次迭代,其值是后面的「初始值 // 2. $pipe 本次迭代的值 return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { try { return $pipe($passable, $stack); } catch (Throwable | Exception $e) { return $this->handleException($passable, $e); } }; }; }
為了更方便分析原理,我們把 carry 方法內聯到 then 中去,并去掉錯誤捕獲的代碼,得到:
public function then(Closure $destination) { $pipeline = array_reduce( array_reverse($this->pipes), function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { return $pipe($passable, $stack); }; }, function ($passable) use ($destination) { return $destination($passable); }); return $pipeline($this->passable); }
這里關鍵是理解 array_reduce 以及 $pipeline($this->passable) 的執行過程,這兩個過程可以類比于「包洋蔥」和「剝洋蔥」的過程。
array_reduce 第一次迭代,$stack 初始值為:
(A)
function ($passable) use ($destination) { return $destination($passable); });
回調函數的返回值為:
(B)
function ($passable) use ($stack, $pipe) { return $pipe($passable, $stack); };
將 A 代入 B 可以得到第一次迭代之后的 $stack 的值:
(C)
function ($passable) use ($stack, $pipe) { return $pipe($passable, function ($passable) use ($destination) { return $destination($passable); }) ); };
第二次迭代,同理,將 C 代入 B 可得:
(D)
// 偽代碼 // 每一層的$pipe都代表一個中間件閉包 function ($passable) use ($stack, $pipe) { return $pipe($passable, //倒數第二層中間件 function ($passable) use ($stack, $pipe) { return $pipe($passable, //倒數第一層中間件 function ($passable) use ($destination) { return $destination($passable); //包含控制器操作的閉包 }) ); }; ); };
以此類推,有多少個中間件,就代入多少次,最后一次得到 $stack 就返回給 $pipeline。由于前面對中間件閉包進行了倒序,排在前面的閉包被包裹在更里層,所以倒序后的閉包越是后面的在外面,從正序來看,則變成越前面的中間件在最外層。
層層包裹好閉包后,我們得到了一個類似洋蔥結構的「超級」閉包 D,該閉包的結構如上面的代碼注釋所示。最后把 $request 對象傳給這個閉包,執行它:$pipeline($this->passable);,由此開啟一個類似剝洋蔥的過程,接下來我們看看這洋蔥是怎么剝開的。
剝洋蔥過程分析
array_map(...) 把每一個中間件類加工成一個類似這種結構的閉包:
function ($request, $next) { $response = handle($request, $next, $param); return $response; }
其中 handle 是中間件中的入口,其結構特點是這樣的:
public function handle($request, $next, $param) { // do sth ------ M1-1 / M2-1 $response = $next($request); // do sth ------ M1-2 / M2-2 return $response; }
我們上面的「洋蔥」一共只有兩層,也就是有兩層中間件的閉包,假設 M1-1,M1-2 分別是第一個中間件 handle 方法的前置和后值操作點位,第二個中間件同理,是 M2-1,M2-2。現在,讓程序執行 $pipeline($this->passable),展開來看,也就是執行:
// 偽代碼 function ($passable) use ($stack, $pipe) { return $pipe($passable, function ($passable) use ($stack, $pipe) { return $pipe($passable, function ($passable) use ($destination) { return $destination($passable); }) ); }; ); }($this->passable)
此時,程序要求從:
return $pipe($passable, function ($passable) use ($stack, $pipe) { return $pipe($passable, function ($passable) use ($destination) { return $destination($passable); }) ); }; );
返回值,也就是要執行第一個中間件閉包,$passable 對應 handle 方法的 $request 參數,而下一層閉包
function ($passable) use ($stack, $pipe) { return $pipe($passable, function ($passable) use ($destination) { return $destination($passable); }) ); }
則對應 handle 方法的 $next 參數。
要執行第一個閉包,即要執行第一個閉包的 handle 方法,其過程是:首先執行 M1-1 點位的代碼,即前置操作,然后執行 $response = $next($request);,這時程序進入執行下一個閉包,$next($request) 展開來,也就是:
function ($passable) use ($stack, $pipe) { return $pipe($passable, function ($passable) use ($destination) { return $destination($passable); }) ); }($request)
依次類推,執行該閉包,即執行第二個中間件的 handle 方法,此時,先執行 M2-1 點位,然后執行 $response = $next($request),此時的 $next 閉包是:
function ($passable) use ($destination) { return $destination($passable); })
屬于洋蔥之芯 —— 最里面的一層,也就是包含控制器操作的閉包,展開來看:
function ($passable) use ($destination) { return $destination($passable); })($request)
最終,我們從 return $destination($passable) 中返回一個 Response 類的實例,也就是,第二層的 $response = $next($request) 語句成功得到了結果,接著執行下面的語句,也就是 M2-2 點位,最后第二層閉包返回結果,也就是第一層閉包的 $response = $next($request) 語句成功得到了結果,然后執行這一層閉包該語句后面的語句,即 M1-2 點位,該點位之后,第一層閉包也成功返回結果,于是,then 方法最終得到了返回結果。
整個過程過來,程序經過的點位順序是這樣的:M1-1→M2-1→控制器操作→M2-2→M1-2→返回結果。
感謝你能夠認真閱讀完這篇文章,希望小編分享的“ThinkPHP6.0如何實現管道模式與中間件”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。