评论

PHP中array_reduce与闭包嵌套

array_reduce和闭包的嵌套使用,可还原目前主流laravel等php框架的中间件的实现的原理。

(PHP 4 >= 4.0.5, PHP 5, PHP 7, PHP 8)

array_reduce — 用回调函数迭代地将数组简化为单一的值

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

参数 ¶
array
输入的 array。

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

item
携带了本次迭代的值。

initial
如果指定了可选参数 initial,该参数将用作处理开始时的初始值,如果数组为空,则会作为最终结果返回。

返回值 ¶
返回结果值。

如果数组为空,并且没有指定 initial 参数,array_reduce() 返回 null。

<?php
interface Middleware
{
    public static function handle(Closure $next);
}

class AddQueuedCookiesToResponse implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "add before\n";
        $next();
        echo "add after\n";
    }
}

class EncryptCookies implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "encryp before\n";
        $next();
        echo "encryp after\n";
    }
}

class CheckForMaintenanceMode implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "check before\n";
        $next();
        echo "check after\n";
    }
}

function getSlice() // 返回一个函数,与上文的f一致
{
    return function ($stack, $pipe) {
        return function () use ($stack, $pipe) {
            return $pipe::handle($stack);
        };
    };
}

function then()
{
    $pipes = [
        "CheckForMaintenanceMode",
        "EncryptCookies",
        "AddQueuedCookiesToResponse",
    ];

    $firstSlice = function () { // 上文的目标函数 target
        echo "请求向路由器传递,返回响应.\n";
    };

    $pipes = array_reverse($pipes);

    $closure = array_reduce($pipes, getSlice(), $firstSlice);
    var_dump($closure);//打印下该闭包

    // 因为最终返回了一个函数,所以需要call_user_func
    call_user_func(array_reduce($pipes, getSlice(), $firstSlice));
}

then();

echo "\n";
//array_reduce($pipes, getSlice(), $firstSlice),执行完成后相当于生成了f嵌套闭包
function f()
{
    return CheckForMaintenanceMode::handle(
        function () {
            return EncryptCookies::handle(
                function () {
                    return AddQueuedCookiesToResponse::handle(function () {
                        echo "请求向路由器传递,返回响应.\n";
                    });
                }
            );
        }
    );
}

call_user_func('f');

嵌套闭包的解包方式和递归的方式类似,它是先从外到内,然后从内回归到外面,所以输出结果是这样的,以上程序执行结果为:

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(26) "AddQueuedCookiesToResponse"
          }
        }
        ["pipe"]=>
        string(14) "EncryptCookies"
      }
    }
    ["pipe"]=>
    string(23) "CheckForMaintenanceMode"
  }
}
check before
encryp before
add before
请求向路由器传递,返回响应.
add after
encryp after
check after

check before
encryp before
add before
请求向路由器传递,返回响应.
add after
encryp after
check after

当第一次迭代的时候 $stack 的值为$firstSlice,$pipe 的值为’AddQueuedCookiesToResponse’。第二次迭代时,会把第一次迭代生成的

function () {
    return AddQueuedCookiesToResponse::handle(function () {
          echo "请求向路由器传递,返回响应.\n";
    });
}

做为$stack传入,$pipe的值为’EncryptCookies’,

function () {
            return EncryptCookies::handle(
                function () {
                    return AddQueuedCookiesToResponse::handle(function () {
                        echo "请求向路由器传递,返回响应.\n";
                    });
                }
            );
}

以此类推,生成嵌套的闭包函数,当调用call_user_func时,会依此执行pipe::handle方法,每次会把handle里的闭包传入(即每次都把var_dump($closure),打印结果里stack下的闭包作为参数),在hanle方法里$next()会调用传入的闭包,直到最后一次$next才===$firstSlice。

array_reduce($pipes, getSlice(), $firstSlice),执行完成后生成了f嵌套闭包,从执行后的打印结果看出来,两者执行结果一致。

带参数的示例:

<?php
interface Middleware
{
    public static function handle($message,Closure $next);
}

class AddQueuedCookiesToResponse implements Middleware
{
    public static function handle($message,Closure $next)
    {
        echo "add before".$message."\n";
		$message = '-add';
        return $next($message);
        echo "add after\n";
    }
}

class EncryptCookies implements Middleware
{
    public static function handle($message,Closure $next)
    {
        echo "encryp before".$message."\n";
		$message = '-encry';
		//return ['code'=>1];
        return $next($message);
        echo "encryp after\n";
    }
}

class CheckForMaintenanceMode implements Middleware
{
    public static function handle($message,Closure $next)
    {
        echo "check before".$message."\n";
		$message = '-check';
        return $next($message);
        echo "check after\n";
    }
}

function getSlice() // 返回一个函数,与上文的f一致
{
    return function ($stack, $pipe) {
        return function ($message) use ($stack, $pipe) {
            return $pipe::handle($message,$stack);
        };
    };
}

function then()
{
    $pipes = [
        "CheckForMaintenanceMode",
        "EncryptCookies",
        "AddQueuedCookiesToResponse",
    ];

    $firstSlice = function () { // 上文的目标函数 target
        echo "请求向路由器传递,返回响应.\n";
		return [];
    };

    $pipes = array_reverse($pipes);

    $closure = array_reduce($pipes, getSlice(), $firstSlice);
  
    // 因为最终返回了一个函数,所以需要call_user_func
    $ret = call_user_func(array_reduce($pipes, getSlice(), $firstSlice),'abc');
	var_dump($ret);
}

then();

echo "\n";
//array_reduce($pipes, getSlice(), $firstSlice),执行完成后相当于生成了f嵌套闭包
function f($p)
{
    return CheckForMaintenanceMode::handle($p,
        function ($p) {
            return EncryptCookies::handle($p,
                function ($p) {
                    return AddQueuedCookiesToResponse::handle($p,function () {
                        echo "请求向路由器传递,返回响应.\n";
						return [];
                    });
                }
            );
        }
    );
}

$ret = call_user_func('f','cba');
var_dump($ret);

打印结果

check beforeabc
encryp before-check
add before-encry
请求向路由器传递,返回响应.
array(0) {
}

check beforecba
encryp before-check
add before-encry
请求向路由器传递,返回响应.
array(0) {
}

闭包直接使用$next()调用,生成嵌套闭包示例:

<?php
$next = function(){
	return 'next function initial';
};

$itemHandler[] = function($message,Closure $next){
    //自定义1
	var_dump("自定义1");
    return $next($message);
};

$itemHandler[] = function($message,Closure $next){
    //自定义2
	var_dump("自定义2");
    return $next($message);
};
foreach(array_reverse($itemHandler) as $item){
	$next = function($p) use($item,$next){
		return $item($p,$next);
	};
};

var_dump($next(123));
var_dump($next);

打印结果:

string(10) "自定义1"
string(10) "自定义2"
string(21) "next function initial"
object(Closure)#5 (2) {
  ["static"]=>
  array(2) {
    ["item"]=>
    object(Closure)#2 (1) {
      ["parameter"]=>
      array(2) {
        ["$message"]=>
        string(10) "<required>"
        ["$next"]=>
        string(10) "<required>"
      }
    }
    ["next"]=>
    object(Closure)#4 (2) {
      ["static"]=>
      array(2) {
        ["item"]=>
        object(Closure)#3 (1) {
          ["parameter"]=>
          array(2) {
            ["$message"]=>
            string(10) "<required>"
            ["$next"]=>
            string(10) "<required>"
          }
        }
        ["next"]=>
        object(Closure)#1 (0) {
        }
      }
      ["parameter"]=>
      array(1) {
        ["$p"]=>
        string(10) "<required>"
      }
    }
  }
  ["parameter"]=>
  array(1) {
    ["$p"]=>
    string(10) "<required>"
  }
}
点赞 0
收藏
评论
登录 后发表内容