Pipeline類中function then()的實現原理
// 檔案 vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
// 管道類
class Pipeline implements PipelineContract
{
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
try {
return $destination($passable);
} catch (Throwable $e) {
return $this->handleException($passable, $e);
}
};
}
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
try {
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];
}
$carry = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
return $this->handleCarry($carry);
} catch (Throwable $e) {
return $this->handleException($passable, $e);
}
};
};
}
}
要理解Pipeline類中then實現中介軟體的原理需要著重瞭解一下重點:
- 內建函式:array_reduce()
- 閉包型別:Closure
- 內建函式:array_reverse()
下面手把手帶你實現下面的功能:
then()功能分析實現
我們可以透過dd($this->pipes())列印發現,$this->pipes()是一箇中介軟體的陣列;
$this->carry() 和 $destination都是一個閉包
步驟一 :
那麼我們來簡單模仿一下寫個簡單的方法來先了解下array_reduce()
先寫一個簡單的控制器:
// 檔案 /routes/web.php
Route::get('/','TestController@index');
// 檔案 /app/Http/Controllers/TestController.php
public function index()
{
$funcA = function () {
echo "funcA <br/>";
};
$funcB = function () {
echo "funcB <br/>";
};
$funcC = function () {
echo "funcC <br/>";
};
$pipes = [$funcA, $funcB, $funcC];
// $carry 是上一個函式的返回值; 若一開是第三個引數為空時 則為null
// $item 陣列的元素值
// 此處我們不考慮$carry 我們讓他每次都執行 閉包$item()
array_reduce($pipes, function ($carry, $item) {
$item();
});
}
結果:
步驟二
現在我們再修改一下,此時我們閉包內不執行$item,而是作為$carry值返回給下一次迴圈作為引數呼叫;並且加入第三個引數,為$carry賦一個預設值:
public function index()
{
... ... // 前面內容不變
// 第三個引數為一個 字串 , 這個值會作為第一次呼叫時,$carry的值
$finalFunc = array_reduce($pipes, function ($carry, $item) {
// 如果是字串則直接列印
if (is_string($carry)) {
echo $carry;
} elseif ($carry instanceof \Closure) {
// 如果是閉包則執行
$carry();
}
// 向下一個呼叫的$carry返回當前的閉包$item;
return $item;
}, "Init<br/>");
// 三次迴圈的引數與返回如下:
// 第一次引數:$carry= "Init<br/>"; $item=closure funcA() 返回 closure funcA()
// 第二次引數:$carry= closure funcA(); $item=closure funcB() 返回 closure funcB()
// 第三次引數:$carry= closure funcB(); $item=closure funcC() 返回 closure funcC()
// 此時的返回值為 closure funcC() 直接呼叫就是呼叫funcC
$finalFunc();
}
執行結果:
我們好像發現,離按順序執行中介軟體已經越來越近了,那我們繼續修改。
步驟三
改變如下
public function index()
{
// 1. 為閉包新增引數,
$middelwareA = function ($request, \Closure $next) {
echo "middlewareA hanlde request <br/>";
};
$middlewareB = function ($request, \Closure $next) {
echo "middlewareB hanlde request <br/>";
};
$middlewareC = function ($request, \Closure $next) {
echo "middlewareC hanlde request <br/>";
};
$pipes = [$middelwareA, $middlewareB, $middlewareC];
// 2. 增加一個變數request
$request = 'request';
// 3. 增加一個閉包,模仿原始碼的中的引數$destination
$callback = function ($request) {
echo "callback handler request <br/>";
};
// 4. 改變第三個引數,傳入一個閉包(下面新增了方法一個新的方法來返回閉包)
$finalFunc = array_reduce($pipes, $this->carry(),$this->prepareDestination($callback));
// 執行解釋: 此時最終返回的是一個閉包 closure funcC($request,$carry)
// 執行解釋: 因為前面的callback funcA funcB 我們都沒做任何處理的直接丟掉了
$finalFunc($request);
}
// 6. 內容改簡單一點,直接呼叫$item()
public function carry()
{
return function ($carry, $item) {
return function ($request) use ($carry, $item) {
// 第一次引數:$carry= closure $callback($request); $item=closure funcA() 返回 closure($request,$carry)
// 第二次引數:$carry= closure funcA($request,$carry); $item=closure funcB() 返回 $item=closure funcB($request,$carry)
// 第二次引數:$carry= closure funcB($request,$carry); $item=closure funcC() 返回 $item=closure funcC($request,$carry)
return $item($request, $carry);
// $item($request, $carry);
};
};
}
public function prepareDestination($callback)
{
return function ($request) use ($callback) {
return $callback($request);
};
}
執行結果 : middlewareC hanlde request
解釋如程式碼註釋所示
我們再微調一下程式碼
步驟四
public function index()
{
// 1. 為閉包新增引數,
$middelwareA = function ($request, \Closure $next) {
echo "middlewareA hanlde request <br/>";
// 此處的$next = closure $callback($request)
return $next($request);
};
$middlewareB = function ($request, \Closure $next) {
echo "middlewareB hanlde request <br/>";
// 此處的$next = closure funcA($request,closure $callback($request))
return $next($request);
};
$middlewareC = function ($request, \Closure $next) {
echo "middlewareC hanlde request <br/>";
// 此處的$next = closure funcB($request,closure funcA(...))
return $next($request);
};
... ... // 中間程式碼省略
$finalFunc($request);
}
結果:
好的,中介軟體的原理實現的差不多了。
我們想想如何實現前後置中介軟體?
步驟五
public function index()
{
$middelwareA = function ($request, \Closure $next) {
echo "before middlewareA hanlde request <br/>";
// 此處的$next = closure $callback($request)
$response = $next($request);
echo "after middlewareA hanlde request <br/>";
return $response;
};
$middlewareB = function ($request, \Closure $next) {
echo "before middlewareB hanlde request <br/>";
// 此處的$next = closure funcA($request,closure $callback($request))
$response = $next($request);
echo "after middlewareB hanlde request <br/>";
return $response;
};
$middlewareC = function ($request, \Closure $next) {
echo "before middlewareC hanlde request <br/>";
// 此處的$next = closure funcB($request,closure funcA(...))
$response = $next($request);
echo "after middlewareC hanlde request <br/>";
return $response;
};
... ... // 中間程式碼省略
$finalFunc($request);
}
執行結果:
至此,大家應該瞭解了then是如何實現中介軟體的原理的了吧.(關於中介軟體的執行順序就留給大家自己思考吧)
本作品採用《CC 協議》,轉載必須註明作者和本文連結