Route 帶有單一個路由的資訊。從它在 Router 如何被初始化的程式碼,可以知道它有哪些基本的資訊。
protected function newRoute($methods, $uri, $action) { return (new Route($methods, $uri, $action)) ->setRouter($this) ->setContainer($this->container); }
|
建構子有三個重要的資訊:$methods
、$uri
、$action
,其中 method 與 uri 很明顯是用來讓開發者定義最基本的資訊,而 action 則是其他資訊。
這些資訊都是用來比對(match)目前的 url 是否符合這個 route 的設定。
Route 建構子裡面其實還有做一些事:
public function __construct($methods, $uri, $action) { $this->uri = $uri; $this->methods = (array) $methods;
$this->action = $this->parseAction($action);
if (in_array('GET', $this->methods) && ! in_array('HEAD', $this->methods)) { $this->methods[] = 'HEAD'; }
if (isset($this->action['prefix'])) { $this->prefix($this->action['prefix']); } }
|
解析 action 做了很多事,主要寫在 RouteAction 類別裡,使用靜態呼叫:
public static function parse($uri, $action) { if (is_null($action)) { return static::missingAction($uri); }
if (is_callable($action)) { return ! is_array($action) ? ['uses' => $action] : [ 'uses' => $action[0].'@'.$action[1], 'controller' => $action[0].'@'.$action[1], ]; }
elseif (! isset($action['uses'])) { $action['uses'] = static::findCallable($action); }
if (is_string($action['uses']) && ! Str::contains($action['uses'], '@')) { $action['uses'] = static::makeInvokable($action['uses']); }
return $action; }
|
makeInvokable()
裡面是幫原本的類別名,後面加上 @__invoke
而已,同時它也是實作 Single Action Controllers 的關鍵程式碼。
簡單來說,整個過程最後的目的是要回傳出一個可以使用的 action array。
接著,回到昨天提到的 addRoute()
:
public function addRoute($methods, $uri, $action) { return $this->routes->add($this->createRoute($methods, $uri, $action)); }
|
這裡使用了 RouteCollection 來新增 Route 實例,add()
裡面有偷做了一些事:
public function add(Route $route) { $this->addToCollections($route);
$this->addLookups($route);
return $route; }
protected function addToCollections($route) { $domainAndUri = $route->getDomain().$route->uri();
foreach ($route->methods() as $method) { $this->routes[$method][$domainAndUri] = $route; }
$this->allRoutes[$method.$domainAndUri] = $route; }
protected function addLookups($route) { if ($name = $route->getName()) { $this->nameList[$name] = $route; }
$action = $route->getAction();
if (isset($action['controller'])) { $this->addToActionList($action, $route); } }
protected function addToActionList($action, $route) { $this->actionList[trim($action['controller'], '\\')] = $route; }
|
看完上面的程式碼,可以知道新增的過程中,其實 RouteCollection 是在忙著建對照表。
如果還有印象的話,在開始講 Routing 時,曾有講到這段程式碼:
$this->app['router']->getRoutes()->refreshNameLookups(); $this->app['router']->getRoutes()->refreshActionLookups();
|
現在或許會知道它在做什麼了:
public function refreshNameLookups() { $this->nameList = [];
foreach ($this->allRoutes as $route) { if ($route->getName()) { $this->nameList[$route->getName()] = $route; } } }
public function refreshActionLookups() { $this->actionList = [];
foreach ($this->allRoutes as $route) { if (isset($route->getAction()['controller'])) { $this->addToActionList($route->getAction(), $route); } } }
|
對照表從屬性來看,總共會有四種
protected $routes = []; protected $allRoutes = []; protected $nameList = []; protected $actionList = [];
|
這些對照表會用在不同的 get 方法上:
public function get($method = null) { return is_null($method) ? $this->getRoutes() : Arr::get($this->routes, $method, []); }
public function getByName($name) { return $this->nameList[$name] ?? null; }
public function getByAction($action) { return $this->actionList[$action] ?? null; }
public function getRoutes() { return array_values($this->allRoutes); }
public function getRoutesByMethod() { return $this->routes; }
public function getRoutesByName() { return $this->nameList; }
|
還有許多 has 方法,這些都會在 Router 要查實例的時候派上用場。
到今天為止,已經知道保存 Route 實例的空間是如何管理的。但在分析 Pipeline 時,有看到下面這段程式碼:
return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter());
|
dispatchToRouter()
究竟是如何找到對應的 Route 實例,並執行的呢?這應該是更神奇的事,就請待下回分曉囉。