一个让PHP小白百思不得其解的匿名函数及array_reduce的问题

最近学习PHP,看到了一段代码,其中涉及到了匿名函数以及array_reduce,把代码敲出来用各种方法分析也没想出是怎么调用的,代码如下:

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

class VerifyCsrfToken implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "(5)验证Csrf-Token".'<br>';
        $next();
    }
}

class ShareErrorsFromSession implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "(4)如果session中有'errors'变量,则共享它".'<br>';
        $next();
    }
}

class StartSession implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "(3)开启session,获取数据".'<br>';
        $next();
        echo "(7)保存数据,关闭session".'<br>';
    }
}

class AddQueuedCookiesToResponse implements Middleware
{
    public static function handle(Closure $next)
    {
        $next();
        echo "(8)添加下一次请求需要的cookie".'<br>';
    }
}

class EncryptCookies implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "(2)对输入请求的cookie进行解密".'<br>';
        $next();
        echo "(9)对输出相应的cookie进行加密".'<br>';
    }
}

class CheckForMaintenanceMode implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "(1)确定当前程序是否处于维护状态".'<br>';
        $next();
    }
}

function getSlice()
{
    return function($stack, $pipe)
    {
        return function() use ($stack, $pipe)
        {
            return $pipe::handle($stack);
        };
    };
}


function then()
{
    $pipes = [
        "CheckForMaintenanceMode",
        "EncryptCookies",
        "AddQueuedCookiesToResponse",
        "StartSession",
        "ShareErrorsFromSession",
        "VerifyCsrfToken"
    ];
    
    $firstSlice = function() {
        echo "(6)请求向路由器传递,返回响应.".'<br>';
    };

    $pipes = array_reverse($pipes);
    $go = array_reduce($pipes, getSlice(),$firstSlice);
    $go();
}
then();
?>

还望有大神能帮忙详解下$go = array_reduce($pipes, getSlice(),$firstSlice);和$go();这两段代码背后的每一步的调用执行流程,以及调用时的参数传递是哪些,如果能用流程图表示就更好啦,谢谢。

阅读 4.7k
2 个回答

那么久了还没人回答.我来回答一下吧.
首先是这样的.我们看下面的代码来理解一下

<?php
function myfunction($v1,$v2)
{
return $v1+$v2;
}
$a=array(10,15,20);
print_r(array_reduce($a,"myfunction",5)); //50
?>

上面的结果为50.那么它的过程是怎么样的呢?我们对代码进行改良

function myfunction($v1, $v2)
{
    var_dump($v1, $v2);
    return $v1 + $v2;
}

$a = array(10, 15, 20);
print_r(array_reduce($a, "myfunction", 5)); //50
echo "\n";

然后可以看到如下输出

int(5)
int(10)
int(15)
int(15)
int(30)
int(20)
50

第一个v1 = 5,v2 = 10;
第二个v1 = 15 (前一个返回的值) , v2 = 15; $a[1]的值;
第三个v1 = 30 (上一次的返回值) , v2 = 20; $a[2]的值;
最后输出50. 那么我们看第一个值为什么是5? 因为array_reduce接收的第3个参数就是表示当第一次迭代的时候传递的值。下面我们来自己实现一个array_reduce去深度的理解它 :)

function myfunction($v1, $v2)
{
    var_dump($v1, $v2);
    return $v1 + $v2;
}

$a = array(10, 15, 20);
print_r(my_array_reduce($a, "myfunction", 5)); //50
echo "\n";


/**
 * array_reduce
 * @param array $arr
 * @param callable $fn
 * @param null $initial
 * @return mixed
 */
function my_array_reduce(array $arr, callable $fn, $initial = null)
{
    $v = $initial;
    foreach ($arr as $item) {
        $v = $fn($v, $item);
    }
    return $v;
}

这样是不是好多了呢?


下面是根据题主的问题进行补充

$arr = [
    'VerifyCsrfToken'
];

function getSlice()
{
    return function($stack, $pipe)
    {
        return function() use ($stack, $pipe)
        {
            return $pipe::handle($stack);
        };
    };
}

$firstSlice = function() {
        echo "(6)请求向路由器传递,返回响应.".'<br>';
};

$go = array_reduce($pipes, getSlice(),$firstSlice);
$go();

当arr 是上面的数组 以及回调的方法是上面的方法时.我们来看看发生了什么
首先
1.getSlice的返回值是function

当arr第一次循环的时候,根据我们上面所提到的array_reduce的原理看看发生了什么?

$arr = [
    'VerifyCsrfToken'
];

function getSlice()
{
    return function($stack, $pipe)
    {
        return function() use ($stack, $pipe)
        {
            return $pipe::handle($stack);
        };
    };
}

首先看这里
$go = array_reduce($pipes, getSlice(),$firstSlice);
第二个参数传的并不是callback 而是 直接写的 getSlice(); 那么这个函数将会直接执行并且将返回值传递给 array_reduce的第二个参数.
也就是直接返回

function ($stack, $pipe) {
        return function () use ($stack, $pipe) {
            return $pipe::handle($stack);
        };
    };

也就是和下面的写法是等价的。

function getSlice($stack, $pipe)
{
    return function () use ($stack, $pipe) {
        return $pipe::handle($stack);
    };
}

$firstSlice = function () {
    echo "(6)请求向路由器传递,返回响应\n";
};

$go = my_array_reduce($pipes, "getSlice", $firstSlice);
$go();

只是因为前者的写法更加优雅 易于让ide查找;

弄清楚了这个我们接下来继续看。

return function () use ($stack, $pipe) {
     return $pipe::handle($stack);
};

当第一次迭代的时候 $stack 的值为$firstSlice
pipe 的值 为 VerifyCsrfToken.
那么这个函数被执行了.$firstSlice当作参数。
所以当调用$go();时
所有的pipe::handle方法会立即执行。
而每次都把$stack作为参数
所以执行顺序是倒过来了。因为到最后一次的时候 $next 才 === $firstSlice


$pipes = [
    "VerifyCsrfToken",
    "VerifyCsrfToken1"
];

/**
 * array_reduce
 * @param array $arr
 * @param callable $fn
 * @param null $initial
 * @return mixed
 */
function my_array_reduce($arr, callable $fn, $initial = null)
{
    $v = $initial;
    foreach ($arr as $item) {
        $v = $fn($v, $item);
    }
    return $v;
}

再来回顾这段代码.为什么VerifyCsrfToken1先执行呢?
因为当foreach执行完毕的时候. $item = $pipes[count($pipes)-1];
也就是最后一个而 $v 永远为上一个return 的 值。

赞赞,感谢大神不吝赐教。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
1 篇内容引用
推荐问题
宣传栏