説明
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 サイトの他の関連記事を参照してください。