pipeline在laravel的启动过程中出现次数很多,要了解laravel的启动过程和生命周期,理解pipeline就是其中的一个关键点。网上对pipeline的讲解很少,所以我自己写一写吧。
首先还是来看看调用栈,也就是从一个请求开始到返回响应,laravel都干了些什么。
在路由中配置如下
Route::get('/', function() {
return debug_backtrace();
});
然后启动laravel自带的web server
php artisan serve
启动laravel自带的serve
然后访问localhost:8000/
可以看到打印的调用栈,很长……
打印调用栈
左下角可以看出,从请求开始到结束,尽管什么都没干,但是依然加载了39个类……那些追求卓越性能的同学们需要好好考虑一下……
我们的重点是pipeline,如果你按照上面的步骤看了,会发现有一个pipeline调用的次数非常多。
那它到底是做什么用的呢?
简单一点讲,它是为了实现Laravel中的middleware。仔细想想middleware的代码
public function handle($request, Closure $next) {
//do something for $request
return $next($request);
}
一个请求经历层层的中间件的处理,才得到最终的请求,这到底是实现的呢?答案就是pipeline
首先来宏观感受下pipeline,到底怎么用的,再去细说它
pipeline在源码的使用
可以看到,主要有3个方法,也是pipeline暴露给使用者的所有方法
- send
- through
- then
send方法
/**
* Set the object being sent through the pipeline.
*
* @param mixed $passable
* @return $this
*/
public function send($passable) {
$this->passable = $passable;
return $this;
}
从说明看就是被在pipeline中传递的对象
,可能不太好理解。那么我们就假定pipeline使用在middleware中,那么这个send的对象就是$request
想想看上面的中间件代码,每个handle
方法接收的第一个参数是$request
,其实就是这儿设置的讲被send的对象,它会在之后被到处传递,A处理完了送给B处理再送给C处理……pipeline,形象吧
through
屏幕快照 2015-09-07 下午9.05.36.png
顾名思义:通过、经由,就是上面send
设置的对象要经由哪些中间件来处理
上面截图宏观说明pipeline的用法的时候可以看到,在调用through的时候
(new Pipeline($this))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
看through
里传的参数,很好懂,如果当前请求我们设置了不需要中间件,那么就传一个空数组,不然就传递我们预先定义好的中间件数组
pipeline里的代码如下
/**
* Set the array of pipes.
*
* @param dynamic|array $pipes
* @return $this
*/
public function through($pipes){
$this->pipes = is_array($pipes) ? $pipes : func_get_args();
return $this;
}
这个看起来也很简单,你要么传一个数组,要么传多个字符串,它会拼成一个数组,无所谓,反正指明中间件的名字就好
then
接下来就是核心,也是代码晦涩的地方了。then
方法就是当一切准备就绪后,下达开始处理的命令。
我们前面也能看到,send
和through
都是设置,并没有做其他什么,所以当 要传递的对象、需要经历的中间件都设置好之后,then
?当然就是开始处理咯!
还是先看看then
的代码
/** * Run the pipeline with a final destination callback.
*
* @param \Closure $destination
* @return mixed
*/
public function then(Closure $destination){
$firstSlice = $this->getInitialSlice($destination);
$pipes = array_reverse($this->pipes);
return call_user_func(
array_reduce($pipes, $this->getSlice(), $firstSlice),
$this->passable);
);
}
看起来短短几行,但是要理解起来可不太容易(这也是我比较喜欢laravel的原因,代码简单而优雅,一般人欣赏不来,哈哈)
我先大概讲一下then
方法需要接收一个匿名函数
$destination,至于什么样的匿名函数,你先可以不用管,往后看。我一句一句解释
public function then(Closure $destination){
//把传进来的匿名函数包装成一个闭包,不太理解没关系,反正知道$firstSlice是一个函数就好了
$firstSlice = $this->getInitialSlice($destination);
//把through方法传进来的参数,也就是中间件数组反转,为什么要这么做?
//简单讲是利用栈的特性,继续往下看就知道了
$pipes = array_reverse($this->pipes);
//array_reduce(...)返回一个函数A,然后执行这个函数,把send进来的对象当参数传递函数A
return call_user_func(
array_reduce($pipes, $this->getSlice(), $firstSlice),
$this->passable);
);
}
然后呢?
没有然后了,这就完成了整个工作,是不是很优雅(坑爹)的代码,哈哈
那我们的精力就放到这个array_reduce
上面,它到底返回了一个什么神奇的函数,我只要把$request传递给它,就过完了所有中间件?array_reduce($arr, 'funcA',5)
大致等价于,第二个参数可以是函数名的字符串
function fake_array_reduce($arr, $func, $initial=null)
$temp = $initial;
foreach($arr as $v) {
$temp = $func($temp, $v);
}
return $temp;
}
理解意思即可
回到then
的源码,代码中传递的第二个参数一个是一个函数,而它是通过执行$this->getSlice()
获得的一个函数,我们来看看这个getSlice
方法
/**
* Get a Closure that represents a slice of the application onion.
*
* @return \Closure
*/
protected function getSlice(){
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if ($pipe instanceof Closure) {
return call_user_func($pipe, $passable, $stack);
} else {
list($name, $parameters) = $this->parsePipeString($pipe);
return call_user_func_array(
[$this->container->make($name), $this->method],
array_merge([$passable, $stack], $parameters)
);
}
};
};
}
刚看到这里我也有点头晕,这闭包套闭包的,什么鬼!
我们在简化一下,就是调用$this->getSlice()
之后得到了一个函数,假设是函数B
,函数B
接收两个参数,并返回一个函数C
我们来大致模拟一下这个过程
写一段假代码,仅仅是为了更好地说明这个过程:
function haha($stack, $middleware) {
return function($param) use ($stack, $middleware){
//do something
};
}
function fake_array_reduce($middlewares, $func, $inital=null) {
$temp = $initial;
//假设传递进来的函数$func = 'haha'
foreach($middlewares as $middleware) {
$temp = $func($temp, $middleware);
}
return $temp;
}
这个过程的基本流程如上,haha
返回了一个空函数(实际上肯定不是),我只是为了说明每次都返回一个函数作为下一次迭代的参数,而并没有执行任何其它功能,按照上面的代码,最后执行完fake_array_reduce
会得到一个函数,这个函数接收一个参数。
所以再回过头来看call_user_func(array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable);
实际上就是相当于调用了array_reduce
最后一次迭代返回的函数,然后给这个函数传递了一个参数$this->passable
所以,这时候我们需要关注最后一次返回的函数是什么,看看源码关键是这段
return function($stack, $pipe) {
return function($passable) use ($stack, $pipe) {
if($pipe instanceof Closure) {
return call_user_func($pipe, $passable, $stack);
} else {
//..
}
}
}
每次迭代传入了当前的$pipe和上一次迭代返回的函数
IMG_0687.JPG
由上图所示
由于把pipes先反转了一下,所以最后一次迭代array_reduce
得到的函数f3
所use的是($stack=f2, $pipe=$middleware[0])
那么call_user_func(f3,$this->passable)
相当于是
return call_user_func($middleware[0]->handle, $this->passable, f2);
仔细想想,middleware里面的handle方法
public function handle($request, Closure $next) {
//...
return $next($request);
}
在这里就相当于就是return f2($request)
也就相当于return call_user_func($middleware[1]->handle, $request, f1)
而这个$request是经过middleware[0]处理过后传递过来的。
……
这个过程比较绕,需要大家自己再画图理解一下,比如源代码里用到的onion(洋葱),$stack(栈),这些单词可以帮助大家更好地理解这个过程。
所以return call_user_func($pipes, $this->getSlice(), $this->passable)
就会把$request
请求按middleware定义的先后顺序,流水线处理,中间件处理完了之后再传递给$this->dispatchToRouter()
这个方法处理(这又是一个抽象),从而得到最后的响应结果。
理解pipeline需要理解 闭包 栈等概念,熟悉了pipeline会对理解php的新特性有比较大的帮助。