管道模式執行全域性中介軟體

lxdong12發表於2020-01-20

Http類的run方法

    public function run(Request $request = null): Response
    {
        //自動建立request物件
        $request = $request ?? $this->app->make('request', [], true);
        $this->app->instance('request', $request);

        try {
            // 得到 Response 類物件
            $response = $this->runWithRequest($request);
        ...
    }

runWithRequest 方法

    protected function runWithRequest(Request $request)
    {
        ...
        // 執行了路由獲得 Response 例項,其間經過全域性中介軟體過濾
        return $this->app->middleware->pipeline()
            ->send($request)
            ->then(function ($request) {
                return $this->dispatchToRoute($request);
            });
    }

框架中的全域性中介軟體預設都是註釋起來的,現在我們開啟兩個

<?php
// 全域性中介軟體定義檔案
return [
    // 全域性請求快取
    // \think\middleware\CheckRequestCache::class,
    // 多語言載入
    \think\middleware\LoadLangPack::class,
    // Session初始化
    \think\middleware\SessionInit::class,
    // 頁面Trace除錯
    // \think\middleware\TraceDebug::class,
];

$this->app->middleware->pipeline() 例項化 middleware 類,執行 middleware 的 pipeline 方法

public function pipeline(string $type = 'global')
{
    return (new Pipeline())
        ->through(array_map(function ($middleware) {
            return function ($request, $next) use ($middleware) {
                [$call, $param] = $middleware;
                if (is_array($call) && is_string($call[0])) {
                    $call = [$this->app->make($call[0]), $call[1]];
                }
                $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']);
}

這裡就是返回了一個 pipeline 類的例項,同時呼叫 pipeline 的 through 方法

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

結果是 pipeline 類例項的 pipes 屬性儲存了一個陣列,陣列中的每個元素都是一個閉包函式,閉包函式內執行了中介軟體類的 handle 方法

pipeline 的 then 方法

public function then(Closure $destination)
{
    $pipeline = array_reduce(
        array_reverse($this->pipes),
        $this->carry(),
        function ($passable) use ($destination) {
            try {
                return $destination($passable);
            } catch (Throwable | Exception $e) {
                return $this->handleException($passable, $e);
            }
        });

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

我們把 carry 方法的程式碼合併過來,去掉異常捕捉的程式碼,看下

$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);
    });

前面我們開啟了兩個全域性中介軟體,pipes 陣列中就有兩個閉包, array_reduce 會迭代兩次,我們來看下第一次迭代
此時 $stack 的初始值是 array_reduce 的第三個引數,我們命名為 A

(A)

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

array_reduce 第二個引數的返回值是 B

(B)

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

將 A 代入 B,得到第一次迭代的返回值 C,也是第二次迭代的 $stack 的值

(C)

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

第二次迭代,將 C 代入 B,得到 D

(D)

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

最終 $pipeline 就是 D 這樣一個超級閉包,這裡 use 的兩個引數 $stack 已經更換成了相關程式碼, $pipe 是執行中介軟體 handle 方法的閉包

或者我們可以這樣理解這個閉包 D,當然 handle 方法不是靜態的,明白意思就好

function ($passable) {
    return LoadLangPack::handle($passable, function ($passable) {
        return SessionInit::handle($passable, function ($passable) use ($destination) {
            return $destination($passable);
        });
    });
}

$destination($passable) 方法就是執行路由,返回 response

中介軟體程式碼的執行順序

handle 方法的程式碼大概是這樣的

public function handle($request, Closure $next)
{
    // 前置操作

    $response = $next($request);

    // 後置操作

    return $response;
}

我們這裡的實際執行順序就是 LoadLangPack::handle 前置操作 -> SessionInit::handle 前置操作 -> 執行路由 -> SessionInit::handle 後置操作 -> LoadLangPack::handle 後置操作

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

相關文章