Laravel流水线原理强烈依赖于array_reduce函数,我们先了解一下array_reduce函数的使用。原标题PHP内置函数array_reduce在Laravel中使用array_reduce在看array_reduce在laravel中的应用时,先看看array_reduce的官方文档是怎么说的。array_reduce()通过将回调函数迭代地应用于数组中的每个元素,将数组缩减为单个值。mixedarray_reduce(array$array,callable$callback[,mixed$initial=NULL])数组输入数组。callbackmixedcallback(mixed$carry,mixed$item)$carry包含上一次迭代的值,如果本次迭代是第一次,那么这个值为initial,item携带本次迭代的值initial如果指定了可选参数initial,将在处理开始之前使用,或者在处理结束且数组为空时使用最后一个结果。从文档中可以看出,array_reduce函数通过给定的回调函数简化了数组的每一项。那么让我们看看如何简化它。$arr=['AAAA','BBBB','CCCC'];$res=array_reduce($arr,function($carry,$item){return$carry.$item;});给定数组的长度为3,因此总迭代次数为3次。$carry=null第一次迭代$item=AAAA返回AAAA$carry=AAAA第一次迭代$item=BBBB第一次迭代返回AAAABBB$carry=AAAABBBBB$item=CCCC返回AAAABBCCCC这样数组简化为字符串具有初始值的字符串AAAABBBCCCC$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),returnsINITIAL-AAAABBBBCCCC此方法将数组简化为一串字符串INITIAL-AAAABBBBCCCCclosure$arr=['AAAA','BBBB','CCCC'];//没有初始值$res=array_reduce($arr,function($carry,$item){returnfunction()use($item){//这里只使用itemreturnstrtolower($item).'-';};});在第一次迭代中,$carry:null,$item=AAAA,返回一个使用$item=AAAA的闭包在第二次迭代中,$carry:使用$item=AAAA,$item=BBBB的闭包,返回一个闭包使用$item=BBBB在第一次迭代中,$carry:使用$item=BBBB闭包,$item=CCCC返回一个闭包,使用$item=CCCC这个方法将数组简化为一个闭包,即最后返回的闭包,当我们执行这个闭包的时候,$res()得到返回值CCCC——上面的方法只使用了($item),每次迭代返回的闭包在下一次迭代中不会用到。它只是返回一个再次使用当前项值的闭包。闭包使用闭包$arr=['AAAA'];$res=array_reduce($arr,function($carry,$item){返回函数()使用($carry,$item){if(is_null($carry)){return'CarryISNULL'.$item;}};});注意此时数组的长度为1,没有指定初始值。由于数组的长度为1,所以只迭代一次,返回一个闭包use($carry=null,$item='AAAA'),当我们执行($res())这个闭包时,结果是CarryISNULLAAAA.接下来我们改造一下,$arr=['AAAA','BBBB'];$res=array_reduce($arr,function($carry,$item){返回函数()使用($carry,$item){if(is_null($carry)){return'CarryISNULL'.$item;}如果($carryinstanceof\Closure){return$carry().$item;}};});我们新增了一个条件判断,如果当前迭代的值是一个闭包,则返回闭包的执行结果。第一次迭代,$carry的值为null,$item的值为AAAA,返回一个闭包,//伪代码function()use($carry=null,$item=AAAA){if(is_null($进位)){返回'进位为空'。$项目;}if($carryinstanceof\Closure){return$carry()。$项目;}}假设我们直接执行闭包,它会返回CarryISNULLAAAA结果。第二次迭代,$carry的值为上面返回的闭包(伪代码),$item的值为BBBB,返回一??个闭包。当我们执行这个闭包时,满足$carryinstanceof\Closure,得到的结果是CarryISNULLAAAAABBBB。Laravel中的array_reverse在大致了解了array_reverse函数的使用之后,我们来看看array_reverse在laravel流水线中的使用。Laravel中间件的原理我已经阐述过了。强烈建议先看看Laravel中间件的原理再回去看。PHP内置方法array_reduce将所有要传递的中间件通过回调方法,压缩成一个Closure。最后,InitialLaravel执行中通过全局中间件的核心代码如下://Illuminate\Foundation\Http\Kernel.phpprotectedfunctionsendRequestThroughRouter($request){return(newPipeline($this->app))->send($request)->through($this->app->shouldSkipMiddleware()?[]:$this->middleware)->then($this->dispatchToRouter());}protectedfunctiondispatchToRouter(){返回函数($request){$this->app->instance('request',$request);返回$this->router->dispatch($request);};}之前说过,我们通过middleware发送一个$request对象一个中间件数组,最后执行dispatchToRouter方法。假设有两个全局中间件,我们看看这两个中间件是如何通过管道压缩成一个Closure的。Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,App\Http\Middleware\AllowOrigin::class,//自定义中间件IlluminatePipelinePipeline是laravel的管道流核心类。在Illuminate\Pipeline\Pipeline的then方法中,$destination就是上面的dispatchToRouter闭包,pipes就是要传递的中间件数组,passable就是Request对象。publicfunctionthen(Closure$destination){$pipeline=array_reduce(array_reverse($this->pipes),$this->carry(),$this->prepareDestination($destination));return$pipeline($this->passable);}array_reverse函数通过$this->carry()传递中间件数组的每一项,初始值为上面dispatchToRouter方法返回的闭包。protectedfunctionprepareDestination(Closure$destination){returnfunction($passable)use($destination){return$destination($passable);}};}protectedfunctioncarry(){returnfunction($stack,$pipe){返回函数($passable)use($stack,$pipe){if($pipeinstanceofClosure){return$pipe($passable,$堆);}elseif(!is_object($pipe)){//解析中间件参数列表($name,$parameters)=$this->parsePipeString($pipe);$pipe=$this->getContainer()->make($name);$parameters=array_merge([$passable,$stack],$parameters);}else{$parameters=[$passable,$stack];}返回$pipe->{$this->method}(...$parameters);};};}第一次迭代,返回A闭包,使用$stack和$pipe,$stack的值是闭包的初值,$pipe是中间件类名,这里是App\Http\Middleware\AllowOrigin::class(注意array_reverse函数中传入的中间件数组是闪回的)。假设我们直接运行闭包,由于此时$pipe是一个String类型的中间件类名,所以只满足条件!is_object($pipe),我们会直接从容器中创建一个中间件的实例,执行中间件实例的handle方法(默认$this->method是handle)。并将请求对象和初始值作为参数传递给这个中间件。publicfunctionhandle($request,Closure$next){//...}在这个中间件的handle方法中,当我们直接执行return$next($request)时,相当于开始执行array_reduce函数的初始的值为closed,即上面dispatchToRouter方法返回的闭包。protectedfunctiondispatchToRouter(){返回函数($request){$this->app->instance('request',$request);返回$this->router->dispatch($request);};}好的,假设完成。在第二次迭代中,还返回了对$stack和$pipe的使用。$stack的值是我们第一次迭代返回的闭包,$pipe是中间件类的名称,这里是Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class。两次迭代结束,回到then方法,我们手动执行第二次迭代返回的闭包。返回$pipeline($this->passable);在执行第二次迭代返回的闭包时,当前闭包使用的$pipe是Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,也只满足!is_object($pipe)条件,我们将创建一个实例从容器中调用CheckForMaintenanceMode中间件,执行实例的handle方法,并将第一次迭代返回的闭包作为参数传递给handle方法。当我们在CheckForMaintenanceMode中间件的handle方法中执行return$next($request)时,此时的$next就是我们第一次迭代返回的闭包,会返回到我们刚才假设的流程。从容器中创建一个App\Http\Middleware\AllowOrigin实例,执行该实例的handle方法,将初始值闭包作为参数传递给AllowOrigin中间件的handle方法。当我们在AllowOrigin中间件执行return$next($request)时,说明我们所有的中间件都通过了,然后开始执行dispatchToRouter。中间件是有顺序的,从这里你应该能明白为什么中间件要用array_reverse来反转。并非所有中间件在运行前都已实例化。使用的时候只需要考虑容器抓取中间件即可。如果不执行$next($request),则后面的所有中间件都不能执行。这篇文章是为上一篇Laravel中间件原理而写的,因为在写Laravel中间件原理的时候,对laravel中array_reduce的运行过程不是很清楚。如果有什么不对的地方,请指正。
