理解Laravel中的pipeline
jopen
9年前
<p>pipeline在laravel的启动过程中出现次数很多,要了解laravel的启动过程和生命周期,理解pipeline就是其中的一个关键点。网上对pipeline的讲解很少,所以我自己写一写吧。<br /> 首先还是来看看调用栈,也就是从一个请求开始到返回响应,laravel都干了些什么。<br /> 在路由中配置如下</p> <pre class="brush:php; toolbar: true; auto-links: false;">Route::get('/', function() { return debug_backtrace(); });</pre> <p>然后启动laravel自带的web server</p> <pre class="brush:php; toolbar: true; auto-links: false;">php artisan serve</pre> <div class="image-package" href="https://simg.open-open.com/show/efe019d533c7c507679665f0e1608e73.png"> <img src="https://simg.open-open.com/show/efe019d533c7c507679665f0e1608e73.png" width="700" height="24.561403508771928" data-original-src="https://simg.open-open.com/show/d1255e22812ef571868f1b5bcbc0d2b7.png" /> <br /> <div class="image-caption"> 启动laravel自带的serve </div> </div> <p><br /> 然后访问<code>localhost:8000/</code><br /> 可以看到打印的调用栈,很长……</p> <div class="image-package" href="https://simg.open-open.com/show/bf41ca60a2dbf6d1251b0ec420da810e.png"> <img src="https://simg.open-open.com/show/bf41ca60a2dbf6d1251b0ec420da810e.png" width="377" height="792" data-original-src="https://simg.open-open.com/show/42df9f783ecde9930569d0e184510a58.png" /> <br /> <div class="image-caption"> 打印调用栈 </div> </div> <p><br /> 左下角可以看出,从请求开始到结束,尽管什么都没干,但是依然加载了39个类……那些追求卓越性能的同学们需要好好考虑一下……<br /> 我们的重点是pipeline,如果你按照上面的步骤看了,会发现有一个pipeline调用的次数非常多。<br /> 那它到底是做什么用的呢?<br /> 简单一点讲,它是为了实现Laravel中的middleware。仔细想想middleware的代码</p> <pre class="brush:php; toolbar: true; auto-links: false;">public function handle($request, Closure $next) { //do something for $request return $next($request); }</pre> <p>一个请求经历层层的中间件的处理,才得到最终的请求,这到底是实现的呢?答案就是pipeline<br /> 首先来宏观感受下pipeline,到底怎么用的,再去细说它<br /> </p> <div class="image-package" href="https://simg.open-open.com/show/a5af6c82b0fe031282f67401f4eef415.png"> <img src="https://simg.open-open.com/show/a5af6c82b0fe031282f67401f4eef415.png" width="700" height="90.4572564612326" data-original-src="https://simg.open-open.com/show/0a4d1a7df2bef4edfd3c558294700b56.png" /> <br /> <div class="image-caption"> pipeline在源码的使用 </div> </div> <p><br /> 可以看到,主要有3个方法,也是pipeline暴露给使用者的所有方法</p> <ul> <li>send</li> <li>through</li> <li>then</li> </ul> <h3>send方法</h3> <pre class="brush:php; toolbar: true; auto-links: false;">/** * Set the object being sent through the pipeline. * * @param mixed $passable * @return $this */ public function send($passable) { $this->passable = $passable; return $this; }</pre> <p>从说明看就是<code>被在pipeline中传递的对象</code>,可能不太好理解。那么我们就假定pipeline使用在middleware中,那么这个send的对象就是<code>$request</code><br /> 想想看上面的中间件代码,每个<code>handle</code>方法接收的第一个参数是<code>$request</code>,其实就是这儿设置的讲被send的对象,它会在之后被到处传递,A处理完了送给B处理再送给C处理……pipeline,形象吧</p> <h3>through</h3> <div class="image-package" href="https://simg.open-open.com/show/60faee48d6d974b65b9a9cfcc89fe3ab.png"> <img src="https://simg.open-open.com/show/60faee48d6d974b65b9a9cfcc89fe3ab.png" width="271" height="71" data-original-src="https://simg.open-open.com/show/0067e892b98f3143ec0c06c1affa8640.png" /> <br /> <div class="image-caption"> 屏幕快照 2015-09-07 下午9.05.36.png </div> </div> <p><br /> 顾名思义:通过、经由,就是上面<code>send</code>设置的对象要经由哪些中间件来处理<br /> 上面截图宏观说明pipeline的用法的时候可以看到,在调用through的时候</p> <pre class="brush:php; toolbar: true; auto-links: false;">(new Pipeline($this)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter());</pre> <p>看<code>through</code>里传的参数,很好懂,如果当前请求我们设置了不需要中间件,那么就传一个空数组,不然就传递我们预先定义好的中间件数组<br /> pipeline里的代码如下</p> <pre class="brush:php; toolbar: true; auto-links: false;">/** * 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; }</pre> <p>这个看起来也很简单,你要么传一个数组,要么传多个字符串,它会拼成一个数组,无所谓,反正指明中间件的名字就好</p> <h3>then</h3> <p>接下来就是核心,也是代码晦涩的地方了。<br /> <code>then</code>方法就是当一切准备就绪后,下达开始处理的命令。<br /> 我们前面也能看到,<code>send</code>和<code>through</code>都是设置,并没有做其他什么,所以当 要传递的对象、需要经历的中间件都设置好之后,<code>then</code>?当然就是开始处理咯!<br /> 还是先看看<code>then</code>的代码</p> <pre class="brush:php; toolbar: true; auto-links: false;">/** * 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); ); }</pre> <p>看起来短短几行,但是要理解起来可不太容易(这也是我比较喜欢laravel的原因,代码简单而优雅,一般人欣赏不来,哈哈)<br /> 我先大概讲一下<br /> <code>then</code>方法需要接收一个<code>匿名函数</code> <em>$destination</em>,至于什么样的匿名函数,你先可以不用管,往后看。我一句一句解释</p> <pre class="brush:php; toolbar: true; auto-links: false;">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); ); }</pre> <p>然后呢?<br /> 没有然后了,这就完成了整个工作,是不是很优雅(坑爹)的代码,哈哈<br /> 那我们的精力就放到这个<code>array_reduce</code>上面,它到底返回了一个什么神奇的函数,我只要把$request传递给它,就过完了所有中间件?<br /> <code>array_reduce($arr, 'funcA',5)</code>大致等价于,第二个参数可以是函数名的字符串</p> <pre class="brush:php; toolbar: true; auto-links: false;">function fake_array_reduce($arr, $func, $initial=null) $temp = $initial; foreach($arr as $v) { $temp = $func($temp, $v); } return $temp; }</pre> <p>理解意思即可<br /> 回到<code>then</code>的源码,代码中传递的第二个参数一个是一个函数,而它是通过执行<code>$this->getSlice()</code>获得的一个函数,我们来看看这个<code>getSlice</code>方法</p> <pre class="brush:php; toolbar: true; auto-links: false;">/** * 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) ); } }; }; }</pre> <p>刚看到这里我也有点头晕,这闭包套闭包的,什么鬼!<br /> 我们在简化一下,就是调用<code>$this->getSlice()</code>之后得到了一个函数,假设是<code>函数B</code>,<code>函数B</code>接收两个参数,并返回一个<code>函数C</code><br /> 我们来大致模拟一下这个过程<br /> 写一段假代码,仅仅是为了更好地说明这个过程:</p> <pre class="brush:php; toolbar: true; auto-links: false;">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; }</pre> <p>这个过程的基本流程如上,<code>haha</code>返回了一个空函数(实际上肯定不是),我只是为了说明每次都返回一个函数作为下一次迭代的参数,而并没有执行任何其它功能,按照上面的代码,最后执行完<code>fake_array_reduce</code>会得到一个函数,这个函数接收一个参数。<br /> 所以再回过头来看<br /> <code>call_user_func(array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable);</code><br /> 实际上就是相当于调用了<code>array_reduce</code>最后一次迭代返回的函数,然后给这个函数传递了一个参数<code>$this->passable</code><br /> 所以,这时候我们需要关注最后一次返回的函数是什么,看看源码关键是这段</p> <pre class="brush:php; toolbar: true; auto-links: false;"> return function($stack, $pipe) { return function($passable) use ($stack, $pipe) { if($pipe instanceof Closure) { return call_user_func($pipe, $passable, $stack); } else { //.. } } }</pre> <p>每次迭代传入了当前的$pipe和上一次迭代返回的函数</p> <div class="image-package" href="https://simg.open-open.com/show/34d83510362793116a6170eb2aeacdba.jpg"> <img src="https://simg.open-open.com/show/34d83510362793116a6170eb2aeacdba.jpg" width="700" height="525.0" data-original-src="https://simg.open-open.com/show/28bfdc1bb4603ae4381418a99c327cde.jpg" /> <br /> <div class="image-caption"> IMG_0687.JPG </div> </div> <p><br /> 由上图所示<br /> 由于把pipes先反转了一下,所以最后一次迭代<code>array_reduce</code>得到的函数<code>f3</code>所use的是($stack=f2, $pipe=$middleware[0])<br /> 那么<code>call_user_func(f3,$this->passable)</code>相当于是</p> <pre class="brush:php; toolbar: true; auto-links: false;">return call_user_func($middleware[0]->handle, $this->passable, f2);</pre> <p>仔细想想,middleware里面的handle方法</p> <pre class="brush:php; toolbar: true; auto-links: false;">public function handle($request, Closure $next) { //... return $next($request); }</pre> <p>在这里就相当于就是<code>return f2($request)</code><br /> 也就相当于<code>return call_user_func($middleware[1]->handle, $request, f1)</code><br /> 而这个$request是经过middleware[0]处理过后传递过来的。<br /> ……<br /> 这个过程比较绕,需要大家自己再画图理解一下,比如源代码里用到的onion(洋葱),$stack(栈),这些单词可以帮助大家更好地理解这个过程。<br /> 所以<code>return call_user_func($pipes, $this->getSlice(), $this->passable)</code>就会把<code>$request</code>请求按middleware定义的先后顺序,流水线处理,中间件处理完了之后再传递给<code>$this->dispatchToRouter()</code>这个方法处理(这又是一个抽象),从而得到最后的响应结果。</p> <p>理解pipeline需要理解 闭包 栈等概念,熟悉了pipeline会对理解php的新特性有比较大的帮助。</p> <h5>That's all thanks</h5> <p>来自: <a href="/misc/goto?guid=4959653397615900153" rel="nofollow" target="_blank">http://www.jianshu.com/p/3c2791a525d0</a></p>