老司机带你实现 Laravel 之管道
前言
在这篇文章之前,我已经写过一系列关于Laravel的文章了,我觉得他们对于你理解Laravel的整个代码结构来说,具有非常重要的意义,今天咱们继续分析,今天的主角是Pipeline,也就是俗称的管道,代码我已经上传至码云:php-pipeline,代码量并不大,但是请仔细阅读,可能并不好理解,可能有点儿短小精悍的意思吧。
管道
管道在Laravel中扮演了一个极其重要的角色,因为你所有的中间件都是通过管道进行操作的,想要理解中间件,必须得理解管道,laravel的管道文件位于vendor/laravel/framework/src/illuminate/Pipeline目录下面:
代码并不多,但是为了给大家讲清楚,我自己实现了一个精简版,上面已经说过了,已经上传到了码云,核心代码16行,哈哈,是不是很惊讶!
希望大家一定要把代码下载下来,这样更有助于理解。
function Pipeline($stack, $pipe)
{
return function ($passable) use ($stack, $pipe) {
if (is_callable($pipe)) {
$pipe($passable, $stack);
} elseif (is_object($pipe)) {
$method = "handle";
if (!method_exists($pipe, $method)) {
throw new InvalidArgumentException('object that own handle method');
} else {
$pipe->$method($passable, $stack);
}
} else {
throw new InvalidArgumentException('$pipe must be callback or object');
}
};
}
上面这个就是它的真面目了,下面我们先来看一下如果使用它:
interface TestUnit
{
public function handle($passable, callable $next = null);
}
class Unit1 implements TestUnit
{
public function handle($passable, callable $next = null)
{
echo __CLASS__ . '->' . __METHOD__ . " called\n";
$next($passable);
}
}
class Unit2 implements TestUnit
{
public function handle($passable, callable $next = null)
{
echo __CLASS__ . '->' . __METHOD__ . " called\n";
$next($passable);
}
}
class Unit3 implements TestUnit
{
public function handle($passable, callable $next = null)
{
echo __CLASS__ . '->' . __METHOD__ . " called\n";
$next($passable);
}
}
class InitialValue implements TestUnit
{
public function handle($passable, callable $next = null)
{
echo __CLASS__ . '->' . __METHOD__ . " called\n";
//$next($passable);
}
}
$pipeline = array_reduce([new Unit1(), new Unit2(), new Unit3()], "Pipeline", function ($passable) {
(new InitialValue())->handle($passable);
});
$pipeline(1);
上面我贴出如何使用Pipeline的例子,接下来的讲解就以这个为例,不然很不好理解,在上面的测试代码里面,我声明了一个TestUnit接口,这个接口只有一个方法,就是handle,然后声明了三个实现TestUnit接口的类,分别是Unit1,Unit2,Unit3,它们的handle方法很简单:
echo __CLASS__ . '->' . __METHOD__ . " called\n";
$next($passable);
在PHP中__CLASS__
指代当前调用调用方法所属的类,__METHOD__
指代当前调用的方法,如果我们运行上面的代码,会得到下面的结果:
E:\php-pipeline>php debug.php
Unit3->Unit3::handle called
Unit2->Unit2::handle called
Unit1->Unit1::handle called
InitialValue->InitialValue::handle called
控制台结果和我预想的结果保持一致,舒服!
在讲解上面的代码之前,你一定要知道如何使用array_reduce
方法,如果你不知道,也没关系,可以参考php的官方文档:array_reduce,使用很简单,我就不多说了。
在上面的代码中,我们看是如何是如何使用array_reduce的:
$pipeline = array_reduce([new Unit1(), new Unit2(), new Unit3()], "Pipeline", function ($passable) {
(new InitialValue())->handle($passable);
});
array_reduce
遍历的是有三个元素组成的数组,分别是Unit1,Unit2,Unit3类的实例对象,回调方法是是Pipeline
,也就是我们上面贴出来的函数,初始值为:
function ($passable) {
(new InitialValue())->handle($passable);
}
注意了,它是一个回调方法,我们把它简称为$init
,这个一定要记住。
好了,不多说了,我们开始遍历[new Unit1(), new Unit2(), new Unit3()]
这个数组。
遍历new Unit1()
,此时我们看调用Pipeline的返回结果,是一个回调方法,对不对?为了后面的分析,我们把这个回调记为如下形式(你可以理解为伪代码):
$c1=callback($passable)[$init,new Unit1()]
同样的,我们遍历new Unit2()
,得到的回调签名如下:
$c2=callback($passable)[$c1,new Unit2()]
最后遍历new Unit3()
,伪代码如下:
$c3=callback($passable)[$c2,new Unit3()]
通过上面的测试代码,我们知道array_reduce返回的是$c3
这个回调方法,也就是赋值给了$pipeline
变量,接下来调用c3
这个回调,如下:
$pipeline(1);
这里为了方便,我们把参数设置为1,实际上你可以设置为任何值,这个没关系,我们看到当调用c3
的时候,这个时候,我们应该回到Pipeline
这个函数的本体了,参数$passable
为1,$stack为c2
,pipe
为new Unit3()
,此时Unit3
的handle
方法被调用,如下:
public function handle($passable, callable $next = null)
{
echo __CLASS__ . '->' . __METHOD__ . " called\n";
$next($passable);
}
$passable
为1,$next
就是c2
,c2
在Unit3
的handle
方法中被调用了,那么调用c2
的时候呢?$passable
为1,$stack
为c1
,Unit2
的handle
和Unit3
的handle
方法是一模一样的,所以此时c1
被调用,c1
被调用的时候$passable
为1,$stack
为init
,init
就是这个啊:
function ($passable) {
(new InitialValue())->handle($passable);
}
是不是感觉说清楚了,也很简单啊?
上面就是整个Laravel管道的精髓了,希望大家能够理解。
总结
还是那句话,要想能力比别人强,必须付出异于常人的毅力。欢迎大家加入下面的qq群,平时可以多多交流:
本作品采用《CC 协议》,转载必须注明作者和本文链接
高认可度评论:
补充一些信息. 什么是
Pipeline
, 什么情况使用Pipeline
Pipeline Pattern 管道模式
管道模式是一种封装模式,用于封装顺序流程。
Example
用
foreach
表示等效于
使用
league/pipeline
实现使用
illuminate/pipeline
实现先码后看
谢谢楼主分享,受教了。 :+1: :+1: :+1:
我正在…… 假装自己看得懂……
补充一些信息. 什么是
Pipeline
, 什么情况使用Pipeline
Pipeline Pattern 管道模式
管道模式是一种封装模式,用于封装顺序流程。
Example
用
foreach
表示等效于
使用
league/pipeline
实现使用
illuminate/pipeline
实现关键在于闭包和函数
看懂了,觉着有点链表的感觉
怎么看着像是套娃
管道的执行顺序和我们加入数组的顺序刚好相反,中间件的调用都是在闭包中的,比如例子中的三个闭包c1,c2,c3.array_reduce就是把第一个闭包放到后面一个闭包中,也就是c1放到c2中,c2放到c3中。array_reduce最终返回的是c3。c3肚子里面装着c2, c2肚子里面装着c1。执行c3,运行到$next(),相当于是c3调用c2。同理c2执行到$next()就是调用c1。也就是函数的嵌套调用。c1的return结果会返回给c2,c2的return结果返回给c3。
没看懂。。。
c3执行完成调用c2然后调用c1,套娃呀