laravel5.5源碼筆記(5、Pipeline管道模式)

Pipeline管道模式,也有人叫它裝飾模式。應該說管道是裝飾模式的一個變種,雖然思想都是同樣的,但這個是閉包的版本,實現方式與傳統裝飾模式也不太同樣。在laravel的源碼中算是一個比較核心的設計模式了。
管道模式,或者說裝飾模式的思想,就是在不改變原有程序的基礎上,能夠方便的在已有程序上添加新的功能。php

在說管道模式以前讓咱們看一下array_reduce這個函數laravel

mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )

array_reduce() 將回調函數 callback 迭代地做用到 array 數組中的每個單元中,從而將數組簡化爲單一的值。

array
輸入的 arraycallback
mixed callback ( mixed $carry , mixed $item )
        carry
        攜帶上次迭代裏的值; 若是本次迭代是第一次,那麼這個值是 initial。

        item
        攜帶了本次迭代的值。

initial
若是指定了可選參數 initial,該參數將在處理開始前使用,或者當處理結束,數組爲空時的最後一個結果。

 

 

以上是php官網對這個函數的解釋,看的一頭霧水對不對?不要緊,咱們先來一個demo設計模式

 

 

//首先咱們有一個數組
$a = array(1, 2, 3, 4, 5);
$x = array();

//咱們輸出了array_reduce的結果爲15,這個函數傳遞了兩個參數,第一個就是上面的數組a,咱們重點來看第二個閉包函數
var_dump(array_reduce($a, "sum")); // int(15)

//array_reduce的第三個參數會在循環開始的時候當作閉包函數的第一個carry值傳入
var_dump(array_reduce($a, "product", 10)); // int(1200), because: 10*1*2*3*4*5

//當array_reduce處理結束後,若結果爲空,也會將第三個參數返回
var_dump(array_reduce($x, "sum", "No data to reduce")); // string(17) "No data to reduce"

//這個閉包函數接收了兩個參數
function sum($carry, $item)
{
    echo 'before___carry:'.$carry.'<br><br>';
    echo 'before___item:'.$item.'<br><br>';

    //這個函數只是簡單的對兩個參數作了加法
    $carry += $item;

    echo 'after___carry:'.$carry.'<br><br>';
    echo '<br><br>';
    return $carry;
}

function product($carry, $item)
{
    echo 'before___carry:'.$carry.'<br><br>';
    echo 'before___item:'.$item.'<br><br>';

    $carry *= $item;

    echo 'after___carry:'.$carry.'<br><br>';
    echo '<br><br>';
    return $carry;
}

你們在運行過這個demo以後,便會發現,array_reduce只是一個循環函數。可是,它和foreach不同的地方在於,它的閉包處理函數所接收的兩個參數。其中的$item參數會在每次循環的時候代入數組的各項值,而$carry就比較專注了,它只接收上一次循環中本身的返回值。不過專注也只是相對的,$carray還接收array_reduce傳入的第三個參數做爲閉包開始循環前$carray的默認值,或是循環結束後返回值爲null時的默認值。我在代碼中已經把變量各時期的值給打印出來了,相信你們一看就會明白。這個函數最典型的例子就是在作累計運算的時候,咱們不須要向foreach循環那樣建立一個變量了。(你們都知道給變量起名字是一件很麻煩的事情)api

ok,函數介紹完了,但是這和管道模式又有什麼關係呢?不知道你們注意到沒有,在array_reduce的閉包函數進行循環的時候,array_reduce的第三個參數從閉包的$array進入,從return返回,緊接着再次從$array中進入,而每次循環的時候,都會從$item中獲取到外部進行運算。這個過程是否是很像數據在一個螺旋管道中流通,同時不斷的向其中添加新的操做,而且沒有改變原先的程序代碼,下降了程序間的耦合度。數組

 接下來進行管道模式瀏覽器

請求處理管道的思惟導圖

思惟導圖看的還不夠清晰?ok,接着舉例子,看demo閉包

從圖中咱們能夠知道類Request都具備相同的接口,那麼咱們能夠約定一個接口規範函數

interface RequestInterface
{
    // 咱們以前講過裝飾者模式,這裏是經過閉包函數實現
    // 經過以後實現類及調用就能夠看出
    public static function handle(Closure $next);
}

 

接口有了那麼咱們就遵循接口開始實現this

 

class Request1 implements RequestInterface
{
    public static function handle(Closure $next)
    {
        echo "Request1 Begin." . "<br />";
        $next();
        echo "Request1 End." . "<br />";
    }
}

class Request2 implements RequestInterface
{
    public static function handle(Closure $next)
    {
        echo "Request2 Begin." . "<br />";
        $next();
        echo "Request2 End." . "<br />";
    }
}

class Request3 implements RequestInterface
{
    public static function handle(Closure $next)
    {
        echo "Request3 Begin." . "<br />";
        $next();
        echo "Request3 End." . "<br />";
    }
}

class Request4 implements RequestInterface
{
    public static function handle(Closure $next)
    {
        echo "Request4 Begin." . "<br />";
        $next();
        echo "Request4 End." . "<br />";
    }
}

 

 

四種請求處理過程咱們均已實現,爲了簡化都是打印一句話,能夠在瀏覽器上直觀顯示流程;
咱們還須要一個客戶端來發出請求spa

 

class Client
{
    // 這裏包含了全部的請求
    private $pipes = [
        'Request1',
        'Request2',
        'Request3',
        'Request4',
    ];

    // 這裏就是思惟導圖中默認返回的匿名回調函數
    private function defaultClosure()
    {
        return function () {
            echo '請求處理中...' . "<br />";
        };
    }

    // 這裏就是整個請求處理管道的關鍵
    private function getSlice()
    {
        return function ($stack, $pipe)
        {
            return function () use ($stack, $pipe)
            {
                return $pipe::handle($stack);
            };
        };
    }

    // 這裏是負責發起請求處理
    public function then()
    {
        call_user_func(array_reduce($this->pipes, $this->getSlice(), $this->defaultClosure()));
    }
}

當咱們調用時:

$worker = new Client();
$worker->then();

瀏覽器就會顯示:

那麼我來解釋一下整個流程吧,在代碼註釋說了getSlice是關鍵,那麼咱們來解讀一下代碼

解析代碼

 

client的then方法,call_user_func是執行一個函數的api,它的參數是一個閉包函數。而這個參數則是咱們文章一開始提到的array_reduce這個循環的最終返回值。

咱們都還記得這個函數一共有三個參數:

第一個參數表明從外界加入到管道中的附加操做

第二個參數則是執行加工的工廠

第三個參數則是進入管道的程序數據源

 

先看第三個參數數據源

    // 這裏就是思惟導圖中默認返回的匿名回調函數
    private function defaultClosure()
    {
        //這個return出去的閉包就是咱們的數據源
        return function () {
            echo '請求處理中...' . "<br />";
        };
    }

 而後對程序進行裝飾的裝飾品數組
// 這裏包含了全部的請求
    private $pipes = [
        'Request1',
        'Request2',
        'Request3',
        'Request4',
    ];

這裏雖然只是個數組,但它在剛剛的循環閉包中被當作對象名來使用,經過名稱調度不一樣的類

而後是循環閉包

    // 這裏就是整個請求處理管道的關鍵
    private function getSlice()
    {
        //這個函數咱們的閉包循環函數
        return function ($stack, $pipe) { //而這裏即是咱們最開始例子中的return返回值,只不過剛開始的時候咱們返回的是一個數字變量,這裏變成了另外一個閉包 return function () use ($stack, $pipe) { return $pipe::handle($stack); }; }; }

這裏的循環閉包返回值也是一個閉包,在不斷的循環中,閉包函數就像洋蔥同樣,一層一層不斷包裹。

如此類推,最終array_reduce返回一個匿名函數,這就像上面思惟導圖最終所描述的同樣

function (){
    return Request4::handle(function (){
        return Request3::handle(function (){
            return Request2::handle(function (){
                return Reuqest1::handle(function (){
                    echo '請求處理中...' . "<br />";
                });
            });
        });
    });
}

因此當調用call_user_func時就是執行array_reduce返回的匿名函數,咱們從各個請求處理類的handle方法也得知,會直接調用傳入的匿名函數,注意順序便可理解瀏覽器的輸出。

與裝飾者模式的關係

這裏沒有用到類來對實例對象進行包裝,而是經過閉包函數完成,每一次處理在上一次處理之上進行包裝,最後得到響應,就像流水線同樣,一個請求進來經過一道道工序加工(包裝)最後生成響應。

 

理解了這幾個demo,那麼laravel裏中間件最重點的部分你就已經理解了。

 

最後,本文內容並不是所有原創,部分圖片與代碼來源:http://blog.chenjunwu.cn/2017/05/02/Request-pipeline

相關文章
相關標籤/搜索