laravel5.5源码笔记(5、Pipeline管道模式)

Pipeline管道模式,也有人叫它装饰模式。应该说管道是装饰模式的一个变种,虽然思想都是同样的,但这个是闭包的版本,实现方式与传统装饰模式也不太同样。在laravel的源码中算是一个比较核心的设计模式了。
管道模式,或者说装饰模式的思想,就是在不改变原有程序的基础上,能够方便的在已有程序上添加新的功能。php

在说管道模式以前让咱们看一下array_reduce这个函数laravel

mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )

array_reduce() 将回调函数 callback 迭代地做用到 array 数组中的每个单元中,从而将数组简化为单一的值。

array
输入的 arraycallback
mixed callback ( mixed $carry , mixed $item )
        carry
        携带上次迭代里的值; 若是本次迭代是第一次,那么这个值是 initial。

        item
        携带了本次迭代的值。

initial
若是指定了可选参数 initial,该参数将在处理开始前使用,或者当处理结束,数组为空时的最后一个结果。

 

 

以上是php官网对这个函数的解释,看的一头雾水对不对?不要紧,咱们先来一个demo设计模式

 

 

//首先咱们有一个数组
$a = array(1, 2, 3, 4, 5);
$x = array();

//咱们输出了array_reduce的结果为15,这个函数传递了两个参数,第一个就是上面的数组a,咱们重点来看第二个闭包函数
var_dump(array_reduce($a, "sum")); // int(15)

//array_reduce的第三个参数会在循环开始的时候当作闭包函数的第一个carry值传入
var_dump(array_reduce($a, "product", 10)); // int(1200), because: 10*1*2*3*4*5

//当array_reduce处理结束后,若结果为空,也会将第三个参数返回
var_dump(array_reduce($x, "sum", "No data to reduce")); // string(17) "No data to reduce"

//这个闭包函数接收了两个参数
function sum($carry, $item)
{
    echo 'before___carry:'.$carry.'<br><br>';
    echo 'before___item:'.$item.'<br><br>';

    //这个函数只是简单的对两个参数作了加法
    $carry += $item;

    echo 'after___carry:'.$carry.'<br><br>';
    echo '<br><br>';
    return $carry;
}

function product($carry, $item)
{
    echo 'before___carry:'.$carry.'<br><br>';
    echo 'before___item:'.$item.'<br><br>';

    $carry *= $item;

    echo 'after___carry:'.$carry.'<br><br>';
    echo '<br><br>';
    return $carry;
}

你们在运行过这个demo以后,便会发现,array_reduce只是一个循环函数。可是,它和foreach不同的地方在于,它的闭包处理函数所接收的两个参数。其中的$item参数会在每次循环的时候代入数组的各项值,而$carry就比较专注了,它只接收上一次循环中本身的返回值。不过专注也只是相对的,$carray还接收array_reduce传入的第三个参数做为闭包开始循环前$carray的默认值,或是循环结束后返回值为null时的默认值。我在代码中已经把变量各时期的值给打印出来了,相信你们一看就会明白。这个函数最典型的例子就是在作累计运算的时候,咱们不须要向foreach循环那样建立一个变量了。(你们都知道给变量起名字是一件很麻烦的事情)api

ok,函数介绍完了,但是这和管道模式又有什么关系呢?不知道你们注意到没有,在array_reduce的闭包函数进行循环的时候,array_reduce的第三个参数从闭包的$array进入,从return返回,紧接着再次从$array中进入,而每次循环的时候,都会从$item中获取到外部进行运算。这个过程是否是很像数据在一个螺旋管道中流通,同时不断的向其中添加新的操做,而且没有改变原先的程序代码,下降了程序间的耦合度。数组

 接下来进行管道模式浏览器

请求处理管道的思惟导图

思惟导图看的还不够清晰?ok,接着举例子,看demo闭包

从图中咱们能够知道类Request都具备相同的接口,那么咱们能够约定一个接口规范函数

interface RequestInterface
{
    // 咱们以前讲过装饰者模式,这里是经过闭包函数实现
    // 经过以后实现类及调用就能够看出
    public static function handle(Closure $next);
}

 

接口有了那么咱们就遵循接口开始实现this

 

class Request1 implements RequestInterface
{
    public static function handle(Closure $next)
    {
        echo "Request1 Begin." . "<br />";
        $next();
        echo "Request1 End." . "<br />";
    }
}

class Request2 implements RequestInterface
{
    public static function handle(Closure $next)
    {
        echo "Request2 Begin." . "<br />";
        $next();
        echo "Request2 End." . "<br />";
    }
}

class Request3 implements RequestInterface
{
    public static function handle(Closure $next)
    {
        echo "Request3 Begin." . "<br />";
        $next();
        echo "Request3 End." . "<br />";
    }
}

class Request4 implements RequestInterface
{
    public static function handle(Closure $next)
    {
        echo "Request4 Begin." . "<br />";
        $next();
        echo "Request4 End." . "<br />";
    }
}

 

 

四种请求处理过程咱们均已实现,为了简化都是打印一句话,能够在浏览器上直观显示流程;
咱们还须要一个客户端来发出请求spa

 

class Client
{
    // 这里包含了全部的请求
    private $pipes = [
        'Request1',
        'Request2',
        'Request3',
        'Request4',
    ];

    // 这里就是思惟导图中默认返回的匿名回调函数
    private function defaultClosure()
    {
        return function () {
            echo '请求处理中...' . "<br />";
        };
    }

    // 这里就是整个请求处理管道的关键
    private function getSlice()
    {
        return function ($stack, $pipe)
        {
            return function () use ($stack, $pipe)
            {
                return $pipe::handle($stack);
            };
        };
    }

    // 这里是负责发起请求处理
    public function then()
    {
        call_user_func(array_reduce($this->pipes, $this->getSlice(), $this->defaultClosure()));
    }
}

当咱们调用时:

$worker = new Client();
$worker->then();

浏览器就会显示:

那么我来解释一下整个流程吧,在代码注释说了getSlice是关键,那么咱们来解读一下代码

解析代码

 

client的then方法,call_user_func是执行一个函数的api,它的参数是一个闭包函数。而这个参数则是咱们文章一开始提到的array_reduce这个循环的最终返回值。

咱们都还记得这个函数一共有三个参数:

第一个参数表明从外界加入到管道中的附加操做

第二个参数则是执行加工的工厂

第三个参数则是进入管道的程序数据源

 

先看第三个参数数据源

    // 这里就是思惟导图中默认返回的匿名回调函数
    private function defaultClosure()
    {
        //这个return出去的闭包就是咱们的数据源
        return function () {
            echo '请求处理中...' . "<br />";
        };
    }

 而后对程序进行装饰的装饰品数组
// 这里包含了全部的请求
    private $pipes = [
        'Request1',
        'Request2',
        'Request3',
        'Request4',
    ];

这里虽然只是个数组,但它在刚刚的循环闭包中被当作对象名来使用,经过名称调度不一样的类

而后是循环闭包

    // 这里就是整个请求处理管道的关键
    private function getSlice()
    {
        //这个函数咱们的闭包循环函数
        return function ($stack, $pipe) { //而这里即是咱们最开始例子中的return返回值,只不过刚开始的时候咱们返回的是一个数字变量,这里变成了另外一个闭包 return function () use ($stack, $pipe) { return $pipe::handle($stack); }; }; }

这里的循环闭包返回值也是一个闭包,在不断的循环中,闭包函数就像洋葱同样,一层一层不断包裹。

如此类推,最终array_reduce返回一个匿名函数,这就像上面思惟导图最终所描述的同样

function (){
    return Request4::handle(function (){
        return Request3::handle(function (){
            return Request2::handle(function (){
                return Reuqest1::handle(function (){
                    echo '请求处理中...' . "<br />";
                });
            });
        });
    });
}

因此当调用call_user_func时就是执行array_reduce返回的匿名函数,咱们从各个请求处理类的handle方法也得知,会直接调用传入的匿名函数,注意顺序便可理解浏览器的输出。

与装饰者模式的关系

这里没有用到类来对实例对象进行包装,而是经过闭包函数完成,每一次处理在上一次处理之上进行包装,最后得到响应,就像流水线同样,一个请求进来经过一道道工序加工(包装)最后生成响应。

 

理解了这几个demo,那么laravel里中间件最重点的部分你就已经理解了。

 

最后,本文内容并不是所有原创,部分图片与代码来源:http://blog.chenjunwu.cn/2017/05/02/Request-pipeline

相关文章
相关标签/搜索