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 協議》,轉載必須註明作者和本文連結