Laravel管道流原理强烈依赖array_reduce函数,咱们先来了解下array_reduce函数的使用。php
原标题PHP 内置函数 array_reduce 在 Laravel 中的使用laravel
在看array_reduce
在laravel
中的应用时,先来看看array_reduce
官方文档是怎么说的。segmentfault
array_reduce()
将回调函数callback
迭代地做用到array
数组中的每个单元中,从而将数组简化为单一的值。数组
mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )
array闭包
输入的 array。app
callback函数
mixed callback ( mixed $carry , mixed $item )
$carry
包括上次迭代的值,若是本次迭代是第一次,那么这个值是initial
,item
携带了本次迭代的值this
initialspa
若是指定了可选参数 initial,该参数将在处理开始前使用,或者当处理结束,数组为空时的最后一个结果。code
从文档说明能够看出,array_reduce
函数是把数组的每一项,都经过给定的callback
函数,来简化
的。
那咱们就来看看是怎么简化的。
$arr = ['AAAA', 'BBBB', 'CCCC']; $res = array_reduce($arr, function($carry, $item){ return $carry . $item; });
给定的数组长度为3,故总迭代三次。
第一次迭代时 $carry = null $item = AAAA 返回AAAA
第一次迭代时 $carry = AAAA $item = BBBB 返回AAAABBBB
第一次迭代时 $carry = AAAABBBB $item = CCCC 返回AAAABBBBCCCC
这种方式将数组简化为一串字符串
AAAABBBBCCCC
$arr = ['AAAA', 'BBBB', 'CCCC']; $res = array_reduce($arr, function($carry, $item){ return $carry . $item; }, 'INITIAL-');
第一次迭代时($carry = INITIAL-),($item = AAAA) 返回INITIAL-AAAA
第一次迭代时($carry = INITIAL-AAAA),($item = BBBB), 返回INITIAL-AAAABBBB
第一次迭代时($carry = INITIAL-AAAABBBB),($item = CCCC),返回INITIAL-AAAABBBBCCCC
这种方式将数组简化为一串字符串
INITIAL-AAAABBBBCCCC
$arr = ['AAAA', 'BBBB', 'CCCC']; //没带初始值 $res = array_reduce($arr, function($carry, $item){ return function() use ($item){//这里只use了item return strtolower($item) . '-'; }; });
第一次迭代时,$carry:null,$item = AAAA,返回一个use了$item = AAAA的闭包
第二次迭代时,$carry:use了$item = AAAA的闭包,$item = BBBB,返回一个use了$item = BBBB的闭包
第一次迭代时,$carry:use了$item = BBBB的闭包,$item = CCCC,返回一个use了$item = CCCC的闭包
这种方式将数组简化为一个闭包,即最后返回的
闭包
,当咱们执行这个闭包时$res()
获得返回值CCCC-
上面这种方式只use ($item)
,每次迭代返回的闭包在下次迭代时,咱们都没有用起来。只是又从新返回了一个use
了当前item
值的闭包。
$arr = ['AAAA']; $res = array_reduce($arr, function($carry, $item){ return function () use ($carry, $item) { if (is_null($carry)) { return 'Carry IS NULL' . $item; } }; });
注意,此时的数组长度为1,而且没有指定初始值
因为数组长度为1,故只迭代一次,返回一个闭包 use($carry = null, $item = 'AAAA')
,当咱们执行($res()
)这个闭包时,获得的结果为Carry IS NULLAAAA
。
接下来咱们从新改造下,
$arr = ['AAAA', 'BBBB']; $res = array_reduce($arr, function($carry, $item){ return function () use ($carry, $item) { if (is_null($carry)) { return 'Carry IS NULL' . $item; } if ($carry instanceof \Closure) { return $carry() . $item; } }; });
咱们新增了一个条件判断,若当前迭代的值是一个闭包,返回该闭包的执行结果。
第一次迭代时,$carry
的值为null
,$item
的值为AAAA,返回一个闭包,
//伪代码 function () use ($carry = null, $item = AAAA) { if (is_null($carry)) { return 'Carry IS NULL' . $item; } if ($carry instanceof \Closure) { return $carry() . $item; } }
假设咱们直接执行该闭包,将会返回Carry IS NULLAAAA
的结果。
第二次迭代时,$carry
的值为上述返回的闭包(伪代码
),$item
的值为BBBB,返回一个闭包,
当咱们执行这个闭包时,知足
$carry instanceof \Closure
,获得结果Carry IS NULLAAAABBBB
。
大体了解了array_reverse
函数的使用后,咱们来瞅瞅laravel
管道流里使用array_reverse
的状况。
我在Laravel中间件原理中有阐述,强烈建议先去看看Laravel中间件原理再回过头来接着看。
php内置方法array_reduce把全部要经过的中间件都经过callback方法并压缩为一个Closure。最后在执行Initial
Laravel
中经过全局中间件的核心代码以下:
//Illuminate\Foundation\Http\Kernel.php protected function sendRequestThroughRouter($request) { return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); } protected function dispatchToRouter() { return function ($request) { $this->app->instance('request', $request); return $this->router->dispatch($request); }; }
正如我前面说的,咱们发送一个$request
对象经过middleware
中间件数组,最后在执行dispatchToRouter
方法。
假设有两个全局中间件,咱们来看看这两个中间件是如何经过管道压缩为一个Closure
的。
Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class, App\Http\Middleware\AllowOrigin::class,//自定义中间件
IlluminatePipelinePipeline为laravel的管道流核心类.
在Illuminate\Pipeline\Pipeline
的then
方法中,$destination
为上述的dispatchToRouter
闭包,pipes
为要经过的中间件数组,passable
为Request
对象。
public function then(Closure $destination) { $pipeline = array_reduce( array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination) ); return $pipeline($this->passable); }
array_reverse
函数将中间件数组的每一项都经过$this->carry()
,初始值为上述dispatchToRouter
方法返回的闭包。
protected function prepareDestination(Closure $destination) { return function ($passable) use ($destination) { return $destination($passable); }; } protected function carry() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { if ($pipe instanceof Closure) { 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 $pipe->{$this->method}(...$parameters); }; }; }
第一次迭代时,返回一个闭包,use
了$stack
和$pipe
,$stack
的值为初始值闭包,$pipe
为中间件类名,此处是App\Http\Middleware\AllowOrigin::class
(注意array_reverse
函数把传进来的中间件数组倒叙了)。
假设咱们直接运行该闭包,因为此时$pipe
是一个String
类型的中间件类名,只知足! is_object($pipe)
这个条件,咱们将直接从容器中make
一个该中间件的实列出来,在执行该中间件实列的handle
方法(默认$this->method
为handle
)。而且将request
对象和初始值做为参数,传给这个中间件。
public function handle($request, Closure $next) { //...... }
在这个中间件的handle
方法中,当咱们直接执行return $next($request)
时,至关于咱们开始执行array_reduce
函数的初始值闭包了,即上述的dispatchToRouter
方法返回的闭包。
protected function dispatchToRouter() { return function ($request) { $this->app->instance('request', $request); return $this->router->dispatch($request); }; }
好,假设结束。在第二次迭代时,也返回一个use
了$stack
和$pipe
,$stack
的值为咱们第一次迭代时返回的闭包,$pipe
为中间件类名,此处是Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class
。
两次迭代结束,回到then
方法中,咱们手动执行了第二次迭代返回的闭包。
return $pipeline($this->passable);
当执行第二次迭代返回的闭包时,当前闭包use
的$pipe
为Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class
,一样只知足! is_object($pipe)
这个条件,咱们将会从容器中make
出CheckForMaintenanceMode
中间件的实列,在执行该实列的handle
方法,而且把第一次迭代返回的闭包做为参数传到handle
方法中。
当咱们在CheckForMaintenanceMode
中间件的handle
方法中执行return $next($request)
时,此时的$next
为咱们第一次迭代返回的闭包,将回到咱们刚才假设的流程那样。从容器中make
一个App\Http\Middleware\AllowOrigin
实列,在执行该实列的handle
方法,并把初始值闭包做为参数传到AllowOrigin
中间件的handle方法中
。当咱们再在AllowOrigin
中间件中执行return $next($request)
时,表明咱们全部中间件都经过完成了,接下来开始执行dispatchToRouter
。
中间件是区分前后顺序的,从这里你应该能明白为何要把中间件用array_reverse
倒叙了。
并非全部中间件在运行前都已经实例化了的,用到的时候才去想容器取
中间件不执行$next($request)后续全部中间件没法执行。
这篇文章是专们为了上一篇Laravel中间件原理写的,由于在写Laravel中间件原理时我也不很清楚
array_reduce
在laravel
中的运行流程。若是有什么不对的,欢迎指正。