【Laravel-海贼王系列】第七章,Pipeline 类解析
Pipeline
Laravel 的中間件是通過管道類來實現的。
通過內核處理請求的過程中管道的作用來解析管道類!
protected function sendRequestThroughRouter($request){$this->app->instance('request', $request);Facade::clearResolvedInstance('request');$this->bootstrap();return (new Pipeline($this->app)) //這是個 Illuminate\Routing\Pipeline 對象,繼承了 Illuminate\Pipeline\Pipeline 對象。->send($request) // 調用 Illuminate\Pipeline\Pipeline 的 send() 方法傳入 $request 對象->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) // 傳入需要經過的中間件數組->then($this->dispatchToRouter());// 傳入最后執行的閉包并且運行管道} 復制代碼接下來我們看看這段代碼是如何讓請求通過所有的中間件之后返回的。
代碼調用追蹤
約定 (new Pipeline($this->app)) 下面統稱 $pipe
$pipe->send($request) // 將 $request 對象賦值給 $pipe->passable
$pipe->pipes 的賦值
4.$pipe->then($this->dispatchToRouter()); 這里是執行父類的 then() 方法
public function then(Closure $destination){$pipeline = array_reduce(array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination));return $pipeline($this->passable);} 復制代碼? array_reverse($this->pipes),就是將剛才存入的中間件順序反轉。
? $this->carry() 這里的 $this 指向的對象是 Illuminate\Routing\Pipeline 對象因此調用 carry() 方法是自身的。
? $this->prepareDestination($destination) 返回一個閉包
return function ($passable) use ($destination) {return $destination($passable);}; 復制代碼接著開始看
$pipeline = array_reduce(array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)); 復制代碼這段代碼可以改造成容易讀的方式
$cb = $this->carry();$stack = $this->prepareDestination($destination);foreach (array_reverse($this->pipes) as $pipe) {$stack = $cb($stack,$pipe);}$pipeline = $stack; 復制代碼先獲取一個閉包,然后獲取第一個閉包參數 $stack ,之后遍歷 pipes 數組來進行迭代,每次迭代會更新下次迭代的 $stack 變量,等迭代完成之后將 $stack 賦值給 $pipeline.
所以我們只要關心最后 $pipeline 拿到的是一個什么東西 那么這里就要解析 $this->carry() 每次執行之后返回的是什么,下面是執行調用的方法。
protected function carry(){return function ($stack, $pipe) {return function ($passable) use ($stack, $pipe) {try {$slice = parent::carry();$callable = $slice($stack, $pipe);return $callable($passable);} catch (Exception $e) {return $this->handleException($passable, $e);} catch (Throwable $e) {return $this->handleException($passable, new FatalThrowableError($e));}};};} 復制代碼這里其實每次執行返回的就是個新閉包,同時 $stack,$pipe 的值也隨著調用存入閉包。為了方便我聲明下變量
$cb = function ($passable) use ($stack, $pipe) {try {$slice = parent::carry();$callable = $slice($stack, $pipe);return $callable($passable);} catch (Exception $e) {return $this->handleException($passable, $e);} catch (Throwable $e) {return $this->handleException($passable, new FatalThrowableError($e));}}; 復制代碼所以上面 $cb 的值就是 $this->carry() 執行后返回的閉包就像洋蔥一樣,我們來看封裝過程。
第一次封裝 $stack1 = $cb($passable) use ($this->prepareDestination($destination),'App\Http\Middleware\TrustProxies') 第二次封裝 $stack2 = $cb($passable) use ($stack1,'Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull') 第三次封裝 $stack3 = $cb($passable) use ($stack2,'App\Http\Middleware\TrimStrings') 第四次封裝 $stack4 = $cb($passable) use ($stack3,'Illuminate\Foundation\Http\Middleware\ValidatePostSize') 第五次封裝 $stack5 = $cb($passable) use ($stack4,'App\Http\Middleware\CheckForMaintenanceMode') 復制代碼最后 $pipeline 對象實際就是 $stack5。
看到這里我們獲取了一個層層封裝的閉包,同時我們也看出為什么中間件的順序先反轉了,因為執行的時候是從 $stack5 開始的!那么下一步就是看看如何執行了。
return $pipeline($this->passable); 復制代碼在遞歸完成之后我們獲得了一個 $pipeline 對象, 此時我們觸發這個閉包,后面就是連鎖反應!這里我用 $stack5 來代替 $pipeline 方便理解。 首先執行
$stack5($this->passable,'App\Http\Middleware\CheckForMaintenanceMode') 復制代碼這段代碼是一個起點,也就是點燃整個連鎖反應的開始,我們來追蹤下去會回到 $cb 這個閉包的邏輯,
$cb = function ($passable) use ($stack, $pipe) {try {$slice = parent::carry();$callable = $slice($stack, $pipe);return $callable($passable);} catch (Exception $e) {return $this->handleException($passable, $e);} catch (Throwable $e) {return $this->handleException($passable, new FatalThrowableError($e));}}; 復制代碼這里最終還是調用了 parent::carry(), 執行到了最里層的函數。
protected function carry(){return function ($stack, $pipe) {return function ($passable) use ($stack, $pipe) {if (is_callable($pipe)) {return $pipe($passable, $stack);} elseif (!is_object($pipe)) {[$name, $parameters] = $this->parsePipeString($pipe);$pipe = $this->getContainer()->make($name);$parameters = array_merge([$passable, $stack], $parameters);} else {$parameters = [$passable, $stack];}$response = method_exists($pipe, $this->method)? $pipe->{$this->method}(...$parameters): $pipe(...$parameters);return $response instanceof Responsable? $response->toResponse($this->container->make(Request::class)): $response;};};} 復制代碼到這里我們已經進入最后的堡壘,由于傳入的 $pipe 是中間件的名稱,不是閉包所以進入 elseif 中開始執行。 第一次執行:
$stack5 = $cb($passable) use ($stack4,'App\Http\Middleware\CheckForMaintenanceMode') 復制代碼function ($passable) use ($stack, $pipe) {if (is_callable($pipe)) {return $pipe($passable, $stack);} elseif (!is_object($pipe)) {// 進入這里開始執行[$name, $parameters] = $this->parsePipeString($pipe);$pipe = $this->getContainer()->make($name); // 從通過Application對象從容器中生產對應的類,這里不拓展了,就是應用了容器的特性來生產類。$parameters = array_merge([$passable, $stack], $parameters); // 這里非常重要,將 $passable (就是開始的 $request 對象) 和 $stack (就是最近一次調用的$stack4) 合并成數組} else {$parameters = [$passable, $stack];}$response = method_exists($pipe, $this->method)? $pipe->{$this->method}(...$parameters): $pipe(...$parameters); // 調用中間件中$pipe->handle($request, $stack4)return $response instanceof Responsable? $response->toResponse($this->container->make(Request::class)): $response;}; 復制代碼分析完上面并沒有完成,最后代碼運行到
$this->method = 'handle'; 默認配置,可以通過 $this->via($method) 來修改。$response = method_exists($pipe, $this->method)? $pipe->{$this->method}(...$parameters): $pipe(...$parameters); // ...$parameters 解構數組參數實際調用 $pipe->handle($request, $stack4) 復制代碼此時只是調用一次閉包,那么之前封裝了那么多層都怎么辦呢?
接下來我們分析 CheckForMaintenanceMode 中間件的 handle($request, Closure $next) 方法。
public function handle($request, Closure $next){if ($this->app->isDownForMaintenance()) {$data = json_decode(file_get_contents($this->app->storagePath().'/framework/down'), true);if (isset($data['allowed']) && IpUtils::checkIp($request->ip(), (array) $data['allowed'])) {return $next($request);}if ($this->inExceptArray($request)) {return $next($request);}throw new MaintenanceModeException($data['time'], $data['retry'], $data['message']);}return $next($request);} 復制代碼return $next($request); 這句話點亮了一切
實際調用了 $stack4($request) , 我們來看看當時 $stack4 這個閉包里面是啥
$stack4 = $cb($passable) use ($stack3,'Illuminate\Foundation\Http\Middleware\ValidatePostSize') 復制代碼是不是和 $stack5 有點像, 直到這里形成了遞歸, 同時解答了為什么中間件的格式要按照文檔上面說用。
回到最初的封裝
第一次封裝 $stack1 = $cb($passable) use ($this->prepareDestination($destination),'App\Http\Middleware\TrustProxies') 第二次封裝 $stack2 = $cb($passable) use ($stack1,'Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull') 第三次封裝 $stack3 = $cb($passable) use ($stack2,'App\Http\Middleware\TrimStrings') 第四次封裝 $stack4 = $cb($passable) use ($stack3,'Illuminate\Foundation\Http\Middleware\ValidatePostSize') 第五次封裝 $stack5 = $cb($passable) use ($stack4,'App\Http\Middleware\CheckForMaintenanceMode') 復制代碼我們的調用鏈就變成了
$stack5 = $cb($passable) use ($stack4,'App\Http\Middleware\CheckForMaintenanceMode') $stack4 = $cb($passable) use ($stack3,'Illuminate\Foundation\Http\Middleware\ValidatePostSize') $stack3 = $cb($passable) use ($stack2,'App\Http\Middleware\TrimStrings') $stack2 = $cb($passable) use ($stack1,'Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull') $stack1 = $cb($passable) use ($this->prepareDestination($destination),'App\Http\Middleware\TrustProxies') 復制代碼最后執行
$this->prepareDestination($destination)$destination = $this->dispatchToRouter();return function ($passable) use ($destination) {return $destination($passable); }; // 返回一個 $response 對象 ...復制代碼到這里管道的核心代碼就結束了,當然是通過在內核啟動周期中 關于請求發送到路由獲取響應這個實例來解析。
laravel 中路由對系統的管道做了細微的拓展,整體還是沒啥變化,就是閉包套閉包,不停地調用,就像剝洋蔥。
總結
以上是生活随笔為你收集整理的【Laravel-海贼王系列】第七章,Pipeline 类解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你知道前端单页面路由是怎么实现的吗?
- 下一篇: https的那些事儿