老司机带你实现 Laravel 之管道

前言

在这篇文章之前,我已经写过一系列关于Laravel的文章了,我觉得他们对于你理解Laravel的整个代码结构来说,具有非常重要的意义,今天咱们继续分析,今天的主角是Pipeline,也就是俗称的管道,代码我已经上传至码云:php-pipeline,代码量并不大,但是请仔细阅读,可能并不好理解,可能有点儿短小精悍的意思吧。

管道

管道在Laravel中扮演了一个极其重要的角色,因为你所有的中间件都是通过管道进行操作的想要理解中间件,必须得理解管道,laravel的管道文件位于vendor/laravel/framework/src/illuminate/Pipeline目录下面:

老司机带你实现Laravel之管道

代码并不多,但是为了给大家讲清楚,我自己实现了一个精简版,上面已经说过了,已经上传到了码云,核心代码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);

老司机带你实现Laravel之管道

这里为了方便,我们把参数设置为1,实际上你可以设置为任何值,这个没关系,我们看到当调用c3的时候,这个时候,我们应该回到Pipeline这个函数的本体了,参数$passable为1,$stack为c2pipenew Unit3(),此时Unit3handle方法被调用,如下:

public function handle($passable, callable $next = null)
{
     echo __CLASS__ . '->' . __METHOD__ . " called\n";
     $next($passable);
}

$passable为1,$next就是c2c2Unit3handle方法中被调用了,那么调用c2的时候呢?$passable为1,$stackc1Unit2handleUnit3handle方法是一模一样的,所以此时c1被调用,c1被调用的时候$passable为1,$stackinitinit就是这个啊:

function ($passable) {
    (new InitialValue())->handle($passable);
}

是不是感觉说清楚了,也很简单啊?

上面就是整个Laravel管道的精髓了,希望大家能够理解。

总结

还是那句话,要想能力比别人强,必须付出异于常人的毅力。欢迎大家加入下面的qq群,平时可以多多交流:

本作品采用《CC 协议》,转载必须注明作者和本文链接

如果有不懂的地方,可以加我的qq:1174332406,或者是微信:itshardjs

本帖由系统于 5个月前 自动加精
Dennis_Ritchie
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 9

先码后看

5个月前 评论

谢谢楼主分享,受教了。 :+1: :+1: :+1:

5个月前 评论
xujun0429

我正在…… 假装自己看得懂……

5个月前 评论

补充一些信息. 什么是 Pipeline, 什么情况使用Pipeline

Pipeline Pattern 管道模式

管道模式是一种封装模式,用于封装顺序流程。

Example

foreach 表示

$result = $payload;

foreach ($stages as $stage) {
    $result = $stage($result);
}

return $result;

等效于

$result = $stage3($stage2($stage1($payload)));

使用 league/pipeline 实现

use League\Pipeline\Pipeline;
use League\Pipeline\StageInterface;

class TimesTwoStage implements StageInterface
{
    public function __invoke($payload)
    {
        return $payload * 2;
    }
}

class AddOneStage implements StageInterface
{
    public function __invoke($payload)
    {
        return $payload + 1;
    }
}

$pipeline = (new Pipeline)
    ->pipe(new TimesTwoStage)
    ->pipe(new AddOneStage);

// Returns 21
$pipeline->process(10);

使用 illuminate/pipeline 实现

//能力不足,写不出来

参考 https://packagist.org/packages/league/pipe...

5个月前 评论
swing07 3个月前

关键在于闭包和函数

4个月前 评论

看懂了,觉着有点链表的感觉

4个月前 评论

怎么看着像是套娃

4个月前 评论

管道的执行顺序和我们加入数组的顺序刚好相反,中间件的调用都是在闭包中的,比如例子中的三个闭包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。

4个月前 评论

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!