Laravel 框架在处理Middleware中采用了一种管道模式。相信有人一定对一下的代码好奇过,
$next
到底是什么?那么我就带大家来一探究竟。
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect('/home');
}
return $next($request);
}
}
管道模式又称装饰模式,实际是装饰模式的一个变种,用闭包的形式来实现装饰模式。它可以像一条流水线一样传递并处理同一个对象,在每一步对对象进行不同的处理,同时每一个步骤又都是相互隔离的,耦合性不高,在需要对同一对象进行一系列业务分离的处理时非常有用。
先来看看Laravel启动时的核心代码,看看Laravel是如何使用管道模式的:
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
在通过路由之前,Laravel使用了管道模式来层层调用系统中注册的全局中间件来对Request
进行处理。先通过Pipeline
的send
方法将需要传递进管道的对象注入,through
方法设置需要通过的中间件。最后通过then
来接收处理过的Request
继续向路由传送。
Pipeline
的then
方法中是这样写的:
/**
* Run the pipeline with a final destination callback.
*
* @param \Closure $destination
* @return mixed
*/
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
核心方法就是这个array_reverse
方法,这是一个php内置方法。文档上是这样写的:
array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] ) : mixed
array_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。
参数 ¶
array : 输入的 array。
callback:
callback ( mixed $carry , mixed $item ) : mixed
carry:携带上次迭代里的值; 如果本次迭代是第一次,那么这个值是 initial。
item:携带了本次迭代的值。
initial:如果指定了可选参数 initial,该参数将在处理开始前使用,或者当处理结束,数组为空时的最后一个结果。
返回值 ¶
返回结果值。
initial 参数,array_reduce() 返回 NULL。
这个函数的作用是循环调取把array中的值传入第二个参数闭包函数中处理,有些像array_map
函数。但它还会把处理结果返回传入下一次调用,依次迭代,第三个参数可选,如果传了会当作第一次调用的参数传入闭包函数中,如果最终返回值为空是还会返回intial的值。
Laravel在then
方法中的作的事情就是把pips中存储的中间件依次调用carry
方法处理,carry
方法返回的是一个闭包函数,其作用是把中间件中的处理操作压成一个栈。然后传入array_reduce
中去依次调用。我写了个简化版的Pipeline来展示carry
的原理。
send
方法,through
方法和then
方法还是仿照Laravel的写法。
passable = $passable;
return $this;
}
public function through($pips) {
$this->pips = is_array($pips) ? $pips : func_get_args();
return $this;
}
protected function carry() {
//压缩pips方法
}
public function then(Closure $destination) {
$pipline = array_reduce(array_reverse($this->pips), $this->carry(), $this->prepareDestination($destination));
return $pipline($this->passable);
}
}
接下来我们重点来看carry方法的实现
protected function carry() {
//array_reduce使用的闭包
return function($stack, $pip) {
//array_reduce压缩起来的闭包函数栈是这一层
return function($passable) use($stack, $pip) {
return $pip->handle($passable, $stack);
};
};
}
当array_reduce
第一次调用carry
方法时,首先拿到的最外层的 return function($stack, $pip)
,函数把prepareDestination
中的闭包函数当作$stack
首先传入。把第一个pip暂且叫作pip1,传入$pip
参数,返回结果展开来是这样:
return function($passable) {
return $pip1->handle($passable, , $destination);
};
第二次调用,将第一次的结果被当作了新的$stack
,$pip
传入pip2,现在返回结果展开变成了这样:
return function($passable) {
return $pip2->handle($passable, , function($passable){
return $pip1->handle($passable, $destination);
});
};
依次递归,最后在then
方法中调用this->passable)来循环递归执行,可以看到,destination被压到了堆栈的最里面。而排在越前面的pip反而会被压到最里面,最后执行,所以Laravel在传入pips数组时执行了array_reverse
方法来确保排在前面的中间件能先被执行。
完整版代码如下:
passable = $passable;
return $this;
}
public function through($pips) {
$this->pips = is_array($pips) ? $pips : func_get_args();
return $this;
}
protected function carry() {
//array_reduce使用的闭包
return function($stack, $pip) {
//array_reduce压缩起来的闭包函数栈是这一层
return function($passable) use($stack, $pip) {
return $pip->handle($passable, $stack);
};
};
}
protected function prepareDestination($destination) {
return function($passable) use ($destination){
return $destination($passable);
};
}
public function then(Closure $destination) {
//通过array_reduce方法把pips压缩成闭包栈:
//return function($passable) {
// return $pip1->handle($passable, function($passable) {
// return $pip2->handle($passable, function($passable) {
// return $destination->handle($passable, function($passable){
// //destination方法
// });
// });
// });
//};
//执行时通过传入handle的第二个闭包函数就可以层层调用:$next($passable);
$pipline = array_reduce(array_reverse($this->pips), $this->carry(), $this->prepareDestination($destination));
return $pipline($this->passable);
}
}