Laravel 中介軟體處理的核心機制 Pipeline 關鍵分析

LJ2016發表於2019-10-11

Illuminate/Pipeline/Pipeline.php

/**
     * Run the pipeline with a final destination callback.
     *
     * @param  \Closure  $destination
     * @return mixed
     */
    public function then(Closure $destination)
    {
        $pipeline = array_reduce(
            array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
        );

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

    /**
     * Get the final piece of the Closure onion.
     *
     * @param  \Closure  $destination
     * @return \Closure
     */
    protected function prepareDestination(Closure $destination)
    {
        return function ($passable) use ($destination) {
            return $destination($passable);
        };
    }

    /**
     * Get a Closure that represents a slice of the application onion.
     *
     * @return \Closure
     */
    protected function carry()
    {
        return function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                if (is_callable($pipe)) {
                    // If the pipe is an instance of a Closure, we will just call it directly but
                    // otherwise we'll resolve the pipes out of the container and call it with
                    // the appropriate method and arguments, returning the results back out.
                    return $pipe($passable, $stack);
                } elseif (! is_object($pipe)) {
                    list($name, $parameters) = $this->parsePipeString($pipe);

                    // If the pipe is a string we will parse the string and resolve the class out
                    // of the dependency injection container. We can then build a callable and
                    // execute the pipe function giving in the parameters that are required.
                    $pipe = $this->getContainer()->make($name);

                    $parameters = array_merge([$passable, $stack], $parameters);
                } else {
                    // If the pipe is already an object we'll just make a callable and pass it to
                    // the pipe as-is. There is no need to do any extra parsing and formatting
                    // since the object we're given was already a fully instantiated object.
                    $parameters = [$passable, $stack];
                }

                $response = method_exists($pipe, $this->method)
                                ? $pipe->{$this->method}(...$parameters)
                                : $pipe(...$parameters);

                return $response instanceof Responsable
                            ? $response->toResponse($this->container->make(Request::class))
                            : $response;
            };
        };
    }
  1. 管道處理的核心方法then,實際上是一個非常有用的PHP函式array_reduce($array, $callback, $initial)的應用,只不過待處理陣列變成了可呼叫的中介軟體函式(字串或closure表示的),這些函式都只接受一個引數即Request例項;
  2. 處理函式因為有額外引數$passable(即 Request例項)需要傳入,所以多包了一層閉包函式【注:關於多層包含的閉包函式,在Python裝飾器原理中可以充分理解,尤其是阮一峰的逐步遞進寫完善的裝飾器過程】;
  3. 初始值也是一個閉包函式,這個是真正的response響應函式。

$this->method = 'handle' 這個方法名,可通過via函式修改。

中介軟體類方法 handle($request, Closure $next) 有2個引數,這個對應閉包處理過後的中介軟體函式的返回值

return $pipe($passable, $stack)。

Laravel中介軟體邏輯,是閉包函式強大功能的一大體現。於是我們使用閉包函式,以函數語言程式設計風格實現簡化版的中介軟體流程:

function f1($f){
    return function($x) use ($f){
        echo 'middleware 1 begin.'.PHP_EOL;
        $x += 1;
        $x = $f($x);
        echo 'middleware 1 end.'.PHP_EOL;
        return $x;  
    };

}

function f2($f){
    return function($x) use($f){
        echo 'middleware 2 begin: '.PHP_EOL;
        $x += 2;
        $x = $f($x);
        echo 'middleware 2 end.'.PHP_EOL;
        return $x;
    };

}

function respond(){
    return function($x){
        echo 'Generate some response.'.PHP_EOL;
        return $x;
    };
}

$x = 1;
$response = f2(f1(respond()))($x);
echo $response;

輸出:
middleware 2 begin:
middleware 1 begin.
Generate some response.
middleware 1 end.
middleware 2 end.
4

可以看出為什麼Laravel的Pipeline的實現需要反轉中介軟體陣列。

高階函式的實現,可以通過層層傳遞"函式名字串"的原始方法來實現,但是通過閉包函式實現更加簡單。
array_reduce寫法

$f1 = function($x, $f){
    echo 'middleware 1 begin.'.PHP_EOL;
    $x += 1;
    $x = $f($x);
    echo 'middleware 1 end.'.PHP_EOL;
    return $x;  
};

$f2 = function($x, $f){
    echo 'middleware 2 begin: '.PHP_EOL;
    $x += 2;
    $x = $f($x);
    echo 'middleware 2 end.'.PHP_EOL;
    return $x;
};

$respond = function($x){
    echo 'Generate some response.'.PHP_EOL;
    return $x;
};

$middlewares = [$f1, $f2];
$initial = $respond;
$foo = array_reduce($middlewares, function($stack, $item){
    return function($request) use ($stack, $item){
        return $item($request, $stack);
    };
}, $initial);

$x = 1;
echo $foo($x);

輸出:
middleware 2 begin:
middleware 1 begin.
Generate some response.
middleware 1 end.
middleware 2 end.
4

相關文章