繼續昨天的 runRoute()
。
直接來看原始碼:
protected function runRoute(Request $request, Route $route) { $request->setRouteResolver(function () use ($route) { return $route; });
$this->events->dispatch(new Events\RouteMatched($route, $request));
return $this->prepareResponse($request, $this->runRouteWithinStack($route, $request) ); }
|
從 runRouteWithinStack()
取得結果後,再給 prepareResponse()
產生 Response。
protected function runRouteWithinStack(Route $route, Request $request) { $shouldSkipMiddleware = $this->container->bound('middleware.disable') && $this->container->make('middleware.disable') === true;
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
return (new Pipeline($this->container)) ->send($request) ->through($middleware) ->then(function ($request) use ($route) { // 實際執行 route 的地方在這裡:$route->run() return $this->prepareResponse( $request, $route->run() ); }); }
|
Route 的 middleware 是用 gatherRouteMiddleware()
產生的:
public function gatherRouteMiddleware(Route $route) { $middleware = collect($route->gatherMiddleware())->map(function ($name) { return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups); })->flatten();
return $this->sortMiddleware($middleware); }
|
Route::gatherMiddleware()
會先取得 Route 裡面設定的 middleware,再交由 MiddlewareNameResolver::resolve()
解析成 Pipeline 可以使用的 middleware 格式;最後使用 sortMiddleware()
排序。
Middleware 的產生比較複雜,留著明天說明,我們先了解 gatherRouteMiddleware()
會產生屬於該 Route 的一組 middleware 就好。
接著,真正執行 Route 的地方就在 $route->run()
:
public function run() { $this->container = $this->container ?: new Container;
try { if ($this->isControllerAction()) { return $this->runController(); }
return $this->runCallable(); } catch (HttpResponseException $e) { return $e->getResponse(); } }
|
再繼續看 runController()
和 runCallable()
之前,我們回憶一下:之前使用 Container::make()
的時候,它都會在建構的時候注入;今天的狀況則不大一樣,Laravel 是要在呼叫 controller method 的時候注入。可以預見的是,待會肯定會看到反射(Reflection)處理。
先看比較單純的 runCallable()
:
protected function runCallable() { $callable = $this->action['uses'];
return $callable(...array_values($this->resolveMethodDependencies( $this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses']) ))); }
|
resolveMethodDependencies()
就不深入分析了,跟分析 Container 類似,只是這次的目標是 ReflectionFunction
。
runController()
的原始碼如下:
protected function runController() { return $this->controllerDispatcher()->dispatch( $this, $this->getController(), $this->getControllerMethod() ); }
|
dispatch()
的實際程式碼如下:
public function dispatch(Route $route, $controller, $method) { $parameters = $this->resolveClassMethodDependencies( $route->parametersWithoutNulls(), $controller, $method );
if (method_exists($controller, 'callAction')) { return $controller->callAction($method, $parameters); }
return $controller->{$method}(...array_values($parameters)); }
|
resolveClassMethodDependencies()
有趣的地方是,會發現最後跟 callable 一樣,呼叫了 resolveMethodDependencies()
:
protected function resolveClassMethodDependencies(array $parameters, $instance, $method) { if (! method_exists($instance, $method)) { return $parameters; }
return $this->resolveMethodDependencies( $parameters, new ReflectionMethod($instance, $method) ); }
|
回到 $route->run()
,如果有寫過 Laravel 的話,會知道下面這些 return 都是可行的:
return view('welcome'); return redirect()->to('some-where'); return response()->json(['some' => 'data']); return 'Hello Wrold';
|
但事實上,在分析 bootstrap 流程時,有提到最後會拿到 Response 輸出,因此中間應該有做了一些轉換,才能拿到正常的 Response,這是 prepareResponse()
的任務:
public function prepareResponse($request, $response) { return static::toResponse($request, $response); }
public static function toResponse($request, $response) { if ($response instanceof Responsable) { $response = $response->toResponse($request); }
if ($response instanceof PsrResponseInterface) { $response = (new HttpFoundationFactory)->createResponse($response);
} elseif ($response instanceof Model && $response->wasRecentlyCreated) { $response = new JsonResponse($response, 201);
} elseif (! $response instanceof SymfonyResponse && ($response instanceof Arrayable || $response instanceof Jsonable || $response instanceof ArrayObject || $response instanceof JsonSerializable || is_array($response))) { $response = new JsonResponse($response);
} elseif (! $response instanceof SymfonyResponse) { $response = new Response($response); }
if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) { $response->setNotModified(); }
return $response->prepare($request); }
|
到此,Request 傳入到產出 Response 的流程都全部講完了,相信讀者對整個流程會做什麼,或是不會做什麼,有一定程度的了解了。
有踩過下面這幾個蠢事,但現在也得到了解答:
- Middleware 的
handle()
以為它也有做依賴注入,實際上它只有傳入 Kernel 那邊所設定的 parameter,這是 Pipeline 的運作原理
- Controller Action 可以定義 PSR-7 的 Request 與回傳 Response,但 Middleware 卻不能做一樣的事,這是今天才知道為何會這樣的
- 為何要有 StartSession,Session 才會正常運作
- 為何 Cookie 直接使用
cookie()
沒有用,要把實例加到 queue()
才行
知道內部運作的細節後,就也更正確的使用 Laravel,這也是想要寫這個主題的主要目的。