昨天在最後面,可以知道一件很重要的資訊:Router 裡面所指的 action 原形,其實是 array。
以昨天的例子來說:
$this->app->make('router') ->prefix('api') ->middleware('api') ->namespace($this->namespace) ->get('/', function() { return 'whatever'; });
|
它會把這些資訊轉換 array,才產生 Route 物件:
[ 'prefix' => 'api', 'middleware' => ['api'], 'namespace' => $this->namespace, 'uses' => function() { return 'whatever'; }, ];
|
Laravel 稱這個資訊為 action,了解這個,對後面要繼續追 code 是有幫助的。
某種程度而言,這也算是一種 domain knowledge。
再一次回味程式碼:
$this->app->make('router') ->prefix('api') ->middleware('api') ->namespace($this->namespace) ->group(base_path('routes/api.php'));
$this->app->make('router') ->middleware('web') ->namespace($this->namespace) ->group(base_path('routes/web.php'));
|
昨天提到 namespace()
完之後,會得到一個 RouteRegistrar 實例,並且把需要的 attributes 都存放在實例裡。
今天要繼續來看 RouteRegistrar::group()
做了什麼事:
public function group($callback) { $this->router->group($this->attributes, $callback); }
|
其實很簡單,是呼叫 Router::group()
:
public function group(array $attributes, $routes) { $this->updateGroupStack($attributes);
$this->loadRoutes($routes);
array_pop($this->groupStack); }
|
會使用 stack 的理由約略可以猜想,因為下面這一種巢狀使用方法,是要能被接受的:
Route::group([], function() { Route::group([], function() { Route::group([], function() { Route::get('/', function() { return 'whatever'; }); }); }); });
|
為了保存各階段的設定,因此採用了 stack 的資料結構。
來看看 updateGroupStack()
做了什麼:
protected function updateGroupStack(array $attributes) { if (! empty($this->groupStack)) { $attributes = RouteGroup::merge($attributes, end($this->groupStack)); }
$this->groupStack[] = $attributes; }
|
RouteGroup 與 BoundMethod 一樣,是一個 helper 類別,裡面都是靜態方法。先來看 RouteGroup::merge()
做了什麼:
public static function merge($new, $old) { if (isset($new['domain'])) { unset($old['domain']); }
$new = array_merge(static::formatAs($new, $old), [ 'namespace' => static::formatNamespace($new, $old), 'prefix' => static::formatPrefix($new, $old), 'where' => static::formatWhere($new, $old), ]);
return array_merge_recursive(Arr::except( $old, ['namespace', 'prefix', 'where', 'as'] ), $new); }
|
簡單來說這個方法的任務是:四個屬性 namespace
、prefix
、where
、as
與舊設定會有特別的合併方法,然後再用新設定覆蓋舊設定。至於合併的方法都不難,可以直接參考原始碼吧。
取得合併後的資料就推入 stack 中,使用 group()
方法才會推 stack,因此數量與呼叫次數會是一樣的,loadRoutes()
之後就會移除。而 loadRoutes()
長這樣:
protected function loadRoutes($routes) { if ($routes instanceof Closure) { $routes($this); } else { $router = $this;
require $routes; } }
|
偶爾會發生,需要傳 Closure,可是不確定參數裡面應該要有什麼,這時通常會翻原始碼來看。從這裡可以知道 group()
可以帶的 callback 長相是這樣:
Route::group([], function() { Route::get('/', function() { return 'whatever'; }); };
Route::group([], function(Router $router) { $router->get('/', function() { return 'whatever'; }); };
|
另外也會發現,group()
也可以帶 filename,而被 require 檔案的寫法,下面兩個是等價:
Route::get('/', function() { return 'whatever'; });
$router->get('/', function() { return 'whatever'; });
|
這也是研究原始碼會發現的小趣事。
回到 stack 的用途,參考下面這段程式碼:
Route::group([], function() { Route::group([], function() { Route::get('/a', function() { return 'whatever'; }); });
Route::group([], function() { Route::get('/b', function() { return 'whatever'; }); }); });
|
Router 不管使用 Application 建置或是 Facade,都會是單例。上面的 Route Facade 其實是對同一個實例做操作。執行的順序如下:
- 設定 1 被載入後,會放在 stack 第一個位置
- 設定 2 跟設定 1 合併,稱之設定 1 + 2,會推到 stack 第二個位置
- 使用設定 1 + 2 設定 route
- 清除 stack 第二筆資料
- 設定 3 跟設定 1 合併,稱之設定 1 + 3,會推到 stack 第二個位置
- 使用設定 1 + 3 設定 route
- 清除 stack 第二筆資料
- 清除 stack 第一筆資料
當使用 Router 的建立 Route 的方法時,就會拿 stack 最頂層的設定來用。
group()
方法的原理大致是這樣