Laravel 4 入門三講(中)laravel 的路由是如何完成註冊的?

  上篇分析了 Laravel 4 的啓動流程 “Laravel 4 在啓動的時候到底做了什麼?”,讀過的朋友應該對 Laravel 4 文件的加載順序有了一個比較清晰的認識,這樣就可以順利的完成開發前的配置及準備工作。

  而開發階段我們將會面臨另一個難點——路由。

  通常,在看過官方文檔後就可以比較順利的使用路由,我們知道在 routes.php 文件中可以通過定義 Route::get() Route::post() Route::group() 等方法來註冊我們的路由,然而你真的瞭解這些路由是怎麼註冊進我們的程序的嗎?

  除了“RESTful控制器”和“資源控制器”之外,所有的路由註冊最終都指向 Illuminate\Routing\Router.php 的 createRoute 方法。而另一個重要的輔助方法則是 group 分組路由。只有理解了這兩個方法,纔算是完全理解了路由的註冊機制。

group()

  讓我們從 group 方法開始,這個方法可以直接使用,像這樣 Route::group(array(), function(){}); 下面是它的源碼:

?
1
2
3
4
5
6
7
8
9
10
11
public function group(array $attributes, Closure $callback)
{
    // 存儲分組共享屬性,對於嵌套使用的路由組,將對傳入的共享屬性做遞歸合併處理
    $this->updateGroupStack($attributes);
 
    // 回調函數中的路由註冊最終都指向了 createRoute 方法
    call_user_func($callback);
 
    // 出棧,移除本次記錄的共享屬性
    array_pop($this->groupStack);
}

  第4行的作用是將傳入的 $attributes 數組存儲到 $this->groupStack 這個分組屬性的堆棧中,如果出現了嵌套使用分組路由的情況,則對傳入的屬性數據做遞歸處理。這些臨時存儲的屬性將在第7行,也就是最終執行到 createRoute 方法時被存儲到各個線路中去,與各自的行爲參數合併。第10行的出棧操作則是避免嵌套使用的分組路由出現屬性存儲的錯誤。

  這裏有兩個重點:

  1. Route::group() 中的匿名函數將會立即被執行,而不像 Route::get() 中的匿名函數被存儲爲回調函數後期調用,所以如果你在分組路由的匿名函數中放上一個 dd() 那麼恭喜,你的整個程序都會 dd 在那裏了。
  2. 關於 $attributes 數組,建議在此定義的屬性如下:
?
1
2
3
4
5
6
7
array(
    'http','https', // 協議類型,默認 http(任選其一即可)
    'domain' => '', // 域名模式字符串
    'prefix' => '', // 前綴
    'before' => '', // 前置過濾器
    'after'  => '', // 後置過濾器
);

createRoute()

  接下來,讓我們來看看 createRoute 方法,像 Route::get('/', function(){}); 這樣的調用最終都會指向它,下面是它的源碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
protected function createRoute($method, $pattern, $action)
{
    // We will force the action parameters to be an array just for convenience.
    // This will let us examine it for other attributes like middlewares or
    // a specific HTTP schemes the route only responds to, such as HTTPS.
    // 將“非數組行爲參數”格式化爲標準的“行爲參數數組”
    // 匿名函數將被解析爲 array(Closure)
    // 字符串將被解析爲   array('uses' => 'usesController@usesMethod')
    if ( ! is_array($action))
    {
        $action = $this->parseAction($action);
    }
     
    $groupCount = count($this->groupStack);
 
    // If there are attributes being grouped across routes we will merge those
    // attributes into the action array so that they will get shared across
    // the routes. The route can override the attribute by specifying it.
    // 當存在“分組共享參數”時,將其合併到當前的“行爲參數數組”
    if ($groupCount > 0)
    {
        $index = $groupCount - 1;
 
        $action = $this->mergeGroup($action, $index);
    }
 
    // Next we will parse the pattern and add any specified prefix to the it so
    // a common URI prefix may be specified for a group of routes easily and
    // without having to specify them all for every route that is defined.
    // 得到正確格式的“模式字符串”及“可選參數數組”
    list($pattern, $optional) = $this->getOptional($pattern);
 
    if (isset($action['prefix']))
    {
        $prefix = $action['prefix'];
 
        // 爲模式字符串增加前綴
        $pattern = $this->addPrefix($pattern, $prefix);
    }
 
    // We will create the routes, setting the Closure callbacks on the instance
    // so we can easily access it later. If there are other parameters on a
    // routes we'll also set those requirements as well such as defaults.
    $route = with(new Route($pattern))->setOptions(array(
 
        // 從“行爲參數數組”中獲取回調函數:匿名函數 或 uses 參數
        '_call' => $this->getCallback($action),
 
    ))->setRouter($this)->addRequirements($this->patterns);
 
    // 設置請求類型
    $route->setRequirement('_method', $method);
 
    // Once we have created the route, we will add them to our route collection
    // which contains all the other routes and is used to match on incoming
    // URL and their appropriate route destination and on URL generation.
    // 爲線路(非路由,注意區分)設置屬性及可選參數
    $this->setAttributes($route, $action, $optional);
 
    // 構造線路名稱
    $name = $this->getName($method, $pattern, $action);
 
    // 將線路加入路由集合實例,注意:同名路由將被覆蓋
    $this->routes->add($name, $route);
 
    // 返回當前線路實例
    return $route;
}

  這部分的註釋已經寫的相當詳細了,需要特別說明的是:

  1. 從14到25行,正是這部分操作將分組路由的屬性合併入其下各個線路的行爲參數中去。所以理論上來說這裏支持的行爲參數,在分組路由的屬性中都是可以被定義的,但是出於各種考慮,分組路由的屬性還是僅建議定義上面提到的那幾種。
  2. 第64行的操作將會照成同名路由被覆蓋,而路由的名稱則是根據“域名”“模式字符串”等數據進行構造的,並且如果存在 as 行爲參數的話則直接使用 as 的字符串。所以像 as 這種行爲參數如果你要把它放到分組路由的屬性中去定義的話,那我只能哈哈了。
  3. 支持定義的行爲參數如下:
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    array(
        'http','https', // 協議類型,默認 http(任選其一即可)
        'domain' => '', // 域名模式字符串(不借助分組路由依然可以針對域名操作)
        'as'     => '', // 別名(優先作爲線路名稱,注意線路覆蓋)
        'prefix' => '', // 前綴(建議在路由分組中設置)
        'before' => '', // 前置過濾器
        'uses'   => '', // 控制器方法字符串(支持簡寫,與 Closure 任選其一)
         Closure,       // 匿名回調函數(支持簡寫,與 uses 任選其一)
        'after'  => '', // 後置過濾器
    );
  4. 支持的簡寫:
    ?
    1
    2
    直接使用 匿名函數 相當於 array(Closure)
    直接使用 字符串   相當於 array('uses' => 'usesController@usesMethod')

相關文件的源碼請參考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章