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是如何实现中间件的原理的了吧.(关于中间件的执行顺序就留给大家自己思考吧)