昨天使用範例說明 Pipeline 的包裝方法,相信至少可以略懂個一二。接下來先補充一下 parsePipeString()
在做什麼:
protected function parsePipeString($pipe)
{
// 使用冒號 `:` 把 $pipe 拆成兩個元素塞到 $name 與 $parameters,沒得拆的話會使用 [] 補到 $parameters 裡
list($name, $parameters) = array_pad(explode(':', $pipe, 2), 2, []);
// 如果有拆成的話,這會是字串,再把它用逗號拆成 array
if (is_string($parameters)) {
$parameters = explode(',', $parameters);
}
return [$name, $parameters];
}
昨天有提到,Pipeline 其實正是 middleware 的實作,而在 Laravel 樣版裡,Http Kernel 其實有定義一個 middleware 是這個:
throttle:60,1
是的,就是它!於是 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 哦。