溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

ThinkPHP6.0如何實(shí)現(xiàn)管道模式與中間件

發(fā)布時(shí)間:2021-01-16 10:15:18 來(lái)源:億速云 閱讀:140 作者:小新 欄目:編程語(yǔ)言

這篇文章主要介紹了ThinkPHP6.0如何實(shí)現(xiàn)管道模式與中間件,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

說(shuō)明

ThinkPHP 6.0 RC5 開(kāi)始使用了管道模式來(lái)實(shí)現(xiàn)中間件,比起之前版本的實(shí)現(xiàn)更加簡(jiǎn)潔、有序。這篇文章對(duì)其實(shí)現(xiàn)細(xì)節(jié)進(jìn)行分析。

首先我們從入口文件 public/index.php 開(kāi)始,$http = (new App())->http;

獲得一個(gè) http 類的實(shí)例后調(diào)用它的 run 方法:$response = $http->run();,然后它的 run 方法又調(diào)用了 runWithRequest 方法:

protected function runWithRequest(Request $request)
{
    .
    .
    .
    return $this->app->middleware->pipeline()
        ->send($request)
        ->then(function ($request) {
            return $this->dispatchToRoute($request);
        });
}

中間件的執(zhí)行都在最后的 return 語(yǔ)句中。

pipeline、through、send 方法

$this->app->middleware->pipeline() 的 pipeline 方法:
public function pipeline(string $type = 'global')
{
    return (new Pipeline())  
           // array_map將所有中間件轉(zhuǎn)換成閉包,閉包的特點(diǎn):
          // 1. 傳入?yún)?shù):$request,請(qǐng)求實(shí)例; $next,一個(gè)閉包
          // 2. 返回一個(gè)Response實(shí)例
        ->through(array_map(function ($middleware) {
            return function ($request, $next) use ($middleware) {
                list($call, $param) = $middleware;
                if (is_array($call) && is_string($call[0])) {
                    $call = [$this->app->make($call[0]), $call[1]];
                }
                 // 該語(yǔ)句執(zhí)行中間件類實(shí)例的handle方法,傳入的參數(shù)是外部傳進(jìn)來(lái)的$request和$next
                 // 還有一個(gè)$param是中間件接收的參數(shù)
                $response = call_user_func($call, $request, $next, $param);
                if (!$response instanceof Response) {
                    throw new LogicException('The middleware must return Response instance');
                }
                return $response;
            };
            // 將中間件排序
        }, $this->sortMiddleware($this->queue[$type] ?? [])))
        ->whenException([$this, 'handleException']);
}

through 方法代碼:

public function through($pipes)
{
    $this->pipes = is_array($pipes) ? $pipes : func_get_args();
    return $this;
}

前面調(diào)用 through 是傳入的 array_map(...) 把中間件封裝為一個(gè)個(gè)閉包,through 則是把這些閉包保存在 Pipeline 類的 $pipes 屬性中。

PHP 的 array_map 方法簽名:

array_map ( callable $callback , array $array1 [, array $... ] ) : array

$callback 迭代作用于每一個(gè) $array 的元素,返回新的值。所以,最后得到 $pipes 中每個(gè)閉包的形式特征是這樣的(偽代碼):

function ($request, $next) {
    $response = handle($request, $next, $param);
    return $response;
}

該閉包接收兩個(gè)參數(shù),一個(gè)是請(qǐng)求實(shí)例,一個(gè)是回調(diào)用函數(shù),handle 方法處理后得到相應(yīng)并返回。

through 返回一個(gè) Pipeline 類的實(shí)例,接著調(diào)用 send 方法:

public function send($passable)
{
    $this->passable = $passable;
    return $this;
}

該方法很簡(jiǎn)單,只是將傳入的請(qǐng)求實(shí)例保存在 $passable 成員變量,最后同樣返回 Pipeline 類的實(shí)例,這樣就可以鏈?zhǔn)秸{(diào)用 Pipeline 類的其他方法。

then,carry 方法

send 方法之后,接著調(diào)用 then 方法:

return $this->app->middleware->pipeline()
            ->send($request)
            ->then(function ($request) {
                return $this->dispatchToRoute($request);
            });

這里的 then 接收一個(gè)閉包作為參數(shù),這個(gè)閉包實(shí)際上包含了控制器操作的執(zhí)行代碼。

then 方法代碼:

public function then(Closure $destination)
{
    $pipeline = array_reduce(
        //用于迭代的數(shù)組(中間件閉包),這里將其倒序
        array_reverse($this->pipes),
        // array_reduce需要的回調(diào)函數(shù)
        $this->carry(),
        //這里是迭代的初始值
        function ($passable) use ($destination) {
            try {
                return $destination($passable);
            } catch (Throwable | Exception $e) {
                return $this->handleException($passable, $e);
            }
        });
    return $pipeline($this->passable);
}

carry 代碼:

protected function carry()
{
    // 1. $stack 上次迭代得到的值,如果是第一次迭代,其值是后面的「初始值
    // 2. $pipe 本次迭代的值
    return function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            try {
                return $pipe($passable, $stack);
            } catch (Throwable | Exception $e) {
                return $this->handleException($passable, $e);
            }
        };
    };
}

為了更方便分析原理,我們把 carry 方法內(nèi)聯(lián)到 then 中去,并去掉錯(cuò)誤捕獲的代碼,得到:

public function then(Closure $destination)
{
    $pipeline = array_reduce(
        array_reverse($this->pipes),
        function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                return $pipe($passable, $stack);
            };
        },
        function ($passable) use ($destination) {
            return $destination($passable);
        });
    return $pipeline($this->passable);
}

這里關(guān)鍵是理解 array_reduce 以及 $pipeline($this->passable) 的執(zhí)行過(guò)程,這兩個(gè)過(guò)程可以類比于「包洋蔥」和「剝洋蔥」的過(guò)程。

array_reduce 第一次迭代,$stack 初始值為:

(A)

function ($passable) use ($destination) {
    return $destination($passable);
});

回調(diào)函數(shù)的返回值為:

(B)

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

將 A 代入 B 可以得到第一次迭代之后的 $stack 的值:

(C)

function ($passable) use ($stack, $pipe) {
    return $pipe($passable, 
        function ($passable) use ($destination) {
            return $destination($passable);
        })
    );
};

第二次迭代,同理,將 C 代入 B 可得:

(D)

// 偽代碼
// 每一層的$pipe都代表一個(gè)中間件閉包
function ($passable) use ($stack, $pipe) {
    return $pipe($passable,  //倒數(shù)第二層中間件
        function ($passable) use ($stack, $pipe) {
            return $pipe($passable,  //倒數(shù)第一層中間件
                function ($passable) use ($destination) {
                    return $destination($passable);  //包含控制器操作的閉包
                })
            );
        };
    );
};

以此類推,有多少個(gè)中間件,就代入多少次,最后一次得到 $stack 就返回給 $pipeline。由于前面對(duì)中間件閉包進(jìn)行了倒序,排在前面的閉包被包裹在更里層,所以倒序后的閉包越是后面的在外面,從正序來(lái)看,則變成越前面的中間件在最外層。

層層包裹好閉包后,我們得到了一個(gè)類似洋蔥結(jié)構(gòu)的「超級(jí)」閉包 D,該閉包的結(jié)構(gòu)如上面的代碼注釋所示。最后把 $request 對(duì)象傳給這個(gè)閉包,執(zhí)行它:$pipeline($this->passable);,由此開(kāi)啟一個(gè)類似剝洋蔥的過(guò)程,接下來(lái)我們看看這洋蔥是怎么剝開(kāi)的。

剝洋蔥過(guò)程分析

array_map(...) 把每一個(gè)中間件類加工成一個(gè)類似這種結(jié)構(gòu)的閉包:

function ($request, $next) {
    $response = handle($request, $next, $param);
    return $response;
}

其中 handle 是中間件中的入口,其結(jié)構(gòu)特點(diǎn)是這樣的:

public function handle($request, $next, $param) {
    // do sth ------ M1-1 / M2-1
    $response = $next($request);
    // do sth ------ M1-2 / M2-2
    return $response;
}

我們上面的「洋蔥」一共只有兩層,也就是有兩層中間件的閉包,假設(shè) M1-1,M1-2 分別是第一個(gè)中間件 handle 方法的前置和后值操作點(diǎn)位,第二個(gè)中間件同理,是 M2-1,M2-2?,F(xiàn)在,讓程序執(zhí)行 $pipeline($this->passable),展開(kāi)來(lái)看,也就是執(zhí)行:

// 偽代碼
function ($passable) use ($stack, $pipe) {
    return $pipe($passable,  
        function ($passable) use ($stack, $pipe) {
            return $pipe($passable,  
                function ($passable) use ($destination) {
                    return $destination($passable);  
                })
            );
        };
    );
}($this->passable)

此時(shí),程序要求從:

return $pipe($passable,  
    function ($passable) use ($stack, $pipe) {
        return $pipe($passable,  
            function ($passable) use ($destination) {
                return $destination($passable);  
            })
        );
    };
);

返回值,也就是要執(zhí)行第一個(gè)中間件閉包,$passable 對(duì)應(yīng) handle 方法的 $request 參數(shù),而下一層閉包

function ($passable) use ($stack, $pipe) {
    return $pipe($passable,  
        function ($passable) use ($destination) {
            return $destination($passable);  
        })
    );
}

則對(duì)應(yīng) handle 方法的 $next 參數(shù)。

要執(zhí)行第一個(gè)閉包,即要執(zhí)行第一個(gè)閉包的 handle 方法,其過(guò)程是:首先執(zhí)行 M1-1 點(diǎn)位的代碼,即前置操作,然后執(zhí)行 $response = $next($request);,這時(shí)程序進(jìn)入執(zhí)行下一個(gè)閉包,$next($request) 展開(kāi)來(lái),也就是:

function ($passable) use ($stack, $pipe) {
    return $pipe($passable,  
        function ($passable) use ($destination) {
            return $destination($passable);  
        })
    );
}($request)

依次類推,執(zhí)行該閉包,即執(zhí)行第二個(gè)中間件的 handle 方法,此時(shí),先執(zhí)行 M2-1 點(diǎn)位,然后執(zhí)行 $response = $next($request),此時(shí)的 $next 閉包是:

function ($passable) use ($destination) {
    return $destination($passable);  
})

屬于洋蔥之芯 —— 最里面的一層,也就是包含控制器操作的閉包,展開(kāi)來(lái)看:

function ($passable) use ($destination) {
    return $destination($passable);  
})($request)

最終,我們從 return $destination($passable) 中返回一個(gè) Response 類的實(shí)例,也就是,第二層的 $response = $next($request) 語(yǔ)句成功得到了結(jié)果,接著執(zhí)行下面的語(yǔ)句,也就是 M2-2 點(diǎn)位,最后第二層閉包返回結(jié)果,也就是第一層閉包的 $response = $next($request) 語(yǔ)句成功得到了結(jié)果,然后執(zhí)行這一層閉包該語(yǔ)句后面的語(yǔ)句,即 M1-2 點(diǎn)位,該點(diǎn)位之后,第一層閉包也成功返回結(jié)果,于是,then 方法最終得到了返回結(jié)果。

整個(gè)過(guò)程過(guò)來(lái),程序經(jīng)過(guò)的點(diǎn)位順序是這樣的:M1-1→M2-1→控制器操作→M2-2→M1-2→返回結(jié)果。

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“ThinkPHP6.0如何實(shí)現(xiàn)管道模式與中間件”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI