ThinkPHP6.0パイプラインモードとミドルウェアの実装分析

藏色散人
リリース: 2019-11-08 17:58:00
転載
4134 人が閲覧しました

説明

ThinkPHP 6.0 RC5 では、パイプライン モードを使用してミドルウェアを実装するようになりました。これは、以前のバージョンの実装よりも簡潔で整然としています。この記事では、その実装の詳細を分析します。

まず、エントリ ファイル public/index.php から開始します。$http = (new App())->http;

http クラスのインスタンスを取得し、その実行を呼び出します。 Method :$response = $http->run(); そして、その run メソッドが runWithRequest メソッドを呼び出します:

protected function runWithRequest(Request $request)
{
    .
    .
    .
    return $this->app->middleware->pipeline()
        ->send($request)
        ->then(function ($request) {
            return $this->dispatchToRoute($request);
        });
}
ログイン後にコピー

ミドルウェアの実行は最後の return ステートメントで行われます。

パイプライン、スルー、メソッド送信

$this->app->middleware->pipeline() 的 pipeline 方法:
public function pipeline(string $type = 'global')
{
    return (new Pipeline())  
           // array_map将所有中间件转换成闭包,闭包的特点:
          // 1. 传入参数:$request,请求实例; $next,一个闭包
          // 2. 返回一个Response实例
        ->through(array_map(function ($middleware) {
            return function ($request, $next) use ($middleware) {
                list($call, $param) = $middleware;
                if (is_array($call) && is_string($call[0])) {
                    $call = [$this->app->make($call[0]), $call[1]];
                }
                 // 该语句执行中间件类实例的handle方法,传入的参数是外部传进来的$request和$next
                 // 还有一个$param是中间件接收的参数
                $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']);
}
ログイン後にコピー

スルー メソッド コード:

public function through($pipes)
{
    $this->pipes = is_array($pipes) ? $pipes : func_get_args();
    return $this;
}
ログイン後にコピー

前の呼び出しスルーは受信 array_map です(...) ミドルウェアをクロージャにカプセル化し、これらのクロージャを Pipeline クラスの $pipes 属性に保存します。

PHP の array_map メソッド シグネチャ:

array_map ( callable $callback , array $array1 [, array $... ] ) : array
ログイン後にコピー

$callback は $array の各要素を反復処理し、新しい値を返します。したがって、$pipes 内の各クロージャの最終的な正式な特性は次のとおりです (擬似コード):

function ($request, $next) {
    $response = handle($request, $next, $param);
    return $response;
}
ログイン後にコピー
ログイン後にコピー

このクロージャは 2 つのパラメータを受け取ります。1 つはリクエスト インスタンスで、もう 1 つはコールバック関数、ハンドル メソッドです。 、応答が取得されて返されます。

through は Pipeline クラスのインスタンスを返し、send メソッドを呼び出します:

public function send($passable)
{
    $this->passable = $passable;
    return $this;
}
ログイン後にコピー

このメソッドは非常に単純で、受信したリクエストのインスタンスを $passable メンバー変数に保存するだけです。最後に Pipeline クラスのインスタンスを返すため、Pipeline クラスの他のメソッドをチェーンで呼び出すことができます。

次に、キャリーメソッド

send メソッドの後に、then メソッドを呼び出します:

return $this->app->middleware->pipeline()
            ->send($request)
            ->then(function ($request) {
                return $this->dispatchToRoute($request);
            });
ログイン後にコピー

then ここでクロージャをパラメータとして受け取ります。このクロージャ パッケージには実際にコントローラ操作の実行コードが含まれています。

then メソッド コード:

public function then(Closure $destination)
{
    $pipeline = array_reduce(
        //用于迭代的数组(中间件闭包),这里将其倒序
        array_reverse($this->pipes),
        // array_reduce需要的回调函数
        $this->carry(),
        //这里是迭代的初始值
        function ($passable) use ($destination) {
            try {
                return $destination($passable);
            } catch (Throwable | Exception $e) {
                return $this->handleException($passable, $e);
            }
        });
    return $pipeline($this->passable);
}
ログイン後にコピー

キャリー コード:

protected function carry()
{
    // 1. $stack 上次迭代得到的值,如果是第一次迭代,其值是后面的「初始值
    // 2. $pipe 本次迭代的值
    return function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            try {
                return $pipe($passable, $stack);
            } catch (Throwable | Exception $e) {
                return $this->handleException($passable, $e);
            }
        };
    };
}
ログイン後にコピー

原理を分析しやすくするために、キャリー メソッドを内部に配置し、それを then に接続し、エラー キャプチャ コードを削除して次の情報を取得します:

public function then(Closure $destination)
{
    $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);
        });
    return $pipeline($this->passable);
}
ログイン後にコピー

ここで重要なのは、array_reduce と $pipeline($this->passable) の実行プロセスを理解することです。 「玉ねぎを包む」と「玉ねぎの皮をむく」という2つの工程に例えられます。

array_reduce の最初の反復、$stack の初期値は次のとおりです:

(A)

function ($passable) use ($destination) {
    return $destination($passable);
});
ログイン後にコピー

コールバック関数の戻り値は次のとおりです:

(B)

function ($passable) use ($stack, $pipe) {
    return $pipe($passable, $stack);
};
ログイン後にコピー

A を B に代入して、最初の反復後の $stack の値を取得します。

(C)

function ($passable) use ($stack, $pipe) {
    return $pipe($passable, 
        function ($passable) use ($destination) {
            return $destination($passable);
        })
    );
};
ログイン後にコピー

2 回目の反復では、同様に C を B に代入して次の結果を取得します:

(D)

// 伪代码
// 每一层的$pipe都代表一个中间件闭包
function ($passable) use ($stack, $pipe) {
    return $pipe($passable,  //倒数第二层中间件
        function ($passable) use ($stack, $pipe) {
            return $pipe($passable,  //倒数第一层中间件
                function ($passable) use ($destination) {
                    return $destination($passable);  //包含控制器操作的闭包
                })
            );
        };
    );
};
ログイン後にコピー

そして必要なだけミドルウェアを置換し、最後に $stack を取得したときに $pipeline に返します。ミドルウェア クロージャは前の順序で逆になっているため、前のクロージャは内側の層でラップされます。そのため、逆順序の後のクロージャは外側にあり、順順序から見ると、前のクロージャになります。 . ミドルウェアは最も外側の層です。

クロージャを層ごとにラップすると、タマネギ構造に似た「スーパー」クロージャ D が得られます。このクロージャの構造は、上記のコード コメントに示されているとおりです。最後に、$request オブジェクトをこのクロージャに渡して $pipeline($this->passable); を実行すると、玉ねぎの皮をむくのと同じようなプロセスが開始されます。

玉ねぎの皮をむくプロセスの分析

array_map(...) 各ミドルウェア クラスを次の構造に似たクロージャに処理します:

function ($request, $next) {
    $response = handle($request, $next, $param);
    return $response;
}
ログイン後にコピー
ログイン後にコピー

ハンドルはミドルウェアの入り口であり、その構造的特徴は次のとおりです。

public function handle($request, $next, $param) {
    // do sth ------ M1-1 / M2-1
    $response = $next($request);
    // do sth ------ M1-2 / M2-2
    return $response;
}
ログイン後にコピー

上記の「オニオン」には合計 2 層しかありません。つまり、M1- と仮定すると、ミドルウェアのクロージャが 2 層あります。 1 と M1-2 はそれぞれ、最初のミドルウェア ハンドル メソッドのプレフィックスとポストバリューの操作点であり、2 番目のミドルウェアである M2-1 と M2-2 にも同じことが当てはまります。ここで、プログラムに $pipeline($this->passable) を実行させ、展開します。つまり、次を実行します。

// 伪代码
function ($passable) use ($stack, $pipe) {
    return $pipe($passable,  
        function ($passable) use ($stack, $pipe) {
            return $pipe($passable,  
                function ($passable) use ($destination) {
                    return $destination($passable);  
                })
            );
        };
    );
}($this->passable)
ログイン後にコピー

この時点で、プログラムは次からの戻り値を必要とします:

return $pipe($passable,  
    function ($passable) use ($stack, $pipe) {
        return $pipe($passable,  
            function ($passable) use ($destination) {
                return $destination($passable);  
            })
        );
    };
);
ログイン後にコピー

、つまり、最初のミドルウェア クロージャを実行するには、$passable がハンドル メソッドの $request パラメータに対応し、次のレベルのクロージャ

function ($passable) use ($stack, $pipe) {
    return $pipe($passable,  
        function ($passable) use ($destination) {
            return $destination($passable);  
        })
    );
}
ログイン後にコピー

がハンドル メソッドの $next パラメータに対応します。

最初のクロージャを実行する、つまり最初のクロージャのハンドル メソッドを実行するプロセスは次のとおりです。まず、ポイント M1-1、つまり事前操作のコードを実行し、次に実行します。 $response = $next($request); の場合、プログラムは次のクロージャの実行に入り、$next($request) が展開されます (つまり:

function ($passable) use ($stack, $pipe) {
    return $pipe($passable,  
        function ($passable) use ($destination) {
            return $destination($passable);  
        })
    );
}($request)
ログイン後にコピー

など)。クロージャを実行します。このとき、ミドルウェアのハンドル メソッドは、最初に M2-1 ポイントを実行し、次に $response = $next($request) を実行します。このときの $next クロージャは、次のとおりです:

function ($passable) use ($destination) {
    return $destination($passable);  
})
ログイン後にコピー

オニオンのコアに属します— — 最も内側のレイヤー (コントローラー操作を含むクロージャー) を展開します:

function ($passable) use ($destination) {
    return $destination($passable);  
})($request)
ログイン後にコピー

最终,我们从 return $destination($passable) 中返回一个 Response 类的实例,也就是,第二层的 $response = $next($request) 语句成功得到了结果,接着执行下面的语句,也就是 M2-2 点位,最后第二层闭包返回结果,也就是第一层闭包的 $response = $next($request) 语句成功得到了结果,然后执行这一层闭包该语句后面的语句,即 M1-2 点位,该点位之后,第一层闭包也成功返回结果,于是,then 方法最终得到了返回结果。

整个过程过来,程序经过的点位顺序是这样的:M1-1→M2-1→控制器操作→M2-2→M1-2→返回结果。

总结

整个过程看起来虽然复杂,但不管中间件有多少层,只要理解了前后两层中间件的这种递推关系,洋葱是怎么一层层剥开又一层层返回的,来多少层都不在话下。

以上がThinkPHP6.0パイプラインモードとミドルウェアの実装分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:learnku.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート