昨天使用範例說明 Pipeline 的包裝方法,相信至少可以略懂個一二。接下來先補充一下 parsePipeString()
在做什麼。
protected function parsePipeString($pipe) { list($name, $parameters) = array_pad(explode(':', $pipe, 2), 2, []);
if (is_string($parameters)) { $parameters = explode(',', $parameters); }
return [$name, $parameters]; }
|
昨天有提到,Pipeline 其實正是 middleware 的實作,而在 Laravel 樣版裡,Http Kernel 其實有定義一個 middleware 是這個:
是的,就是它!於是 throttle
會被轉成 $name 傳入 $app->make()
,$parameters 則會補到 [$passable, $stack]
後面,成為參數的一部分。同時這也是有的 middleware 如 ThrottleRequests 為什麼 handle()
可以接這麼多參數的原因。
接著我們來看 Hub。
它與 Pipeline 的關係圖如下:
@startuml interface Illuminate\Contracts\Pipeline\Pipeline { + {abstract} send($traveler) + {abstract} through($stops) + {abstract} via($method) + {abstract} then(Closure $destination) }
interface Illuminate\Contracts\Pipeline\Hub { + {abstract} pipe($object, $pipeline = null) }
Illuminate\Contracts\Pipeline\Pipeline <|.. Illuminate\Pipeline\Pipeline Illuminate\Contracts\Pipeline\Hub <|.. Illuminate\Pipeline\Hub Illuminate\Pipeline\Pipeline <|-- Illuminate\Routing\Pipeline Illuminate\Pipeline\Pipeline <- Illuminate\Pipeline\Hub @enduml
|
先看一下 pipe()
的實作:
public function pipe($object, $pipeline = null) { $pipeline = $pipeline ?: 'default';
return call_user_func( $this->pipelines[$pipeline], new Pipeline($this->container), $object ); }
|
從這裡可以了解,$this->pipelines[$pipeline]
實際要放的 Closure 應該要長的像這樣:
function(Pipeline $pipeline, $object) {
}
|
它的用途大概是,我們可以定義很多種流程,然後依不同的情境執行不同的流程。
$hub = $app->make(Hub::class);
if ($request->isAjax()) { return $hub->pipe($request, 'ajax'); }
return $hub->pipe($request);
|
而事實上,Laravel 預設樣版並沒有使用 Hub 的功能。
最後來看一下 Routing 的繼承實作,來看其中一段加上例外處理的程式碼:
try { return $destination($passable); } catch (Exception $e) { return $this->handleException($passable, $e); } catch (Throwable $e) { return $this->handleException($passable, new FatalThrowableError($e)); }
|
這裡可以看到會由 handleException()
來處理例外,來看一下這段程式碼,會有意外的發現:
protected function handleException($passable, Exception $e) { if (! $this->container->bound(ExceptionHandler::class) || ! $passable instanceof Request) { throw $e; }
$handler = $this->container->make(ExceptionHandler::class);
$handler->report($e);
$response = $handler->render($passable, $e);
if (method_exists($response, 'withException')) { $response->withException($e); }
return $response; }
|
如果還有印象的話,ExceptionHandler
正是分析 bootstrap 流程一開始的「綁定實作」之一,也就是 $app->singleton()
所綁定的其中一個類別名。這裡可以發現,它會呼叫 report()
以及 render()
,剛好就是 Laravel 樣版的 Handler
實作的一部分。
namespace App\Exceptions;
use Exception; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler { public function report(Exception $exception) { parent::report($exception); }
public function render($request, Exception $exception) { return parent::render($request, $exception); } }
|
我們知道文件裡有寫如何使用 Exception Handler,而實際上抓 Exception 並轉交給 Handler 處理的實作就是在這裡。
今日總結
這兩天 Pipeline 的 Closure 實作,並不是那麼好懂,不過當理解之後,Golang 或 Javascript 之類的語言,也都能使用類似的方法實作 middleware 哦。