分析 Routing(2)
一樣,先從類別圖開始。這次因為相關的類別太多,所以會先以 Router 設定 Controller 以及 Request 如何對應到正確的 Controller 為主,而不會把所有類別都硬塞到這次的圖裡。
@startuml
interface Illuminate\Contracts\Routing\BindingRegistrar
interface Illuminate\Contracts\Routing\Registrar
class Router {
# current : Route, current route
# currentRequest : Illuminate\Http\Request
}
Router -> Illuminate\Support\Traits\Macroable
Illuminate\Contracts\Routing\BindingRegistrar <.. Router
Illuminate\Contracts\Routing\Registrar <.. Router
Router o-- RouteCollection: new instance
Router --> Route: new instance
Router o-- Illuminate\Contracts\Events\Dispatcher
Router o-- Illuminate\Container\Container
Router --> Illuminate\Routing\Pipeline
Router --> RouteRegistrar
@enduml
昨天的程式碼再看一下:
$this->app->make('router') |
查 Router::prefix()
的原始碼,會發現它是宣告成 protected 的,很不可思議,因為上面的程式碼是 public 呼叫。其實這是 Magic Method __call()
的關係,來看它是怎麼做的:
public function __call($method, $parameters) |
如果 public 呼叫 prefix()
的話,就會走到最後一行。而類別裡面呼叫的話,則會是宣告 protected 的方法。
事實上,方法名稱重複並不好,因為原本以為不能呼叫,但實際可以,通常不會想到是
__call()
的關係。
RouteRegistrar 是一個輔助類別,可以讓主要類別處理某些事比較容易一點。這個做法與 Container 的 ContextualBindingBuilder
是一樣的設計。
如果是使用 prefix()
函式觸發 __call()
的話,會得到下面這樣的等價物件:
return (new RouteRegistrar($this))->attribute('prefix', $prefixValue); |
RouteRegistrar 的建構子很單純,只是把 Router 找個位置放而已,再來看 attribute()
:
public function attribute($key, $value) |
這裡可以知道,當使用 prefix()
時,會回傳 RouteRegistrar 實例,但後面 middleware()
與 namespace()
等,又是如何實現的呢?答案一樣是 Magic Method,在 RouteRegistrar::__call()
裡:
public function __call($method, $parameters) |
如果一直設定 attribute 的話,這些屬性都只會存在 RouteRegistrar 實例裡,跟 Router 實例就無法連結上。實際會把 attribute 回寫到 Router 上的方法是 registerRoute()
:
protected function registerRoute($method, $uri, $action = null) |
compileAction()
大概看一下:
protected function compileAction($action) |
這裡的程式碼,跟
registerRoute()
一開始做的事沒什麼兩樣,也許有其他理由但目前不清楚。
綜合以上的分析,我們可以知道下面兩段程式碼是等價的:
$this->app->make('router') |
會這樣設計,有一個原因是:Laravel 對 action 資訊的定義正是長這樣,但如果直接使用 array 傳遞參數的話,最明顯的問題就是違反最小知識原則(Least Knowledge Principle),因為 array 的格式,等於是曝露資料細節,當 array 規格調整時,將會引發一場災難;相對的這樣設計,雖然程式碼顯得複雜許多,不過帶來的好處就是,使用起來非常直觀,且依賴只有該實例曝露出來的方法,這也是比較容易調整的(如:使用 alias)。
group()
比較複雜了一點,明天再接著繼續看。