鍍金池/ 問答/PHP  網(wǎng)絡(luò)安全/ 一個(gè)讓PHP小白百思不得其解的匿名函數(shù)及array_reduce的問題

一個(gè)讓PHP小白百思不得其解的匿名函數(shù)及array_reduce的問題

最近學(xué)習(xí)PHP,看到了一段代碼,其中涉及到了匿名函數(shù)以及array_reduce,把代碼敲出來用各種方法分析也沒想出是怎么調(diào)用的,代碼如下:

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

class VerifyCsrfToken implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "(5)驗(yàn)證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,獲取數(shù)據(jù)".'<br>';
        $next();
        echo "(7)保存數(shù)據(jù),關(guān)閉session".'<br>';
    }
}

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

class EncryptCookies implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "(2)對(duì)輸入請(qǐng)求的cookie進(jìn)行解密".'<br>';
        $next();
        echo "(9)對(duì)輸出相應(yīng)的cookie進(jìn)行加密".'<br>';
    }
}

class CheckForMaintenanceMode implements Middleware
{
    public static function handle(Closure $next)
    {
        echo "(1)確定當(dāng)前程序是否處于維護(hù)狀態(tài)".'<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)請(qǐng)求向路由器傳遞,返回響應(yīng).".'<br>';
    };

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

還望有大神能幫忙詳解下$go = array_reduce($pipes, getSlice(),$firstSlice);和$go();這兩段代碼背后的每一步的調(diào)用執(zhí)行流程,以及調(diào)用時(shí)的參數(shù)傳遞是哪些,如果能用流程圖表示就更好啦,謝謝。

回答
編輯回答
葬愛

贊贊,感謝大神不吝賜教。

2018年2月24日 19:48
編輯回答
忠妾

那么久了還沒人回答.我來回答一下吧.
首先是這樣的.我們看下面的代碼來理解一下

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

上面的結(jié)果為50.那么它的過程是怎么樣的呢?我們對(duì)代碼進(jìn)行改良

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

第一個(gè)v1 = 5,v2 = 10;
第二個(gè)v1 = 15 (前一個(gè)返回的值) , v2 = 15; $a[1]的值;
第三個(gè)v1 = 30 (上一次的返回值) , v2 = 20; $a[2]的值;
最后輸出50. 那么我們看第一個(gè)值為什么是5? 因?yàn)閍rray_reduce接收的第3個(gè)參數(shù)就是表示當(dāng)?shù)谝淮蔚臅r(shí)候傳遞的值。下面我們來自己實(shí)現(xiàn)一個(gè)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;
}

這樣是不是好多了呢?


下面是根據(jù)題主的問題進(jìn)行補(bǔ)充

$arr = [
    'VerifyCsrfToken'
];

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

$firstSlice = function() {
        echo "(6)請(qǐng)求向路由器傳遞,返回響應(yīng).".'<br>';
};

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

當(dāng)arr 是上面的數(shù)組 以及回調(diào)的方法是上面的方法時(shí).我們來看看發(fā)生了什么
首先
1.getSlice的返回值是function

當(dāng)arr第一次循環(huán)的時(shí)候,根據(jù)我們上面所提到的array_reduce的原理看看發(fā)生了什么?

$arr = [
    'VerifyCsrfToken'
];

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

首先看這里
$go = array_reduce($pipes, getSlice(),$firstSlice);
第二個(gè)參數(shù)傳的并不是callback 而是 直接寫的 getSlice(); 那么這個(gè)函數(shù)將會(huì)直接執(zhí)行并且將返回值傳遞給 array_reduce的第二個(gè)參數(shù).
也就是直接返回

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

也就是和下面的寫法是等價(jià)的。

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

$firstSlice = function () {
    echo "(6)請(qǐng)求向路由器傳遞,返回響應(yīng)\n";
};

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

只是因?yàn)榍罢叩膶懛ǜ觾?yōu)雅 易于讓ide查找;

弄清楚了這個(gè)我們接下來繼續(xù)看。

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

當(dāng)?shù)谝淮蔚臅r(shí)候 $stack 的值為$firstSlice
pipe 的值 為 VerifyCsrfToken.
那么這個(gè)函數(shù)被執(zhí)行了.$firstSlice當(dāng)作參數(shù)。
所以當(dāng)調(diào)用$go();時(shí)
所有的pipe::handle方法會(huì)立即執(zhí)行。
而每次都把$stack作為參數(shù)
所以執(zhí)行順序是倒過來了。因?yàn)榈阶詈笠淮蔚臅r(shí)候 $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先執(zhí)行呢?
因?yàn)楫?dāng)foreach執(zhí)行完畢的時(shí)候. $item = $pipes[count($pipes)-1];
也就是最后一個(gè)而 $v 永遠(yuǎn)為上一個(gè)return 的 值。

2017年8月3日 14:29