在阅读laravel源码过程中,在Illuminate\Foudation\Http\Kenel.php中,开始处理requst请求中有这么一段代码
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
透过字面理解知道,这段代码是实例化一个Pipeline(流水线),然后依次将本次请求的$requst对象,交给中间件middleware处理,之后再给路由器转发处理这个请求。
在laravel的文档中,我们也知道处理请求的这样一个过程,请求会先经过我们定义的中间件,然后再通过路由解析,交给具体的controller处理,这样其实就像是一个流水线的过程,$request对象依次的在各个工作台上被处理,每个工作台都把自己处理完的$request对象交给下一个工作台。
但是在阅读 Pipeline 类的时候,还是遇到的困难,经过近一小时的努力理解和手动尝试,最终彻底弄懂了,觉得这几行代码非常有趣,值得记录一下。
代码理解
-
new Pipleline($this->app)
将laravel容积对象传递给pipeline -
pipeline->send($request)
public function send($passable) { $this->passable = $passable; return $this; }
设置pipeline中的 $passable为本次请求的request对象
-
pipeline->through()
public function through($pipes) { $this->pipes = is_array($pipes) ? $pipes : func_get_args(); return $this; }
将kenel.php中定义的的middleware数组传递给pipeline中的pipes
-
pipeline->then()
public function then(Closure $destination) { $pipeline = array_reduce( array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination) ); return $pipeline($this->passable); }
这个方法中的array_reduce是最关键的操作,在此之前,我对这个数组的方法仅仅是讲过的层面,平时开发中从没用过。
在这里,laravel用这个方法实现了一个流水线,具体的流程如下:- 首先我们需要了解下 array_reduce 的作用和使用方法,
-
方法定义 :array_reduce ( array
$array
, [callable]$callback
, [mixed]$initial
=null
) : [mixed] - 作用: 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值
$arr = [2, 4, 6, 8]; $result = array_reduce($arr, function($stack, $pipe) { // $stack就是上次遍历数组上一个元素时,对这个元素处理之后return的值, // 当遍历第一个数组中的第一个元素时,如果array_reduce第三个参数为null时,$stack 就是空,否则$stack最开始就是第三个参数的值 return $stack + $pipe; }, 10); echo $result.PHP_EOL; // echo 30
-
方法定义 :array_reduce ( array
- 应用到代码中的then方法中,array_reduce有三个参数
- array_reverse($pipes)
即kenel中定义的中间件的逆序。为什么要逆序呢,是因为第二个参数返回的是一个方法,该方法内部每次都会把前一次的结果压到栈中,导致最终执行的顺序是倒着的,所以先把$pipe倒序一下,就能保证后边顺序执行了 - $this->carry()
protected function carry() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { if (is_callable($pipe)) { return $pipe($passable, $stack); } elseif (! is_object($pipe)) { list($name, $parameters) = $this->parsePipeString($pipe); $pipe = $this->getContainer()->make($name); $parameters = array_merge([$passable, $stack], $parameters); } else { $parameters = [$passable, $stack]; } return method_exists($pipe, $this->method) ? $pipe->{$this->method}(...$parameters) : $pipe(...$parameters); }; }; }
但仔细阅读我们发现,在这个闭包中,每次都把$stack作为第二个参数,交给$pipe()或者$pipe->handle()方法,在middleware的handle方法中,我们知道他定义是handle($request, Closure $next)。
遍历第一个middleware后,$stack就是carry最内层的方法,也就被当作了下一个中间件的$next参数,所以在每个中间件的最后,我们执行$next($request)
,其实就是把这个中间件处理过的$request交给了之前的那个中间件。
这一块比较绕,我按照这个思路写了一个小demo,利于理解class middleware1 { function handle($request, Closure $next) { echo "middleware 1, request=".$request.PHP_EOL; $request = $request."-1"; return $next($request); } } class middleware2 { function handle($request, Closure $next) { echo "middleware 2, request=".$request.PHP_EOL; $request = $request."-2"; return $next($request); } } class middleware3 { function handle($request, Closure $next) { echo "middleware 3, request=".$request.PHP_EOL; $request = $request."-3"; return $next($request); } } const METHOD = "handle"; $pipes = [ new middleware1(), new middleware2(), new middleware3() ]; function carry() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { $params = [$passable, $stack]; return $pipe->{METHOD}(...$params); }; }; } $pipeline = array_reduce(array_reverse($pipes), carry(), function($passable) { echo "final passable, final request=".$passable.PHP_EOL; }); $pipeline("request"); // middleware 1, request=request // middleware 2, request=request-1 // middleware 3, request=request-1-2 // final passable, final request=request-1-2-3
- array_reverse($pipes)
- this->prepareDestination($destination))
第三个参数返回值也是一个匿名函数, 这样能保证最后一个middleware中执行$next(request,把流水线继续下去,否则最后一个中间件中的$next就会失败了
- 首先我们需要了解下 array_reduce 的作用和使用方法,
总结
自己水平有限,写的有些乱,大家可以通过执行一下上边的那个小demo,参照laravel源码,应该能理解的更好
网友评论