上篇分析了 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(){}); 下面是它的源码:
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行的出栈操作则是避免嵌套使用的分组路由出现属性存储的错误。
这里有两个重点:
- Route::group() 中的匿名函数将会立即被执行,而不像 Route::get() 中的匿名函数被存储为回调函数后期调用,所以如果你在分组路由的匿名函数中放上一个 dd() 那么恭喜,你的整个程序都会 dd 在那里了。
- 关于 $attributes 数组,建议在此定义的属性如下:
array(
'http','https', // 协议类型,默认 http(任选其一即可)
'domain' => '', // 域名模式字符串
'prefix' => '', // 前缀
'before' => '', // 前置过滤器
'after' => '', // 后置过滤器
);
createRoute()
接下来,让我们来看看 createRoute 方法,像 Route::get('/', function(){}); 这样的调用最终都会指向它,下面是它的源码:
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;
}
这部分的注释已经写的相当详细了,需要特别说明的是:
- 从14到25行,正是这部分操作将分组路由的属性合并入其下各个线路的行为参数中去。所以理论上来说这里支持的行为参数,在分组路由的属性中都是可以被定义的,但是出于各种考虑,分组路由的属性还是仅建议定义上面提到的那几种。
- 第64行的操作将会照成同名路由被覆盖,而路由的名称则是根据“域名”“模式字符串”等数据进行构造的,并且如果存在 as 行为参数的话则直接使用 as 的字符串。所以像 as 这种行为参数如果你要把它放到分组路由的属性中去定义的话,那我只能哈哈了。
- 支持定义的行为参数如下:
array( 'http','https', // 协议类型,默认 http(任选其一即可) 'domain' => '', // 域名模式字符串(不借助分组路由依然可以针对域名操作) 'as' => '', // 别名(优先作为线路名称,注意线路覆盖) 'prefix' => '', // 前缀(建议在路由分组中设置) 'before' => '', // 前置过滤器 'uses' => '', // 控制器方法字符串(支持简写,与 Closure 任选其一) Closure, // 匿名回调函数(支持简写,与 uses 任选其一) 'after' => '', // 后置过滤器 );
- 支持的简写:
直接使用 匿名函数 相当于 array(Closure) 直接使用 字符串 相当于 array('uses' => 'usesController@usesMethod')
相关文件的源码请参考:
- http://git.oschina.net/chengwu/cnComment-Laravel-4/blob/master/vendor/laravel/framework/src/Illuminate/Routing/Router.php
- http://git.oschina.net/chengwu/cnComment-Laravel-4/blob/master/vendor/laravel/framework/src/Illuminate/Routing/Route.php