手把手帶你理解中介軟體Pipeline_function then()原理

Ruma_z發表於2021-07-06

Pipeline類中function then()的實現原理

// 檔案 vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
// 管道類
class Pipeline implements PipelineContract
{
    public function then(Closure $destination)
    {
        $pipeline = array_reduce(
            array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
        );

        return $pipeline($this->passable);
    }

    protected function prepareDestination(Closure $destination)
    {
        return function ($passable) use ($destination) {
            try {
                return $destination($passable);
            } catch (Throwable $e) {
                return $this->handleException($passable, $e);
            }
        };
    }

    protected function carry()
    {
        return function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                try {
                    if (is_callable($pipe)) {
                        return $pipe($passable, $stack);
                    } elseif (! is_object($pipe)) {
                        [$name, $parameters] = $this->parsePipeString($pipe);
                        $pipe = $this->getContainer()->make($name);
                        $parameters = array_merge([$passable, $stack], $parameters);
                    } else {
                        $parameters = [$passable, $stack];
                    }
                    $carry = method_exists($pipe, $this->method)
                                    ? $pipe->{$this->method}(...$parameters)
                                    : $pipe(...$parameters);

                    return $this->handleCarry($carry);
                } catch (Throwable $e) {
                    return $this->handleException($passable, $e);
                }
            };
        };
    }

}

要理解Pipeline類中then實現中介軟體的原理需要著重瞭解一下重點:

  1. 內建函式:array_reduce()
  2. 閉包型別:Closure
  3. 內建函式:array_reverse()

下面手把手帶你實現下面的功能:

手把手帶你理解中介軟體Pipeline_function then()原理

then()功能分析實現

我們可以透過dd($this->pipes())列印發現,$this->pipes()是一箇中介軟體的陣列

手把手帶你理解中介軟體Pipeline_function then()原理

$this->carry() 和 $destination都是一個閉包

步驟一 :

那麼我們來簡單模仿一下寫個簡單的方法來先了解下array_reduce()

先寫一個簡單的控制器:

// 檔案 /routes/web.php
Route::get('/','TestController@index');

// 檔案 /app/Http/Controllers/TestController.php
public function index()
{
    $funcA = function () {
        echo "funcA <br/>";
    };

    $funcB = function () {
        echo "funcB <br/>";
    };

    $funcC = function () {
        echo "funcC <br/>";
    };
    $pipes = [$funcA, $funcB, $funcC];
    // $carry 是上一個函式的返回值; 若一開是第三個引數為空時 則為null
    // $item 陣列的元素值
    // 此處我們不考慮$carry 我們讓他每次都執行 閉包$item()
    array_reduce($pipes, function ($carry, $item) {
        $item();
    });
}

結果:手把手帶你理解中介軟體Pipeline_function then()原理

步驟二

現在我們再修改一下,此時我們閉包內不執行$item,而是作為$carry值返回給下一次迴圈作為引數呼叫;並且加入第三個引數,為$carry賦一個預設值:

public function index()
{

    ... ... // 前面內容不變

        // 第三個引數為一個 字串 , 這個值會作為第一次呼叫時,$carry的值
    $finalFunc = array_reduce($pipes, function ($carry, $item) {
        // 如果是字串則直接列印
        if (is_string($carry)) {
            echo $carry;
        } elseif ($carry instanceof \Closure) {
            // 如果是閉包則執行
            $carry();
        }
        // 向下一個呼叫的$carry返回當前的閉包$item;
        return $item;
    }, "Init<br/>");

    // 三次迴圈的引數與返回如下:
    // 第一次引數:$carry= "Init<br/>"; $item=closure funcA() 返回 closure funcA()
    // 第二次引數:$carry= closure funcA(); $item=closure funcB() 返回 closure funcB()
    // 第三次引數:$carry= closure funcB(); $item=closure funcC() 返回 closure funcC()

    // 此時的返回值為 closure funcC() 直接呼叫就是呼叫funcC
    $finalFunc();
}

執行結果:手把手帶你理解中介軟體Pipeline_function then()原理

我們好像發現,離按順序執行中介軟體已經越來越近了,那我們繼續修改。

步驟三

改變如下

public function index()
{
    // 1. 為閉包新增引數,
    $middelwareA = function ($request, \Closure $next) {
        echo "middlewareA hanlde request <br/>";
    };
    $middlewareB = function ($request, \Closure $next) {
        echo "middlewareB hanlde request <br/>";
    };
    $middlewareC = function ($request, \Closure $next) {
        echo "middlewareC hanlde request <br/>";
    };

    $pipes = [$middelwareA, $middlewareB, $middlewareC];

    // 2. 增加一個變數request
    $request = 'request';
    // 3. 增加一個閉包,模仿原始碼的中的引數$destination
    $callback = function ($request) {
        echo "callback handler request <br/>";
    };
    // 4. 改變第三個引數,傳入一個閉包(下面新增了方法一個新的方法來返回閉包)
    $finalFunc = array_reduce($pipes, $this->carry(),$this->prepareDestination($callback));

    // 執行解釋: 此時最終返回的是一個閉包 closure funcC($request,$carry)
    // 執行解釋: 因為前面的callback funcA funcB 我們都沒做任何處理的直接丟掉了
    $finalFunc($request);
}

// 6. 內容改簡單一點,直接呼叫$item()
public function carry()
{
    return function ($carry, $item) {
        return function ($request) use ($carry, $item) {
            // 第一次引數:$carry= closure $callback($request); $item=closure funcA() 返回 closure($request,$carry)
            // 第二次引數:$carry= closure funcA($request,$carry); $item=closure funcB() 返回 $item=closure funcB($request,$carry)
            // 第二次引數:$carry= closure funcB($request,$carry); $item=closure funcC() 返回 $item=closure funcC($request,$carry)
            return $item($request, $carry);
            //                 $item($request, $carry);
        };
    };
}

public function prepareDestination($callback)
{
    return function ($request) use ($callback) {
        return $callback($request);
    };
}

執行結果 : middlewareC hanlde request

解釋如程式碼註釋所示

我們再微調一下程式碼

步驟四
 public function index()
    {
        // 1. 為閉包新增引數,
        $middelwareA = function ($request, \Closure $next) {
            echo "middlewareA hanlde request <br/>";
            // 此處的$next = closure $callback($request)
            return $next($request);
        };
        $middlewareB = function ($request, \Closure $next) {
            echo "middlewareB hanlde request <br/>";
            // 此處的$next = closure funcA($request,closure $callback($request))
            return $next($request);
        };
        $middlewareC = function ($request, \Closure $next) {
            echo "middlewareC hanlde request <br/>";
            // 此處的$next = closure funcB($request,closure funcA(...))
            return $next($request);
        };

        ... ... // 中間程式碼省略

        $finalFunc($request);
    }

結果:手把手帶你理解中介軟體Pipeline_function then()原理

好的,中介軟體的原理實現的差不多了。

我們想想如何實現前後置中介軟體

步驟五
public function index()
{
    $middelwareA = function ($request, \Closure $next) {
        echo "before middlewareA hanlde request <br/>";
        // 此處的$next = closure $callback($request)
        $response = $next($request);
        echo "after middlewareA hanlde request <br/>";
        return $response;
    };
    $middlewareB = function ($request, \Closure $next) {
        echo "before middlewareB hanlde request <br/>";
        // 此處的$next = closure funcA($request,closure $callback($request))
        $response = $next($request);
        echo "after middlewareB hanlde request <br/>";
        return $response;
    };

    $middlewareC = function ($request, \Closure $next) {
        echo "before middlewareC hanlde request <br/>";
        // 此處的$next = closure funcB($request,closure funcA(...))
        $response = $next($request);
        echo "after middlewareC hanlde request <br/>";
        return $response;
    };

    ... ... // 中間程式碼省略

    $finalFunc($request);
}

執行結果:
手把手帶你理解中介軟體Pipeline_function then()原理

至此,大家應該瞭解了then是如何實現中介軟體的原理的了吧.(關於中介軟體的執行順序就留給大家自己思考吧)

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章