之前自己研究laravel5的管道代码,只能懵懂。后来又忘记了。
今天看到《Laravel框架关键技术解析》6.2.2 请求管道处理,讲解的比较详细,把复杂的管道简化后容易理解其处理逻辑。
为了加深印象,彻底理解管道模式,必须在本机写代码进行测试,并把代码做了些改进,方便理解。书里面是从最复杂的管道到最简化的管道。个人觉得还是从最简化版开始学会比较容易接收和消化。
学习方法:第一次学习的时候,光看代码没什么效果,最好复制代码去自己的机器上跑一跑。咱这行最重要的是实践,光学理论很容易忘记,也无法消化。
【简化版】pipe.php
<?php
interface Step{
public static function go(Closure $next);
}
class FirstStep implements Step{
public static function go(Closure $next)
{
echo "第一步开始", nl2br(PHP_EOL);
$next();
echo "第一步结束", nl2br(PHP_EOL);
}
}
function goFun($step, $className){
return function () use ($step, $className){
return $className::go($step);
};
}
function then(){
$steps = [
'FirstStep'
];
$prepare = function(){
echo "初始化开始", nl2br(PHP_EOL);
};
$go = array_reduce($steps, 'goFun', $prepare); //获得一个回调函数
$go();
}
then();
<<<OUTPUT
运行后输出:
第一步开始
初始化开始
第一步结束
OUTPUT这个管道只有一个处理过程,最难理解也是最关键之一是 array_reduce($steps, "goFun", $prepare) 函数和 goFun($step, $className)函数。
其中 array_reduce() 函数估计大部分人都没用过。点击查看 array_reduce() 有关PHP手册介绍, array_reduce() 的第一个是要处理的数组,第二个是处理函数名称或回调函数,第三个参数为可选参数,为初始化参数,就是第一个被传入管道的“数据对象”,按装饰器模式解释就是“被装饰对象”。
goFun($step, $className) 函数中 $step 就是上一个或初始化传入的结果,$className,当前遍历的数组值。通过 array_reduce() 把数组 $steps 里面的类,迭代调用 goFun() 函数处理。而 goFun() 返回的是一个回调函数,所以相当于一个公式,把每个类的处理嵌套到另外一个匿名函数里面。类似“堆”。
下面的例子增加了2个执行步骤,更直观的看到执行顺序: 【中等版】pipe2.php
<?php
/**
* Created by PhpStorm.
* User: peter
* Date: 2017/5/12
* Time: 14:33
*
* 管道原理总结:
* 1、类似装饰器模式,自动按照顺序装饰第一个传入的对象。
* 2、每次装饰都是以回调函数的方式,代入下一个回调函数,等待最后统一执行。
* 3、回调函数“代入”后,就形成堆一样的结构(后进先出),最后“代入”的回调函数优先执行。
* laravel形象的把管道比喻成洋葱,先长出的层在里面,后长出的层在外面,但是拨的时候,必须从外面开始拨。
* 4、管道的执行顺序可以自己安排,不一定是正序或倒序执行,根据每个逻辑处理的优先情况,
* 关键在 go() 执行的时候,是否让上一个回调函数先执行,如果让上一个回调函数先执行,则自己的逻辑将排在上一个回调函数的后面,反之在前面。
*
*/
define('SUOJIN', '     ');
//计数器
final class count{
static $count = 0;
static function showRunStep(){
return '排第 '.++self::$count.' 个执行:';
}
}
interface Step{
public static function go(Closure $next);
}
class FirstStep implements Step{
public static function go(Closure $next)
{
echo count::showRunStep(),str_repeat(SUOJIN, 1),"第一步开始", nl2br(PHP_EOL); //先自己执行
$next(); //再让上一个匿名函数执行
echo count::showRunStep(),str_repeat(SUOJIN, 1),"第一步结束", nl2br(PHP_EOL); //等上一个匿名函数执行完,再执行
}
}
class SecondStep implements Step{
public static function go(Closure $next)
{
echo count::showRunStep(),str_repeat(SUOJIN, 2),"第二步开始", nl2br(PHP_EOL);
$next();
echo count::showRunStep(),str_repeat(SUOJIN, 2),"第二步结束", nl2br(PHP_EOL);
}
}
function goFun($step, $className){
return function () use ($step, $className){
return $className::go($step);
};
}
function then(){
$steps = [
'FirstStep',
'SecondStep',
];
$prepare = function(){
echo count::showRunStep(),"初始化开始", nl2br(PHP_EOL);
};
//$steps = array_reverse($steps); //使用数组倒序后,可以优先执行 FirstStep
$go = array_reduce($steps, 'goFun', $prepare); //获得一个回调函数(包含多层的匿名函数)
$go();
}
then();
执行输出:
<<<OUTPUT
排第 1 个执行: 第二步开始
排第 2 个执行: 第一步开始
排第 3 个执行:初始化开始
排第 4 个执行: 第一步结束
排第 5 个执行: 第二步结束
OUTPUT【管道原理总结】:
1、类似装饰器模式,自动按照顺序装饰第一个传入的对象。
2、每次装饰都是以回调函数的方式,代入下一个回调函数,等待最后统一执行。
3、回调函数代入后,就形成堆一样的结构(后进先出),最后执行的回调函数优先执行。 laravel形象的把管道比喻成洋葱,先长出的层在里面,后长出的层在外面,但是拨的时候,必须从外面开始拨。
4、管道的执行顺序可以自己安排,不一定是正序或倒序执行,根据每个逻辑处理的优先情况, 关键在 go() 执行的时候,是否让上一个回调函数先执行,如果让上一个回调函数先执行,则自己的逻辑将排在上一个回调函数的后面,反之在前面。
最后在看看比较接近Laravel管道复制的模式例子(Laravel目前管道更复杂) 【复杂版】pipe3.php
<?php
/**
* Created by PhpStorm.
* User: peter
* Date: 2017/5/12
* Time: 13:43
*
* 管道的执行顺序可以自己安排,不一定是正序或倒序执行,根据每个逻辑处理的优先情况
*
*/
define('SUOJIN', '     ');
//计数器
final class count{
static $count = 0;
static function showRunStep(){
return '排第 '.++self::$count.' 个执行:';
}
}
//接口
interface Middleware
{
public static function handle(Closure $next);
}
class VerifyCsrfToken implements Middleware
{
public static function handle(Closure $next)
{
echo count::showRunStep(),str_repeat(SUOJIN, 6),"[pipes排序:6]【VerifyCsrfToken】【即将执行上一个回调函数(初始化回调函数)】[验证 Csrf-Token]", nl2br(PHP_EOL);
$next(); //执行上一个回调函数(初始化回调函数)
}
}
class ShareErrorsFromSession implements Middleware
{
public static function handle(Closure $next)
{
echo count::showRunStep(),str_repeat(SUOJIN, 5),"[pipes排序:5]【ShareErrorsFromSession】【即将执行上一个回调函数(VerifyCsrfToken)】[如果 session 中有 errors 变量,则共享它]", nl2br(PHP_EOL);
$next(); //执行上一个回调函数( pipes排序=6 )
}
}
class StartSession implements Middleware
{
public static function handle(Closure $next)
{
// TODO: Implement handle() method.
echo count::showRunStep(),str_repeat(SUOJIN, 4),"[pipes排序:4]【StartSession】【即将执行上一个回调函数(ShareErrorsFromSession)】[开启 session,获取数据]", nl2br(PHP_EOL);
$next(); //执行上一个回调函数(pipes排序=5)
echo count::showRunStep(),str_repeat(SUOJIN, 4),"[pipes排序:4]【StartSession】[保存数据,关闭 session]", nl2br(PHP_EOL);
}
}
class AddQueueCookiesToResponse implements Middleware
{
public static function handle(Closure $next)
{
// TODO: Implement handle() method.
$next(); //先执行上一个:4(因为用 array_reverse 倒序了)
echo count::showRunStep(),str_repeat(SUOJIN, 3),"[pipes排序:3]【AddQueueCookiesToResponse】【执行完(StartSession)后在执行本函数】[添加下一次需要的cookie]", nl2br(PHP_EOL);
}
}
class EncryptCookies implements Middleware
{
public static function handle(Closure $next)
{
// TODO: Implement handle() method.
echo count::showRunStep(),str_repeat(SUOJIN, 2),"[pipes排序:2]【EncryptCookies】【即将执行上一个回调函数(AddQueueCookiesToResponse)】[对输入请求的 cookie 进行解密]", nl2br(PHP_EOL);
$next(); //先执行上一个:3(因为用 array_reverse 倒序了)
echo count::showRunStep(),str_repeat(SUOJIN, 2),"[pipes排序:2]【EncryptCookies】【执行完(AddQueueCookiesToResponse)后在执行本函数】[对输出响应的 cookie 进行加密]", nl2br(PHP_EOL);
}
}
class CheckForMaintenanceMode implements Middleware{
public static function handle(Closure $next)
{
// TODO: Implement handle() method.
echo count::showRunStep(),str_repeat(SUOJIN, 1),"[pipes排序:1]【CheckForMaintenanceMode】【即将执行上一个回调函数(EncryptCookies)】[确定当前程序是否处于维护状态]", nl2br(PHP_EOL);
$next();
}
}
function getSlice(){
/**
* $stack 结果,就是回调函数的嵌套(堆)
*/
return function ($stack, $pipe){
return function() use ($stack, $pipe){
return $pipe::handle($stack);
};
};
}
function then(){
$pipes = [
"CheckForMaintenanceMode",
"EncryptCookies",
"AddQueueCookiesToResponse",
"StartSession",
"ShareErrorsFromSession",
"VerifyCsrfToken",
];
$firstSlice = function(){
echo count::showRunStep(),"[初始化]请求向路由器传递,返回响应", nl2br(PHP_EOL);
};
$pipes = array_reverse($pipes); //数组倒序排
call_user_func(
array_reduce($pipes, getSlice(), $firstSlice)
);
}
then();排第 1 个执行: [pipes排序:1]【CheckForMaintenanceMode】【即将执行上一个回调函数(EncryptCookies)】[确定当前程序是否处于维护状态] 排第 2 个执行: [pipes排序:2]【EncryptCookies】【即将执行上一个回调函数(AddQueueCookiesToResponse)】[对输入请求的 cookie 进行解密] 排第 3 个执行: [pipes排序:4]【StartSession】【即将执行上一个回调函数(ShareErrorsFromSession)】[开启 session,获取数据] 排第 4 个执行: [pipes排序:5]【ShareErrorsFromSession】【即将执行上一个回调函数(VerifyCsrfToken)】[如果 session 中有 errors 变量,则共享它] 排第 5 个执行: [pipes排序:6]【VerifyCsrfToken】【即将执行上一个回调函数(初始化回调函数)】[验证 Csrf-Token] 排第 6 个执行:[初始化]请求向路由器传递,返回响应 排第 7 个执行: [pipes排序:4]【StartSession】[保存数据,关闭 session] 排第 8 个执行: [pipes排序:3]【AddQueueCookiesToResponse】【执行完(StartSession)后在执行本函数】[添加下一次需要的cookie] 排第 9 个执行: [pipes排序:2]【EncryptCookies】【执行完(AddQueueCookiesToResponse)后在执行本函数】[对输出响应的 cookie 进行加密]
把 pipe3.php 代码修改,直接打印:
var_dump(array_reduce($pipes, getSlice(), $firstSlice));die;
直接输出匿名函数的嵌套结构:
object(Closure)#8 (1) {
["static"]=>
array(2) {
["stack"]=>
object(Closure)#7 (1) {
["static"]=>
array(2) {
["stack"]=>
object(Closure)#6 (1) {
["static"]=>
array(2) {
["stack"]=>
object(Closure)#5 (1) {
["static"]=>
array(2) {
["stack"]=>
object(Closure)#4 (1) {
["static"]=>
array(2) {
["stack"]=>
object(Closure)#3 (1) {
["static"]=>
array(2) {
["stack"]=>
object(Closure)#1 (0) {
}
["pipe"]=>
string(15) "VerifyCsrfToken"
}
}
["pipe"]=>
string(22) "ShareErrorsFromSession"
}
}
["pipe"]=>
string(12) "StartSession"
}
}
["pipe"]=>
string(25) "AddQueueCookiesToResponse"
}
}
["pipe"]=>
string(14) "EncryptCookies"
}
}
["pipe"]=>
string(23) "CheckForMaintenanceMode"
}
}这段代码就是执行嵌套好的匿名函数,类似 laravel 里面的 PipeLine->then()
call_user_func( array_reduce($pipes, getSlice(), $firstSlice) );
注意:laravel 的 handel() (匿名函数)是有2个参数的,第一个参数是 传入的数据,第二个是上一个匿名函数。本文例子只有一个参数(匿名函数)。
推荐其他文章:
如何使用 Laravel 管道:
https://www.jianshu.com/p/09948badf244
https://learnku.com/articles/2769/laravel-pipeline-realization-of-the-principle-of-single-component