検索
ホームページPHPフレームワークThinkPHPThinkPHP6.0パイプラインモードとミドルウェアの実装分析

説明

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で複製されています。侵害がある場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

MantisBT

MantisBT

Mantis は、製品の欠陥追跡を支援するために設計された、導入が簡単な Web ベースの欠陥追跡ツールです。 PHP、MySQL、Web サーバーが必要です。デモおよびホスティング サービスをチェックしてください。

mPDF

mPDF

mPDF は、UTF-8 でエンコードされた HTML から PDF ファイルを生成できる PHP ライブラリです。オリジナルの作者である Ian Back は、Web サイトから「オンザフライ」で PDF ファイルを出力し、さまざまな言語を処理するために mPDF を作成しました。 HTML2FPDF などのオリジナルのスクリプトよりも遅く、Unicode フォントを使用すると生成されるファイルが大きくなりますが、CSS スタイルなどをサポートし、多くの機能強化が施されています。 RTL (アラビア語とヘブライ語) や CJK (中国語、日本語、韓国語) を含むほぼすべての言語をサポートします。ネストされたブロックレベル要素 (P、DIV など) をサポートします。

WebStorm Mac版

WebStorm Mac版

便利なJavaScript開発ツール

VSCode Windows 64 ビットのダウンロード

VSCode Windows 64 ビットのダウンロード

Microsoft によって発売された無料で強力な IDE エディター