array_reduce如何形成多层闭包函数【装饰者模式在Laravel框架中的实现】

84 阅读3分钟

Laravel框架中使用装饰模式来处理请求。其中用到了array_reduce方法。那么array_reduce是如何完成调用的。

先看一下装饰模式代码的简化版: 代码出处

<?php
	header("content-type:text/html;charset=GB2312");
    /*定义一个 中间件接口*/
    interface Middleware
    {
        public static function handle(Closure $next); 
    }
    
    /*定义一个 csrf验证类*/
    class VerifyCsrfToken implements Middleware
    {
        public static function handle(Closure $next)
        {
            echo "验证Csrf-Token"."<br/> \n";
            $next();
        }   
            
    }
    
    /*定义一个 错误分享类*/
    class ShareErrorsFromSession implements Middleware
    {
        public static function handle(Closure $next)
        {
            echo "如果session中有'errors'变量,则共享它"."<br/> \n";
            $next();
        }
    }
    
    /*定义一个 开启session类*/
    class StartSession implements Middleware
    {
        public static function handle(Closure $next)
        {
            echo "开启session,获取数据"."<br/> \n";
            $next();
            echo "报错数据,关闭session"."<br> \n";
        }
    }
    
    /*定义一个 添加cookie队列至响应 类*/
    class AddQueuedCookiesToResponse implements Middleware
    {
        public static function handle(Closure $next)
        {
            $next();
            echo "添加下一次请求需要的cookie"."<br/> \n";
        }
    }
    
    /*定义一个 加密cookie类*/
    class EncryptCookies implements  Middleware
    {
        public static function handle(Closure $next)
        {
            echo "对输入请求的cookie进行解密"."<br/> \n";
            $next();
            echo "对输出响应的cookie进行加密"."<br/> \n";
        }
    }
    
    /*定义一个 检测维护状态类*/
    class ChecKForMaintenanceMode implements Middleware
    {
        public static function handle(Closure $next)
        {
            echo "确定当前程序是否处于维护状态"."<br/> \n";
            $next();
        }
    }
    
    function getSlice()
    {
        $func =  function($stack, $pipe)
        {
			error_log(implode(' | ',array(__FILE__,__FUNCTION__,__LINE__, ' first call',' stack:' . get_class($stack),'pipe:'. $pipe)));
            $func1 =  function() use ($stack, $pipe)
            {
				error_log(implode(' | ',array(__FILE__,__FUNCTION__,__LINE__,' second call',' stack:' . get_class($stack),'pipe:'. $pipe)));
                return $pipe::handle($stack);     
            };
			return $func1;
        };
		return $func;
    }
    
    function then()
    {
        $pipes = [
            "ChecKForMaintenanceMode",
            "EncryptCookies",
            "AddQueuedCookiesToResponse",
            "StartSession",
            "ShareErrorsFromSession",
            "VerifyCsrfToken"
        ];
        $firstSlice = function() {
            echo "请求向路由器传递,返回响应"."<br/> \n";
        };
		print_r($pipes);
        $pipes = array_reverse($pipes);  
		$reduce = array_reduce($pipes, getSlice(), $firstSlice);
		var_dump($reduce);
        call_user_func($reduce); 
        // array_reduce()向用户自定义函数发送数组中的值,并返回一个字符串:
    }
	
	then();
	
   /**输出:
    * 确定当前程序是否处于维护状态
    * 对输入请求的cookie进行解密
    * 开启session,获取数据
    * 如果session中有'errors'变量,则共享它
    * 验证Csrf-Token
    * 请求向路由器传递,返回响应
    * 保存数据,关闭session
    * 添加下一次请求需要的cookie
    * 对输出响应的cookie进行加密
    */    

控制台输出:

$ php test.php
Array
(
    [0] => ChecKForMaintenanceMode
    [1] => EncryptCookies
    [2] => AddQueuedCookiesToResponse
    [3] => StartSession
    [4] => ShareErrorsFromSession
    [5] => VerifyCsrfToken
)
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"]=>
// stack和pipe这里就是静态外部变量,
// 通过使用外部静态变量,把上一层的结果当成下一层的参数。
// 然后形成这种迭代模型。
                        object(Closure)#1 (0) {
                        }

                        ["pipe"]=>
                        string(15) "VerifyCsrfToken"
                      }
                    }
                    ["pipe"]=>
                    string(22) "ShareErrorsFromSession"
                  }
                }
                ["pipe"]=>
                string(12) "StartSession"
              }
            }
            ["pipe"]=>
            string(26) "AddQueuedCookiesToResponse"
          }
        }
        ["pipe"]=>
        string(14) "EncryptCookies"
      }
    }
    ["pipe"]=>
    string(23) "ChecKForMaintenanceMode"
  }
}
确定当前程序是否处于维护状态<br/>
对输入请求的cookie进行解密<br/>
开启session,获取数据<br/>
如果session中有'errors'变量,则共享它<br/>
验证Csrf-Token<br/>
请求向路由器传递,返回响应<br/>
报错数据,关闭session<br>
添加下一次请求需要的cookie<br/>
对输出响应的cookie进行加密<br/>

日志中输出:

[19-Mar-2019 10:58:27 Europe/Berlin] C:\xampp\htdocs\test\test.php | {closure} | 76 |  first call |  stack:Closure | pipe:VerifyCsrfToken
[19-Mar-2019 10:58:27 Europe/Berlin] C:\xampp\htdocs\test\test.php | {closure} | 76 |  first call |  stack:Closure | pipe:ShareErrorsFromSession
[19-Mar-2019 10:58:27 Europe/Berlin] C:\xampp\htdocs\test\test.php | {closure} | 76 |  first call |  stack:Closure | pipe:StartSession
[19-Mar-2019 10:58:27 Europe/Berlin] C:\xampp\htdocs\test\test.php | {closure} | 76 |  first call |  stack:Closure | pipe:AddQueuedCookiesToResponse
[19-Mar-2019 10:58:27 Europe/Berlin] C:\xampp\htdocs\test\test.php | {closure} | 76 |  first call |  stack:Closure | pipe:EncryptCookies
[19-Mar-2019 10:58:27 Europe/Berlin] C:\xampp\htdocs\test\test.php | {closure} | 76 |  first call |  stack:Closure | pipe:ChecKForMaintenanceMode

[19-Mar-2019 10:58:27 Europe/Berlin] C:\xampp\htdocs\test\test.php | {closure} | 79 |  second call |  stack:Closure | pipe:ChecKForMaintenanceMode
[19-Mar-2019 10:58:27 Europe/Berlin] C:\xampp\htdocs\test\test.php | {closure} | 79 |  second call |  stack:Closure | pipe:EncryptCookies
[19-Mar-2019 10:58:27 Europe/Berlin] C:\xampp\htdocs\test\test.php | {closure} | 79 |  second call |  stack:Closure | pipe:AddQueuedCookiesToResponse
[19-Mar-2019 10:58:27 Europe/Berlin] C:\xampp\htdocs\test\test.php | {closure} | 79 |  second call |  stack:Closure | pipe:StartSession
[19-Mar-2019 10:58:27 Europe/Berlin] C:\xampp\htdocs\test\test.php | {closure} | 79 |  second call |  stack:Closure | pipe:ShareErrorsFromSession
[19-Mar-2019 10:58:27 Europe/Berlin] C:\xampp\htdocs\test\test.php | {closure} | 79 |  second call |  stack:Closure | pipe:VerifyCsrfToken

明显可以看出array_reduce方法返回的是一个多层闭包函数。 那么这种结构是如何形成?

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

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

array_reduce 用回调函数迭代地将数组简化为单一的值。不明白这句话的意思,就写了以下代码进行理解:

1 调用顺序
<?php
function myfunction($v1,$v2)
{
    error_log(implode(' | ',array(__FILE__,__FUNCTION__,__LINE__,' v1:' . $v1,'v2:'.$v2)));
    return $v1 . "-" . $v2;
}
$a=array("Dog","Cat","Horse");
print_r(array_reduce($a,"myfunction"));



输出:

-Dog-Cat-Horse

日志输出:


[18-Mar-2019 18:28:26 Asia/Shanghai] /home/dev/git/test/reduce.php | myfunction | 4 |  v1: | v2:Dog
[18-Mar-2019 18:28:26 Asia/Shanghai] /home/dev/git/test/reduce.php | myfunction | 4 |  v1:-Dog | v2:Cat
[18-Mar-2019 18:28:26 Asia/Shanghai] /home/dev/git/test/reduce.php | myfunction | 4 |  v1:-Dog-Cat | v2:Horse

试着使用函数调用:

<?php
function myfunction($v1,$v2)
{
    error_log(implode(' | ',array(__FILE__,__FUNCTION__,__LINE__,' v1:' . $v1,'v2:'.$v2)));
    return $v1 . "-" . $v2;
}
print_r(myfunction(myfunction(myfunction('','Dog'),'Cat'),'Horse'));

输出:

-Dog-Cat-Horse

日志输出:

[18-Mar-2019 18:28:26 Asia/Shanghai] /home/dev/git/test/reduce.php | myfunction | 4 |  v1: | v2:Dog
[18-Mar-2019 18:28:26 Asia/Shanghai] /home/dev/git/test/reduce.php | myfunction | 4 |  v1:-Dog | v2:Cat
[18-Mar-2019 18:28:26 Asia/Shanghai] /home/dev/git/test/reduce.php | myfunction | 4 |  v1:-Dog-Cat | v2:Horse

那么就可以理解为是数组有n个元素,将数组最后一个元素作为函数第二个参数,然后前n-1个元素的没有function的值作为第一个参数;

那么前n-1个元素的myfunction的值为多少?

然后再调用 第n-1个元素作为函数的第二个参数,前n-2个元素的没有function的值作为第一个参数。

如此迭代下去,就如同我直接调用这个函数的调用方式:

其实,以上的只是其中一部分。

更多的是使用array_reduce将回调函数迭代形成一个元素的功能。

array_reduce会对其中的元素从前到后的进行迭代,第一个元素在最里面,那么对于call_user_func来说是最后执行,它的执行顺序是先外面再里面。 所以要按照数组定义的顺序来执行,那么就需要使用array_reverse对数组进行一次逆序操作。

手册中对于call_user_func这个方法没有特殊讲解,只是说用来调用回调函数。

2 不使用array_reduce来构成多层闭包函数
<?php

function getFunc(){
	$arg = 1;
	$func1 = function($arg) {
		print "func1 [{$arg}]\n";
	};
	// 使用use,将外部变量引入到闭包中,形成闭包中的静态变量。
	$func2 = function () use ($func1,$arg) {
		//static $object;
		print "func2 [$arg]\n";
		return $func1($arg);

	};
	
	$func3 = function () use($func2,$arg){
		//static $object;
		print "func3 [$arg]\n";
		return  $func2($arg);
	};
	return $func3;	
}


$ff = getFunc();
var_dump($ff);
call_user_func($ff);

输出:

ttwp@ttwp /c/xampp/htdocs/test
$ php tt.php
object(Closure)#3 (1) {
  ["static"]=>
  array(2) {
    ["func2"]=>
    object(Closure)#2 (1) {
      ["static"]=>
      array(2) {
        ["func1"]=>
        object(Closure)#1 (1) {
          ["parameter"]=>
          array(1) {
            ["$arg"]=>
            string(10) "<required>"
          }
        }
        ["arg"]=>
        int(1)
      }
    }
    ["arg"]=>
    int(1)
  }
}
func3 [1]
func2 [1]
func1 [1]