Illuminate/Pipeline/Pipeline.php
/**
* Run the pipeline with a final destination callback.
*
* @param \Closure $destination
* @return mixed
*/
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
/**
* Get the final piece of the Closure onion.
*
* @param \Closure $destination
* @return \Closure
*/
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
return $destination($passable);
};
}
/**
* Get a Closure that represents a slice of the application onion.
*
* @return \Closure
*/
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if (is_callable($pipe)) {
// If the pipe is an instance of a Closure, we will just call it directly but
// otherwise we'll resolve the pipes out of the container and call it with
// the appropriate method and arguments, returning the results back out.
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
list($name, $parameters) = $this->parsePipeString($pipe);
// If the pipe is a string we will parse the string and resolve the class out
// of the dependency injection container. We can then build a callable and
// execute the pipe function giving in the parameters that are required.
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
// If the pipe is already an object we'll just make a callable and pass it to
// the pipe as-is. There is no need to do any extra parsing and formatting
// since the object we're given was already a fully instantiated object.
$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;
};
};
}
- 管道處理的核心方法then,實際上是一個非常有用的PHP函式array_reduce($array, $callback, $initial)的應用,只不過待處理陣列變成了可呼叫的中介軟體函式(字串或closure表示的),這些函式都只接受一個引數即Request例項;
- 處理函式因為有額外引數$passable(即 Request例項)需要傳入,所以多包了一層閉包函式【注:關於多層包含的閉包函式,在Python裝飾器原理中可以充分理解,尤其是阮一峰的逐步遞進寫完善的裝飾器過程】;
- 初始值也是一個閉包函式,這個是真正的response響應函式。
$this->method = 'handle' 這個方法名,可通過via函式修改。
中介軟體類方法 handle($request, Closure $next) 有2個引數,這個對應閉包處理過後的中介軟體函式的返回值
return $pipe($passable, $stack)。
Laravel中介軟體邏輯,是閉包函式強大功能的一大體現。於是我們使用閉包函式,以函數語言程式設計風格實現簡化版的中介軟體流程:
function f1($f){
return function($x) use ($f){
echo 'middleware 1 begin.'.PHP_EOL;
$x += 1;
$x = $f($x);
echo 'middleware 1 end.'.PHP_EOL;
return $x;
};
}
function f2($f){
return function($x) use($f){
echo 'middleware 2 begin: '.PHP_EOL;
$x += 2;
$x = $f($x);
echo 'middleware 2 end.'.PHP_EOL;
return $x;
};
}
function respond(){
return function($x){
echo 'Generate some response.'.PHP_EOL;
return $x;
};
}
$x = 1;
$response = f2(f1(respond()))($x);
echo $response;
輸出:
middleware 2 begin:
middleware 1 begin.
Generate some response.
middleware 1 end.
middleware 2 end.
4
可以看出為什麼Laravel的Pipeline的實現需要反轉中介軟體陣列。
高階函式的實現,可以通過層層傳遞"函式名字串"的原始方法來實現,但是通過閉包函式實現更加簡單。
array_reduce寫法
$f1 = function($x, $f){
echo 'middleware 1 begin.'.PHP_EOL;
$x += 1;
$x = $f($x);
echo 'middleware 1 end.'.PHP_EOL;
return $x;
};
$f2 = function($x, $f){
echo 'middleware 2 begin: '.PHP_EOL;
$x += 2;
$x = $f($x);
echo 'middleware 2 end.'.PHP_EOL;
return $x;
};
$respond = function($x){
echo 'Generate some response.'.PHP_EOL;
return $x;
};
$middlewares = [$f1, $f2];
$initial = $respond;
$foo = array_reduce($middlewares, function($stack, $item){
return function($request) use ($stack, $item){
return $item($request, $stack);
};
}, $initial);
$x = 1;
echo $foo($x);
輸出:
middleware 2 begin:
middleware 1 begin.
Generate some response.
middleware 1 end.
middleware 2 end.
4