[TOC]
#### 路由地址
前面我们已经掌握了路由规则和路由变量的使用,路由规则的作用主要是用于匹配检查,在匹配到正确的路由规则后,并且通过了所有的条件检查,路由地址就粉墨登场了,不要以为路由地址很简单,其实不然哦,包含了下面几种类型的定义方式。
路由地址的作用就是告诉系统该路由规则应该解析到哪去,是到某个控制器方法还是其它的类方法(或者闭包函数)。而模块/控制器/操作这种路由地址其本质上也是对应到一个类的动态方法,另外也可以路由到一个类的静态方法,函数的方式是通过闭包来完成(目前路由不支持路由到一个函数名)。
要知道路由地址最终是怎么运作的,其实看think\App类的run方法里面的这段代码就明白了:
~~~
switch ($dispatch['type']) {
case 'redirect':
// 执行重定向跳转
$data = Response::create($dispatch['url'], 'redirect')->code($dispatch['status']);
break;
case 'module':
// 模块/控制器/操作
$data = self::module($dispatch['module'], $config, isset($dispatch['convert']) ? $dispatch['convert'] : null);
break;
case 'controller':
// 执行控制器操作
$data = Loader::action($dispatch['controller']);
break;
case 'method':
// 执行回调方法
$data = self::invokeMethod($dispatch['method']);
break;
case 'function':
// 执行闭包
$data = self::invokeFunction($dispatch['function']);
break;
case 'response':
$data = $dispatch['response'];
break;
default:
throw new \InvalidArgumentException('dispatch type not support');
}
~~~
这段代码清晰的指出了不同类型的调度是如何执行的,路由类think\Route最终解析后返回的就是一个类似于
~~~
['type' => 'module', 'module' => ['index', 'Index', 'hello']]
~~~
的格式数组,然后App类根据调度类型type来判断实际需要执行什么,从代码可以看出,路由类里面实际上有可能返回6种不同的调度类型(除去非法的调度异常之外),而Response对象其实不支持在路由地址中定义,所以实际上就是5种路由地址的定义类型,下面的主要内容就是来讲解如何定义这5种不同类型的路由地址。
不同的路由类型其实代表了不同的应用架构模式,大多数情况下的应用普遍采用的是基于模块/控制器/操作的架构开发模式,也是ThinkPHP5.0使用的默认模式,之所以说是默认模式,是因为关闭路由后的`URL解析`也是按照`模块/控制器/操作`的方式进行的。
#### 路由到`模块/控制器/操作`
路由地址的第一种定义方式,也是最常用的方式就是路由到 模块/控制器/操作 这种,也就是一种模块化的设计架构,每个URL请求都会最终执行应用的模块(其实就是一个目录)下面的控制器(一个控制器类)的操作(控制器类的方法),例如:
~~~
Route::get('hello/:name','index/Index/hello');
~~~
表示路由到index模块下的Index控制器的hello操作,实际上在匹配到路由规则后,会解析为
~~~
['type' => 'module', 'module' => ['index', 'Index', 'hello']]
~~~
返回给think\App类,然后调用App类的module方法执行相关操作。
同时会进行模块的初始化操作(包括配置读取、公共文件载入、行为定义载入、语言包载入等等)。
这种路由类型的路由地址的格式为:
~~~
[模块/控制器/]操作?参数1=值1&参数2=值2...
~~~
假设一个Blog控制器类定义如下:
~~~
<?php
namespace app\index\controller;
class Blog {
public function read($id){
return 'read:'.$id;
}
}
~~~
定义路由如下:
~~~
// 路由到默认模块或者绑定模块
Route::get('blog/:id','blog/read');
// 路由到index模块
Route::get('blog/:id','index/blog/read');
~~~
路由地址的解析规则从后往前解析,先解析操作,然后解析控制器,最后解析模块。
虽然路由地址里面的模块不是必须的,但建议是写完整的路由地址,避免受其它配置的影响,另外一个也方便路由地址的生成。
路由地址中支持多级控制器(关于多级控制器的概念请参考官方手册),使用下面的方式进行设置:
~~~
Route::get('blog/:id','index/group.Blog/read');
~~~
表示路由到下面的控制器类
~~~
app\index\controller\group\Blog
~~~
Blog控制器类定义如下:
~~~
<?php
namespace app\index\controller\group;
class Blog
{
public function read($id){
return 'read:'.$id;
}
}
~~~
需要注意的是,如果控制器使用了驼峰命名的话,在路由地址里面也需要使用驼峰方式定义,例如:
~~~
<?php
namespace app\index\controller;
class GroupUser
{
public function read($id){
return 'read:'.$id;
}
}
~~~
用例
~~~
// 错误的定义
Route::get('user/:id','index/groupuser/read');
// 正确的定义
Route::get('user/:id','index/GroupUser/read');
~~~
建议在路由地址中定义的控制器名称和控制器类名保持大小写一致,方便查看也方便URL地址的统一生成。
还可以支持路由到动态的模块、控制器或者操作,例如:
~~~
// action变量的值作为操作方法传入
Route::get(':action/blog/:id','index/Blog/:action');
// 路由到动态的控制器和操作
Route::rule(':controller/:action','index/:controller/:action');
~~~
#### 额外参数
路由地址里面支持额外传入参数(额外参数指的是不在URL里面的参数,隐式传入需要的操作中,有时候能够起到一定的安全防护作用,后面我们会提到)。例如:
~~~
Route::get('blog/:id','index/Blog/read?status=1&app_id=5');
~~~
上面的路由规则定义中额外参数status和app_id参数都是URL里面不存在的,属于隐式传值,当然并不一定需要用到,只是在需要的时候可以使用。
> 【5.1须知】
> 可以使用下面的方式
~~~
Route::get('blog/:id','index/Blog/read)
->append(['status'=>1,'app_id'=>5]);
~~~
#### 路由到操作方法
第二种路由类型是直接路由到控制器类的方法执行,这种方式看起来似乎和第一种是一样的,区别在于直接执行某个控制器类的方法,而不需要去解析 模块/控制器/操作这些,同时也不会去初始化模块(也就意味着不会去加载模块配置和公共文件)。
路由地址的格式为:
@[模块/控制器/]操作
例如,定义如下路由后:
~~~
Route::get('blog/:id','@index/Blog/read');
~~~
访问
~~~
http://tp5.com/blog/5
~~~
系统会直接执行
~~~
Loader::action('index/blog/read');
~~~
相当于直接调用`\app\index\controller\blog`类的`read方法`。
通常这种方式下面,由于没有定义当前模块名、当前控制器名和当前方法名 ,从而导致视图的默认模板规则失效,所以这种情况下面,如果使用了视图模板渲染,则必须传入明确的参数。
#### 路由到类的方法
第二种路由方式直接执行了控制器类(确切的说是访问控制器)的方法,如果希望直接执行其它类的方法,就需要使用下面的方式:
路由地址的格式为(动态方法):
> \带命名空间的完整类名@方法名
或者(静态方法)
> \带命名空间完整类名::方法名
这种方式更进一步,可以支持执行任何类的方法,而不仅仅是执行控制器的操作方法,例如:
~~~
Route::get('blog/:id','\app\index\service\Blog@read');
~~~
执行的是 `\app\index\service\Blog类`的`read方法`。
也支持执行某个静态方法,例如:
~~~
Route::get('blog/:id','\app\index\service\Blog::read');
~~~
#### 路由到重定向地址
重定向的外部地址必须以“`/`”或者`http`开头的地址。
如果路由地址以“`/`”开头或者带有“`://`”的地址会认为是一个重定向地址或者外部地址,例如:
~~~
Route::get('blog/:id','http://blog/read/id/:id');
Route::get('blog/:id','https://blog/read/id/:id');
Route::get('blog/:id','/blog/read/id/:id');
~~~
和
~~~
Route::get('blog/:id','blog/read');
~~~
虽然都是路由到同一个地址,但是前者采用的是301重定向的方式路由跳转,这种方式的好处是URL可以比较随意(包括可以在URL里面传入更多的非标准格式的参数),而后者只是支持模块和操作地址。举个例子,如果我们希望`avatar/123`重定向到
`/member/avatar/id/123_small`的话,只能使用:
~~~
Route::get('avatar/:id','/member/avatar/id/:id_small');
~~~
路由地址采用重定向地址的话,如果要引用动态变量,直接使用动态变量即可。
采用重定向到外部地址通常对网站改版后的URL迁移过程非常有用,例如:
~~~
Route::get('blog/:id','http://blog.thinkphp.cn/read/:id');
~~~
表示当前网站(可能是`http://thinkphp.cn `)的 blog/123地址会直接重定向到`http://blog.thinkphp.cn/read/123`
> 【5.1须知】
> 可以使用下面的方式
~~~
Route::redirect('blog/:id','http://blog.thinkphp.cn/read/:id');
~~~
#### 使用闭包定义
我们可以使用闭包的方式定义一些特殊需求的路由,而不需要执行控制器的操作方法了,例如:
~~~
Route::get('hello',function(){
return 'hello,world!';
});
~~~
闭包定义的时候支持参数传递,例如:
~~~
Route::get('hello/:name',function($name){
return 'Hello,'.$name;
});
~~~
路由规则中定义的变量名称就是闭包函数中的参数名称,不分次序。
因此,如果我们访问的URL地址是:
~~~
http://tp5.com/hello/thinkphp
~~~
则浏览器输出的结果是:
~~~
Hello,thinkphp
~~~
小结
相信你已经掌握了路由地址的定义规范了,对路由地址的规范总结如下:
|路由类型 |定义格式|
|--|--|
|路由到模块/控制器/操作| '[模块/控制器/操作]?额外参数1=值1&额外参数2=值2...'|
|路由到重定向地址| '外部地址'(默认301重定向) 或者 ['外部地址','重定向代码']|
|路由到控制器的方法 |'@[模块/控制器/]操作'|
|路由到类的方法 |'\完整的命名空间类::静态方法' 或者 '\完整的命名空间类@动态方法'|
|路由到闭包函数| 闭包函数定义(支持参数传入)|
下一篇我会来讲解路由分组的用法。