老司機帶你實現 Laravel 之管道

Dennis_Ritchie發表於2019-12-17

前言

在這篇文章之前,我已經寫過一系列關於Laravel的文章了,我覺得他們對於你理解Laravel的整個程式碼結構來說,具有非常重要的意義,今天我們們繼續分析,今天的主角是Pipeline,也就是俗稱的管道,程式碼我已經上傳至碼雲:php-pipeline,程式碼量並不大,但是請仔細閱讀,可能並不好理解,可能有點兒短小精悍的意思吧。

管道

管道在Laravel中扮演了一個極其重要的角色,因為你所有的中介軟體都是通過管道進行操作的想要理解中介軟體,必須得理解管道,laravel的管道檔案位於vendor/laravel/framework/src/illuminate/Pipeline目錄下面:

老司機帶你實現Laravel之管道

程式碼並不多,但是為了給大家講清楚,我自己實現了一個精簡版,上面已經說過了,已經上傳到了碼雲,核心程式碼16行,哈哈,是不是很驚訝!

希望大家一定要把程式碼下載下來,這樣更有助於理解。

function Pipeline($stack, $pipe)
{
    return function ($passable) use ($stack, $pipe) {
        if (is_callable($pipe)) {
            $pipe($passable, $stack);
        } elseif (is_object($pipe)) {
            $method = "handle";
            if (!method_exists($pipe, $method)) {
                throw new InvalidArgumentException('object that own handle method');
            } else {
                $pipe->$method($passable, $stack);
            }
        } else {
            throw new InvalidArgumentException('$pipe must be callback or object');
        }
    };
}

上面這個就是它的真面目了,下面我們先來看一下如果使用它:

interface  TestUnit
{
    public function handle($passable, callable $next = null);
}

class  Unit1 implements TestUnit
{
    public function handle($passable, callable $next = null)
    {
        echo __CLASS__ . '->' . __METHOD__ . " called\n";
        $next($passable);
    }
}

class  Unit2 implements TestUnit
{
    public function handle($passable, callable $next = null)
    {
        echo __CLASS__ . '->' . __METHOD__ . " called\n";
        $next($passable);
    }
}

class  Unit3 implements TestUnit
{
    public function handle($passable, callable $next = null)
    {
        echo __CLASS__ . '->' . __METHOD__ . " called\n";
        $next($passable);
    }
}

class  InitialValue implements TestUnit
{
    public function handle($passable, callable $next = null)
    {
        echo __CLASS__ . '->' . __METHOD__ . " called\n";
        //$next($passable);
    }
}

$pipeline = array_reduce([new Unit1(), new Unit2(), new Unit3()], "Pipeline", function ($passable) {
    (new InitialValue())->handle($passable);
});
$pipeline(1);

上面我貼出如何使用Pipeline的例子,接下來的講解就以這個為例,不然很不好理解,在上面的測試程式碼裡面,我宣告瞭一個TestUnit介面,這個介面只有一個方法,就是handle,然後宣告瞭三個實現TestUnit介面的類,分別是Unit1,Unit2,Unit3,它們的handle方法很簡單:

echo __CLASS__ . '->' . __METHOD__ . " called\n";
$next($passable);

在PHP中__CLASS__指代當前呼叫呼叫方法所屬的類,__METHOD__指代當前呼叫的方法,如果我們執行上面的程式碼,會得到下面的結果:

E:\php-pipeline>php debug.php
Unit3->Unit3::handle called
Unit2->Unit2::handle called
Unit1->Unit1::handle called
InitialValue->InitialValue::handle called

控制檯結果和我預想的結果保持一致,舒服!

在講解上面的程式碼之前,你一定要知道如何使用array_reduce方法,如果你不知道,也沒關係,可以參考php的官方文件:array_reduce,使用很簡單,我就不多說了。

在上面的程式碼中,我們看是如何是如何使用array_reduce的:

$pipeline = array_reduce([new Unit1(), new Unit2(), new Unit3()], "Pipeline", function ($passable) {
    (new InitialValue())->handle($passable);
});

array_reduce遍歷的是有三個元素組成的陣列,分別是Unit1,Unit2,Unit3類的例項物件,回撥方法是是Pipeline,也就是我們上面貼出來的函式,初始值為:

function ($passable) {
    (new InitialValue())->handle($passable);
}

注意了,它是一個回撥方法,我們把它簡稱為$init,這個一定要記住。

好了,不多說了,我們開始遍歷[new Unit1(), new Unit2(), new Unit3()]這個陣列。

遍歷new Unit1()此時我們看呼叫Pipeline的返回結果,是一個回撥方法,對不對?為了後面的分析,我們把這個會調記為如下形式(你可以理解為虛擬碼):

$c1=callback($passable)[$init,new Unit1()]

同樣的,我們遍歷new Unit2(),得到的回撥簽名如下:

$c2=callback($passable)[$c1,new Unit2()]

最後遍歷new Unit3(),虛擬碼如下:

$c3=callback($passable)[$c2,new Unit3()]

通過上面的測試程式碼,我們知道array_reduce返回的是$c3這個回撥方法,也就是賦值給了$pipeline變數,接下來呼叫c3這個回撥,如下:

$pipeline(1);

老司機帶你實現Laravel之管道

這裡為了方便,我們把引數設定為1,實際上你可以設定為任何值,這個沒關係,我們看到當呼叫c3的時候,這個時候,我們應該回到Pipeline這個函式的本體了,引數$passable為1,$stack為c2pipenew Unit3(),此時Unit3handle方法被呼叫,如下:

public function handle($passable, callable $next = null)
{
     echo __CLASS__ . '->' . __METHOD__ . " called\n";
     $next($passable);
}

$passable為1,$next就是c2c2Unit3handle方法中被呼叫了,那麼呼叫c2的時候呢?$passable為1,$stackc1Unit2handleUnit3handle方法是一模一樣的,所以此時c1被呼叫,c1被呼叫的時候$passable為1,$stackinitinit就是這個啊:

function ($passable) {
    (new InitialValue())->handle($passable);
}

是不是感覺說清楚了,也很簡單啊?

上面就是整個Laravel管道的精髓了,希望大家能夠理解。

總結

還是那句話,要想能力比別人強,必須付出異於常人的毅力。歡迎大家加入下面的qq群,平時可以多多交流:

如果有不懂的地方,可以加我的qq:1174332406,或者是微信:itshardjs

相關文章