标签归档:管道

Laravel 管道详解

函数http://php.net/array_reduce用法(http://php.net/array_reduce
):

// array_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。
mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )

第二参数的$callback调用格式:

//$carry携带上次迭代里的值; 如果本次迭代是第一次,那么这个值是 initial
//$item携带了本次迭代的值
mixed callback ( mixed $carry , mixed $item )

1 如果$array为空,直接返回$initial
2 如果$array不为空,那么$initial作为第一次迭代时的$carry
3 每次迭代调用的$callback的结果作为下次迭代调用$callback的$carry

这里主要是第三点,经过特殊处理,可以产生多层嵌套(C包装了B,B包装了A)。

列子:

$arr = [3, 2, 1];
$arr = [];
$fn = function ($carry, $hold) {
    return function() use ($carry, $hold) {
        echo "$hold -- Start\n";
        $carry();
        echo "$hold -- End \n";
    };
};
$init = function () {
    echo "init\n";
};
$callback = array_reduce($arr, $fn, $init);
$callback();

输出:

// $arr等于空时
init

// $arr = [3,2,1]时
1 -- Start
2 -- Start
3 -- Start
init
3 -- End 
2 -- End 
1 -- End

各种情况分析:
1 当$arr为空时,直接返回$init,这里返回一个闭包函数,执行后直接输出”init”字符串
2 当$arr等于[3]时,第一个次迭代,相当于如下代码:

// 相当于
$fn($init, 3);
// 相当于
$fn(function () {
    echo "init\n";
}, 3);
// 相当于
function (function () {
    echo "init\n";
}, 3);
// 相当于
function() use(function () {
    echo "init\n";
}, 3) {
    echo "$hold -- Start\n";
    (function () {
        echo "init\n";
    })();
    echo "$hold -- End \n";
}
// 最后运行这个闭包,输出
3 -- Start
init
3 -- End

3 对于$arr大于1的情况也是类似,最终会得到类似这样的结构

(function() use($carray, 1) {
    echo "1 -- Start\n";
    (function () use ($array, 2) {
        echo "2 -- Start\n";
        (function () use($carray, 1) {
            echo "3 -- Start\n";
            (function () {
                echo "init\n";
            })();
            echo "3 -- End\n";
        })();
        echo "2 -- End\n";
    })();
    echo "1 -- End\n";
})();

使用闭包,把每次迭代过程中的变量锁定,执行时,从外层逐层剥离。这个就是管道的实现方式。

在Laravel中,包Illuminate\Pipeline的管道实现原理与以上的描述类似。而管道的应用,最经典的就是中间件的设计。请求在分发到具体的路由前(或之后),可以设置其经过一些列的中间,要经过的中间件,就是一个个管道,在任何时刻,都可以非常无痛的插入和删除某个中间件,以下是模拟过程:

class VerifyCsrfToken
{
    public static function handle(Closure $next)
    {
        echo "VerifyCsrfToken\n";
        $next();
    }
}

class ShareErrorsFromSession
{
    public static function handle(Closure $next)
    {
        echo "ShareErrorsFromSession\n";
        $next();
    }
}

class StartSession
{
    public static function handle(Closure $next)
    {
        echo "StartSession -- Start\n";
        $next();
        echo "StartSession -- End\n";
    }
}

class AddQueuedCookiesToResponse
{
    public static function handle(Closure $next)
    {
        $next();
        echo "AddQueuedCookiesToResponse\n";
    }
}

class EncryptCookies
{
    public static function handle(Closure $next)
    {
        echo "EncryptCookies -- Start\n";
        $next();
        echo "EncryptCookies -- End\n";
    }
}

class CheckForMaintenanceMode
{
    public static function handle(Closure $next)
    {
        echo "CheckForMaintenanceMode\n";
        $next();
    }
}

class pipeline
{
    protected $hold = null;

    protected $pipes = [];

    public function send($hold)
    {
        $this->hold = $hold;
        return $this;
    }

    public function though(array $pipes)
    {
        $this->pipes = $pipes;
        return $this;
    }

    public function then(Closure $to)
    {
        $fn = function ($stack, $pipe) {
            return function() use ($stack, $pipe) {
                return $pipe::handle($stack);
            };
        };
        $callBack = array_reduce($this->pipes, $fn, $to);

        return $callBack();
    }
}

//////////////////////////////////////
$hold = [];
$pipes = array_reverse([
//    "CheckForMaintenanceMode",
//    "EncryptCookies",
//    "AddQueuedCookiesToResponse",
//    "StartSession",
//    "ShareErrorsFromSession",
//    "VerifyCsrfToken"
]);
$result = (new Pipeline())->though($pipes)->then(function () {
    echo "--------Request to Route------------\n";
});

这样,我们自定义的中间件(数组顺序),如果希望在路由前运行,就在$next()前执行,如果希望路由后执行,就在$next()后运行。当然,在执行某个中间件时(管道),也可以直接return,这样后面的代码就不会执行。

以上的演示代码已经把管道的实现基本说清楚,以下是Illuminate\Pipeline的用法:

<?php

require __DIR__.'/../vendor/autoload.php';

class MyPipe
{
    public function handle($request, $next, $param1 = 1, $param2 = 2)
    {
        echo "MyPipe Handle -- request is $request, param1 is $param1, param2 is $param2 \n";
        $request++;
        $next($request);
    }

    public function fire($request, $next, $param1 = 1, $param2 = 2)
    {
        echo "MyPipe Fire -- request is $request, param1 is $param1, param2 is $param2 \n";
        $request++;
        $next($request);
    }
}
$container = new Illuminate\Container\Container();

$request = 10;
// 三种使用方式
$pipes = [
    // 直接传递一个闭包
    function($request, $next) {
        $request++;
        $next($request);
    },
    // 直接生成一个包含"handle方法的对象",无法传递额外参数
    new MyPipe(),
    // 管道对象生成时需要把容器对象传入,因为需要依靠容器根据字符串类make对象
    // 直接指定一个包含"handle"方法的类名称
    // handle方法如果有超过2个参数,超过的参数可以在冒号后给出,并以逗号分隔多个参数
    "MyPipe:11,22"
];

// 更换默认调用的方法
$pipeline = new Illuminate\Pipeline\Pipeline($container);
$pipeline->via("fire")->send($request)->through($pipes)->then(function ($request) {
    echo "request is $request\n";
    echo "Finish ---\n";
});

// 使用默认的handle方法
$pipeline = new Illuminate\Pipeline\Pipeline($container);
$pipeline->send($request)->through($pipes)->then(function ($request) {
    echo "request is $request\n";
    echo "Finish ---\n";
});

// 输出
MyPipe Fire -- request is 11, param1 is 1, param2 is 2 
MyPipe Fire -- request is 12, param1 is 11, param2 is 22 
request is 13
Finish ---

MyPipe Handle -- request is 11, param1 is 1, param2 is 2 
MyPipe Handle -- request is 12, param1 is 11, param2 is 22 
request is 13
Finish ---

闭包函数应用:

$destination = function () {};

$fn = function ($passable) use ($destination) {
    return $destination($passable);
};
$passable = 1;
// $destination() 和$destination($passable)等同
// $destination($passable) 和 $fn($passable)等同

当要把一个闭包以一个统一的格式执行时(总是带一个或多个参数),通常需要做这样的转换。$destination()这个闭包函数定义时不需要传递参数,但是在调用时传递传输参数过去也不会报错。

demo($destination) 
{
    return function ($passable) use ($destination) {
        return $destination($passable);
    }
}
//
demo($destination) === function ($passable) use ($destination) {
    return $destination($passable);
};
// 调用时就必须是fn(param)格式,外部参数注入到了闭包,闭包中就可以使用到外部传递进来的参数。

传递一个闭包函数给一个函数,这个函数返回了一个包装了这个闭包函数的闭包函数,这样外部的闭包函数执行,就可以把外部的变量传递给内部的闭包函数。

其它管道实现参考:
https://github.com/thephpleague/pipeline