摘要:管道流原理强烈依赖函数,我们先来了解下函数的使用。第二次迭代时,的值为上述返回的闭包伪代码,的值为,返回一个闭包,当我们执行这个闭包时,满足,得到结果。自定义中间件为的管道流核心类在的方法中,为上述的闭包,为要通过的中间件数组,为对象。
Laravel管道流原理强烈依赖array_reduce函数,我们先来了解下array_reduce函数的使用。
原标题PHP 内置函数 array_reduce 在 Laravel 中的使用
array_reduce在看array_reduce在laravel中的应用时,先来看看array_reduce官方文档是怎么说的。
array_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。
mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )
array
输入的 array。
callback
mixed callback ( mixed $carry , mixed $item )
$carry包括上次迭代的值,如果本次迭代是第一次,那么这个值是 initial,item 携带了本次迭代的值
initial
如果指定了可选参数 initial,该参数将在处理开始前使用,或者当处理结束,数组为空时的最后一个结果。
从文档说明可以看出,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值的闭包。
闭包USE闭包$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,返回一个闭包,
Laravel中的array_reverse当我们执行这个闭包时,满足$carry instanceof Closure,得到结果Carry IS NULLAAAABBBB。
大致了解了array_reverse函数的使用后,我们来瞅瞅laravel管道流里使用array_reverse的情况。
我在Laravel中间件原理中有阐述,强烈建议先去看看Laravel中间件原理再回过头来接着看。
php内置方法array_reduce把所有要通过的中间件都通过callback方法并压缩为一个Closure。最后在执行Initial
Laravel中通过全局中间件的核心代码如下:
//IlluminateFoundationHttpKernel.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的。
IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::class, AppHttpMiddlewareAllowOrigin::class,//自定义中间件
IlluminatePipelinePipeline为laravel的管道流核心类.
在IlluminatePipelinePipeline的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为中间件类名,此处是AppHttpMiddlewareAllowOrigin::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为中间件类名,此处是IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::class。
两次迭代结束,回到then方法中,我们手动执行了第二次迭代返回的闭包。
return $pipeline($this->passable);
当执行第二次迭代返回的闭包时,当前闭包use的$pipe为IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::class,同样只满足! is_object($pipe)这个条件,我们将会从容器中make出CheckForMaintenanceMode中间件的实列,在执行该实列的handle方法,并且把第一次迭代返回的闭包作为参数传到handle方法中。
当我们在CheckForMaintenanceMode中间件的handle方法中执行return $next($request)时,此时的$next为我们第一次迭代返回的闭包,将回到我们刚才假设的流程那样。从容器中make一个AppHttpMiddlewareAllowOrigin实列,在执行该实列的handle方法,并把初始值闭包作为参数传到AllowOrigin中间件的handle方法中。当我们再在AllowOrigin中间件中执行return $next($request)时,代表我们所有中间件都通过完成了,接下来开始执行dispatchToRouter。
中间件是区分先后顺序的,从这里你应该能明白为什么要把中间件用array_reverse倒叙了。
并不是所有中间件在运行前都已经实例化了的,用到的时候才去想容器取
中间件不执行$next($request)后续所有中间件无法执行。
这篇文章是专们为了上一篇Laravel中间件原理写的,因为在写Laravel中间件原理时我也不很清楚array_reduce在laravel中的运行流程。如果有什么不对的,欢迎指正。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/23190.html
摘要:直到所有中间件都执行完毕,最后在执行最后的即上述的方法如果上述有地方难懂的,可以参考这边文章内置函数在中的使用以上是在通过全局中间件时的大致流程,通过中间件和路由中间件也是一样的,都是采用管道流操作,详情可翻阅源码 简介 Laravel 中间件提供了一种方便的机制来过滤进入应用的 HTTP 请求, 如ValidatePostSize用来验证POST请求体大小、ThrottleReque...
摘要:将请求传入到指定的中间件路由。用于处理任务的方法接收两个参数,第一个是一个可传递的对象,第二个是闭包,在运行最后一个管道后对象将被重定向到这个闭包。我希望这个实例能够让你对有更深如的了解,并知道如何使用它们。 这是一篇译文,原文 Understanding Laravel Pipelines。译文首发于 深入理解 Laravel 管道,转载请注明出处。 基本上,你可以使用 larave...
摘要:好久没有写文章了,记录一下这段时间学习的东西吧中间件是个非常方便的东西,能将一些逻辑实现解耦,并且在中,中间件的编写也是非常的方便。对于的中间件,他的实现原理也是和这个一样的。 好久没有写文章了,记录一下这段时间学习的东西吧 laravel中间件是个非常方便的东西,能将一些逻辑实现解耦,并且在laravel中, 中间件的编写也是非常的方便。谁用谁知道。 1.装饰器模式 laravel中...
摘要:官方地址是目前最流行的框架,发展势头迅猛,应用非常广泛,有丰富的扩展包可以应付你能想到的各种应用场景,框架思想前卫,跟随时代潮流,提倡优雅代码,自称为工匠,其中的模板引擎容器以及扩展包为业务的开发提供了极大的便利。 laravel5.5+ laravel官方地址 laravel是目前最流行的php框架,发展势头迅猛,应用非常广泛,有丰富的扩展包可以应付你能想到的各种应用场景,lara...
阅读 709·2021-08-17 10:11
阅读 1573·2019-08-30 11:15
阅读 999·2019-08-26 13:54
阅读 3484·2019-08-26 11:47
阅读 1192·2019-08-26 10:20
阅读 2793·2019-08-23 18:35
阅读 1198·2019-08-23 17:52
阅读 1281·2019-08-23 16:19