回過頭來,我們來看 Http Kernel 的這段程式碼。
return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter());
|
這是產生最終 Response 的過程。其中 Middleware 的原理已經在 Pipeline 分析過了;Router 基本運作原理也分析了。今天要來看的是,dispatchToRouter()
到底是如何選到符合的 Route。
一樣把原始碼打開:
protected function dispatchToRouter() { return function ($request) { $this->app->instance('request', $request);
return $this->router->dispatch($request); }; }
|
這裡回傳了一個 Closure,它會被放到 Pipeline 裡執行。而傳入的 $request
,即為 Pipeline 傳入的 send($request)
。
拿到 request 之後,立刻設定到 Container 裡。這代表在這個時機點之後,才能開始使用 request()
取得 Request 實例。
這裡呼叫了 Router 的 dispatch()
方法:
public function dispatch(Request $request) { $this->currentRequest = $request;
return $this->dispatchToRoute($request); }
public function dispatchToRoute(Request $request) { return $this->runRoute($request, $this->findRoute($request)); }
|
繼續看 findRoute()
:
protected function findRoute($request) { $this->current = $route = $this->routes->match($request);
$this->container->instance(Route::class, $route);
return $route; }
|
這裡會看到,實際 match 的工作是交由 RouteCollection 處理的。
public function match(Request $request) { $routes = $this->get($request->getMethod());
$route = $this->matchAgainstRoutes($routes, $request);
if (! is_null($route)) { return $route->bind($request); }
$others = $this->checkForAlternateVerbs($request);
if (count($others) > 0) { return $this->getRouteForMethods($request, $others); }
throw new NotFoundHttpException; }
|
再來,因為每個方法都有分析的價值,所以下面會一個一個來看。首先看 matchAgainstRoutes()
是如何找到匹配的 Route:
protected function matchAgainstRoutes(array $routes, $request, $includingMethod = true) { list($fallbacks, $routes) = collect($routes)->partition(function ($route) { return $route->isFallback; });
return $routes->merge($fallbacks)->first(function ($value) use ($request, $includingMethod) { return $value->matches($request, $includingMethod); }); }
|
因為使用了 Collection::first()
,因此比對 Request 與 Route 就會有順序,這也是 Route 先設定會先匹配的原因。另外,建構對照表也是照設定順序,因此後設定的會把前面設定的覆蓋。
public function matches(Request $request, $includingMethod = true) { $this->compileRoute();
foreach ($this->getValidators() as $validator) { if (! $includingMethod && $validator instanceof MethodValidator) { continue; }
if (! $validator->matches($this, $request)) { return false; } }
return true; }
|
預設的 Validator 如下,這也是驗證 Route 與 Request 的基本判斷方法:
- UriValidator - 確認 Uri 是否匹配,同時這也是最複雜的比對,不過主要都是 Symfony 的套件完成了
- MethodValidator - 確認 Method 是否匹配
- SchemeValidator - 如果有設定 http / https 的話,就會比對
- HostValidator - 如果有設定 Domain 的話,就會比對
回到 match() 的流程,bind()
蠻單純的,先跳過,來看 checkForAlternateVerbs()
:
protected function checkForAlternateVerbs($request) { $methods = array_diff(Router::$verbs, [$request->getMethod()]);
$others = [];
foreach ($methods as $method) { if (! is_null($this->matchAgainstRoutes($this->get($method), $request, false))) { $others[] = $method; } }
return $others; }
|
如果有找到任何可能的 method,再來就會呼叫 getRouteForMethods()
:
protected function getRouteForMethods($request, array $methods) { if ($request->method() == 'OPTIONS') { return (new Route('OPTIONS', $request->path(), function () use ($methods) { return new Response('', 200, ['Allow' => implode(',', $methods)]); }))->bind($request); }
$this->methodNotAllowed($methods); }
|
當正常找找不到,找替代的也找不到,那麼就是 404 找不到了。
到了今天,總算知道 Route 是怎麼被匹配出來的了,但還有另一個主題:runRoute()
,它是如何執行的,這就留到明天再繼續分析。