ThinkPHP5的路由模式

Published on 2016 - 12 - 10

路由模式

ThinkPHP5.0的路由比较灵活,并且不需要强制定义,可以总结归纳为如下三种方式:

普通模式

关闭路由,完全使用默认的PATH_INFO方式URL:

'url_route_on'  =>  false,

路由关闭后,不会解析任何路由规则,采用默认的PATH_INFO 模式访问URL:

http://serverName/index.php/module/controller/action/param/value/...

但仍然可以通过操作方法的参数绑定、空控制器和空操作等特性实现URL地址的简化。

可以设置url_param_type配置参数来改变pathinfo模式下面的参数获取方式,默认是按名称成对解析,支持按照顺序解析变量,只需要更改为:

// 按照顺序解析变量
'url_param_type'    =>  1,

混合模式

开启路由,并使用路由定义+默认PATH_INFO方式的混合:

'url_route_on'  =>  true,
'url_route_must'=>  false,

该方式下面,只需要对需要定义路由规则的访问地址定义路由规则,其它的仍然按照第一种普通模式的PATH_INFO模式访问URL。

强制模式

开启路由,并设置必须定义路由才能访问:

'url_route_on'          =>  true,
'url_route_must'        =>  true,

这种方式下面必须严格给每一个访问地址定义路由规则(包括首页),否则将抛出异常。

首页的路由规则采用/定义即可,例如下面把网站首页路由输出Hello,world!

Route::get('/',function(){
    return 'Hello,world!';
});

资源路由

资源路由

5.0支持设置RESTFul请求的资源路由,方式如下:

Route::resource('blog','index/blog');

或者在路由配置文件中使用rest添加资源路由定义:

return [
    // 定义资源路由
    '__rest__'=>[
        // 指向index模块的blog控制器
        'blog'=>'index/blog',
    ],
    // 定义普通路由
    'hello/:id'=>'index/hello',
]

设置后会自动注册7个路由规则,如下:

标识 请求类型 生成路由规则 对应操作方法(默认)
index GET blog
create GET blog/create
save POST blog
read GET blog/:id
edit GET blog/:id/edit
update PUT blog/:id
delete DELETE blog/:id

具体指向的控制器由路由地址决定,例如上面的设置,会对应index模块的blog控制器,你只需要为Blog控制器创建以上对应的操作方法就可以支持下面的URL访问:

http://serverName/blog/
http://serverName/blog/128
http://serverName/blog/28/edit

Blog控制器中的对应方法如下:

namespace app\index\controller;
class Blog {
    public function index(){
    }

    public function read($id){
    }    

    public function edit($id){
    }    
}

可以改变默认的id参数名,例如:

Route::resource('blog','index/blog',['var'=>['blog'=>'blog_id']]);

控制器的方法定义需要调整如下:

namespace app\index\controller;
class Blog {
    public function index(){
    }

    public function read($blog_id){
    }    

    public function edit($blog_id){
    }    
}

也可以在定义资源路由的时候限定执行的方法(标识),例如:

// 只允许index read edit update 四个操作
Route::resource('blog','index/blog',['only'=>['index','read','edit','update']]);
// 排除index和delete操作
Route::resource('blog','index/blog',['except'=>['index','delete']]);

资源路由的标识不可更改,但生成的路由规则和对应操作方法可以修改。

如果需要更改某个资源路由标识的对应操作,可以使用下面方法:

Route::rest('create',['GET', '/add','add']);

设置之后,URL访问变为:

http://serverName/blog/create

变成

http://serverName/blog/add

创建blog页面的对应的操作方法也变成了add。

支持批量更改,如下:

Route::rest([
    'save'   => ['POST', '', 'store'],
    'update' => ['PUT', '/:id', 'save'],
    'delete' => ['DELETE', '/:id', 'destory'],
]);

资源嵌套

支持资源路由的嵌套,例如:

Route::resource('blog.comment','index/comment');

就可以访问如下地址:

http://serverName/blog/128/comment/32
http://serverName/blog/128/comment/32/edit

生成的路由规则分别是:

blog/:blog_id/comment/:id
blog/:blog_id/comment/:id/edit

Comment控制器对应的操作方法如下:

namespace app\index\controller;
class Comment{
    public function edit($id,$blog_id){
    }
}

edit方法中的参数顺序可以随意,但参数名称必须满足定义要求。

如果需要改变其中的变量名,可以使用:

// 更改嵌套资源路由的blog资源的资源变量名为blogId
Route::resource('blog.comment','index/comment',['var'=>['blog'=>'blogId']]);
Comment控制器对应的操作方法改变为:

namespace app\index\controller;

class Comment{
    public function edit($id,$blogId)
    {
    }
}

快捷路由

快捷路由允许你快速给控制器注册路由,并且针对不同的请求类型可以设置方法前缀,例如:

// 给User控制器设置快捷路由
Route::controller('user','index/User');

User控制器定义如下:

namespace app\index\controller;

class User {
    public function getInfo()
    {
    }

    public function getPhone()
    {
    }

    public function postInfo()
    {
    }
    public function putInfo()
    {
    }

    public function deleteInfo()
    {
    }
}

我们可以通过下面的URL访问

get http://localhost/user/info
get http://localhost/user/phone
post http://localhost/user/info
put http://localhost/user/info
delete http://localhost/user/info

路由别名

路由别名功能可以使用一条规则,批量定义一系列的路由规则。

例如,我们希望使用user可以访问index模块的User控制器的所有操作,可以使用:

// user 别名路由到 index/User 控制器
Route::alias('user','index/User');

如果在路由配置文件route.php中定义的话,使用:

return [
    '__alias__' =>  [
        'user'  =>  'index/User',
    ],
];

和前面的方式是等效的。

然后可以直接通过URL地址访问User控制器的操作,例如:

http://serverName/index.php/user/add
http://serverName/index.php/user/edit/id/5
http://serverName/index.php/user/read/id/5

路由别名可以指向任意一个有效的路由地址,例如下面指向一个类

// user 路由别名指向 User控制器类
Route::alias('user','\app\index\controller\User');

路由别名不支持变量类型和路由条件判断,单纯只是为了缩短URL地址,并且在定义的时候需要注意避免和路由规则产生混淆。

支持给路由别名设置路由条件,例如:

// user 别名路由到 index/user 控制器
Route::alias('user','index/user',['ext'=>'html']);

或者在路由配置文件中使用:

return [
    '__alias__' =>  [
        'user'  =>  ['index/user',['ext'=>'html']],
    ],
];

操作方法黑白名单(v5.0.2+)

路由别名的操作方法支持白名单或者黑名单机制,例如:

// user 别名路由到 index/user 控制器
Route::alias('user','index/user',[
    'ext'=>'html'
    'allow'=>'index,read,edit,delete',
]);

或者使用黑名单机制

// user 别名路由到 index/user 控制器
Route::alias('user','index/user',[
    'ext'=>'html'
    'except'=>'save,delete',
]);

并且支持设置操作方法的请求类型,例如:

// user 别名路由到 index/user 控制器
Route::alias('user','index/user',[
    'ext'=>'html'
    'allow'=>'index,save,delete',
    'method'=>['index'=>'GET','save'=>'POST','delete'=>'DELETE'],
]);

路由分组

路由分组功能允许把相同前缀的路由定义合并分组,这样可以提高路由匹配的效率,不必每次都去遍历完整的路由规则。

例如,我们有定义如下两个路由规则的话

'blog/:id'   => ['Blog/read', ['method' => 'get'], ['id' => '\d+']],
'blog/:name' => ['Blog/read', ['method' => 'post']],

可以合并到一个blog分组

'[blog]'     => [
    ':id'   => ['Blog/read', ['method' => 'get'], ['id' => '\d+']],
    ':name' => ['Blog/read', ['method' => 'post']],
],

可以使用Route类的group方法进行注册,如下:

Route::group('blog',[
    ':id'   => ['Blog/read', ['method' => 'get'], ['id' => '\d+']],
    ':name' => ['Blog/read', ['method' => 'post']],
]);

可以给分组路由定义一些公用的路由设置参数,例如:

Route::group('blog',[
    ':id'   => ['Blog/read', [], ['id' => '\d+']],
    ':name' => ['Blog/read', [],
],['method'=>'get','ext'=>'html']);

支持使用闭包方式注册路由分组,例如:

Route::group('blog',function(){
    Route::rule(':id','blog/read',[],['id'=>'\d+']);
    Route::rule(':name','blog/read',[],['name'=>'\w+']);
},['method'=>'get','ext'=>'html']);

如果仅仅是用于对一些路由规则设置一些公共的路由参数,也可以使用:

Route::group(['method'=>'get','ext'=>'html'],function(){
    Route::rule('blog/:id','blog/read',[],['id'=>'\d+']);
    Route::rule('blog/:name','blog/read',[],['name'=>'\w+']);
});

路由分组支持嵌套,例如:

Route::group(['method'=>'get','ext'=>'html'],function(){
    Route::group('blog',function(){
        Route::rule('blog/:id','blog/read',[],['id'=>'\d+']);
        Route::rule('blog/:name','blog/read',[],['name'=>'\w+']);
    }
});

MISS路由

全局MISS路由

如果希望在没有匹配到所有的路由规则后执行一条设定的路由,可以使用MISS路由功能,只需要在路由配置文件中定义:

return [
    'new/:id'   => 'News/read',
    'blog/:id'  => ['Blog/update',['method' => 'post|put'], ['id' => '\d+']],
    '__miss__'  => 'public/miss',
];

或者使用miss方法注册路由

Route::miss('public/miss');

当没有匹配到所有的路由规则后,会路由到 public/miss路由地址。

分组MISS路由

分组支持独立的MISS路由,例如如下定义:

return [
    '[blog]' =>  [
        'edit/:id'  => ['Blog/edit',['method' => 'get'], ['id' => '\d+']],
        ':id'       => ['Blog/read',['method' => 'get'], ['id' => '\d+']],
        '__miss__'  => 'blog/miss',
    ],
    'new/:id'   => 'News/read',
    '__miss__'  => 'public/miss',
];

如果使用group方法注册路由的话,可以使用下面的方式:

Route::group('blog',function(){
    Route::rule(':id','blog/read',[],['id'=>'\d+']);
    Route::rule(':name','blog/read',[],['name'=>'\w+']);
    Route::miss('blog/miss');
},['method'=>'get','ext'=>'html']);

闭包支持

我们可以使用闭包的方式定义一些特殊需求的路由,而不需要执行控制器的操作方法了,例如:

Route::get('hello',function(){ 
    return 'hello,world!';
});

参数传递

闭包定义的时候支持参数传递,例如:

Route::get('hello/:name',function($name){ 
    return 'Hello,'.$name;
});

规则路由中定义的动态变量的名称 就是闭包函数中的参数名称,不分次序。

因此,如果我们访问的URL地址是:

http://serverName/hello/thinkphp

则浏览器输出的结果是:

Hello,thinkphp

路由绑定

可以使用路由绑定简化URL或者路由规则的定义,绑定支持如下方式:

绑定到模块/控制器/操作

把当前的URL绑定到模块/控制器/操作,最多支持绑定到操作级别,例如在路由配置文件中添加:

// 绑定当前的URL到 index模块
Route::bind('index');
// 绑定当前的URL到 index模块的blog控制器
Route::bind('index/blog');
// 绑定当前的URL到 index模块的blog控制器的read操作
Route::bind('index/blog/read');

该方式针对路由到模块/控制器/操作有效,假如我们绑定到了index模块的blog控制器,那么原来的访问URL从

http://serverName/index/blog/read/id/5

可以简化成

http://serverName/read/id/5

如果定义了路由

Route::get('index/blog/:id','index/blog/read');

那么访问URL就变成了

http://serverName/5

绑定到命名空间

把当前的URL绑定到某个指定的命名空间,例如:

// 绑定命名空间
Route::bind('\app\index\controller','namespace');

那么,我们接下来只需要通过

http://serverName/blog/read/id/5

就可以直接访问 \app\index\controller\Blog类的read方法。

绑定到类

把当前的URL直接绑定到某个指定的类,例如:

// 绑定到类
Route::bind('\app\index\controller\Blog','class');

那么,我们接下来只需要通过

http://serverName/read/id/5

就可以直接访问 \app\index\controller\Blog类的read方法。

注意:绑定到命名空间和类之后,不会进行模块的初始化工作。

入口文件绑定

如果我们需要给某个入口文件绑定模块,可以使用下面两种方式:

  • 常量定义

只需要入口文件添加BIND_MODULE常量,即可把当前入口文件绑定到指定的模块或者控制器,例如:

// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
// 绑定到index模块
define('BIND_MODULE','index');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';
  • 自动入口绑定

如果你的入口文件都是对应实际的模块名,那么可以使用入口文件自动绑定模块的功能,只需要在应用配置文件中添加:

// 开启入口文件自动绑定模块
'auto_bind_module'  =>  true,

当我们重新添加一个 public/demo.php入口文件,内容和public/index.php一样:

// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';

但其实访问 demo.php的时候,其实已经自动绑定到了demo模块。

绑定模型

路由规则和分组支持绑定模型数据,例如:

Route::rule('hello/:id','index/index/hello','GET',[
    'ext'                   =>  'html',
    'bind_model'    =>  [
        'user'  =>  '\app\index\model\User',
    ],
]);

会自动给当前路由绑定 id为 当前路由变量值的User模型数据。

可以定义模型数据的查询条件,例如:

Route::rule('hello/:name/:id','index/index/hello','GET',[
    'ext'                   =>  'html',
    'bind_model'    =>  [
        'user'  =>  ['\app\index\model\User','id&name']
    ],
]);

表示查询id和name的值等于当前路由变量的模型数据。

也可以使用闭包来返回模型对象数据

Route::rule('hello/:id','index/index/hello','GET',[
    'ext'                   =>  'html',
    'bind_model'    =>  [
        'user'  =>  function($param){
            $model = new \app\index\model\User;
            return $model->where($param)->find();
        }
    ],
]);

闭包函数的参数就是当前请求的URL变量信息。

在控制器中可以通过下面的代码或者使用依赖注入获取:

request()->user;

绑定的模型可以直接在控制器的架构方法或者操作方法中自动注入。

域名路由

ThinkPHP支持完整域名、子域名和IP部署的路由和绑定功能,同时还可以起到简化URL的作用。

要启用域名部署路由功能,首先需要开启:

'url_domain_deploy' =>  true

定义域名部署规则支持两种方式:动态注册和配置定义。

动态注册

可以在应用的公共文件或者配置文件中动态注册域名部署规则,例如:

// blog子域名绑定到blog模块
Route::domain('blog','blog');
// 完整域名绑定到admin模块
Route::domain('admin.thinkphp.cn','admin');
// IP绑定到admin模块
Route::domain('114.23.4.5','admin');

blog子域名绑定后,URL访问规则变成:

// 原来的URL访问
http://www.thinkphp.cn/blog/article/read/id/5
// 绑定到blog子域名访问
http://blog.thinkphp.cn/article/read/id/5

支持绑定的时候添加默认参数,例如:

// blog子域名绑定到blog模块
Route::domain('blog','blog?var=thinkphp');

除了绑定到模块之外,还隐式传入了一个$_GET['var'] = 'thinkphp' 变量。

支持直接绑定到控制器,例如:

// blog子域名绑定到index模块的blog控制器
Route::domain('blog','index/blog');

URL访问地址变化为:

// 原来的URL访问
http://www.thinkphp.cn/index/blog/read/id/5
// 绑定到blog子域名访问
http://blog.thinkphp.cn/read/id/5

如果你的域名后缀比较特殊,例如是com.cn或者net.cn 之类的域名,需要配置:

'url_domain_root'=>'thinkphp.com.cn'

泛域名部署

可以支持泛域名部署规则,例如:

// 绑定泛二级域名域名到book模块
Route::domain('*','book?name=*');

下面的URL访问都会直接访问book模块

http://hello.thinkphp.cn
http://quickstart.thinkphp.cn

并且可以直接通过$_GET['name']变量 获取当前的泛域名。

支持三级泛域名部署,例如:

// 绑定泛三级域名到user模块
Route::domain('*.user','user?name=*');

如果我们访问如下URL地址:

http://hello.user.thinkphp.cn

的同时,除了会访问user模块之外,还会默认传入 $_GET['name'] = 'hello'

在配置传入参数的时候,如果需要使用当前的泛域名作为参数,可以直接设置为“*”即可。

目前只支持二级域名和三级域名的泛域名部署。

配置定义方式

除了动态注册之外,还支持直接在路由配置文件中定义域名部署规则,例如:

return [
    '__domain__'=>[
        'blog'      => 'blog',
        // 泛域名规则建议在最后定义
        '*.user'    =>  'user',
        '*'         => 'book',
    ],
    // 下面是路由规则定义
]

域名绑定地址

前面我们看到的域名部署规则:

// blog子域名绑定到blog模块
Route::domain('blog','blog');

其实是把域名绑定到模块的方式,其实还有其他的绑定方式。

  • 绑定到命名空间
// blog子域名绑定命名空间
Route::domain('blog','\app\blog\controller');
  • 绑定到类
// blog子域名绑定到类
Route::domain('blog','@\app\blog\controller\Article');
  • 绑定到闭包函数

如果需要,你也可以直接把域名绑定到一个闭包函数,例如:

// blog子域名绑定闭包函数
Route::domain('blog',function(){
    echo 'hello';
    return ['bind'=>'module','module'=>'blog'];
});

域名绑定到闭包函数其实是一种劫持,可以在闭包函数里面动态注册其它的绑定机制或者注册新的路由,例如:

Route::domain('www', function(){
    // 动态注册域名的路由规则
    Route::rule('new/:id', 'index/news/read');
    Route::rule(':user', 'index/user/info');
});

如果你不希望继续,可以直接在闭包函数里面中止执行。

// blog子域名绑定到闭包函数
Route::domain('blog',function(){
    exit('hello');
});
  • 绑定路由规则

可以把域名绑定到一系列指定的路由规则,例如:

Route::domain('blog',[
    // 动态注册域名的路由规则
    ':id' => ['blog/read',['method'=>'GET'],['id'=>'\d+']],
    ':name'=>'blog/read',
]);

如果使用配置文件配置的话,可以按照下面的方式:

return [
    '__domain__'=>[
        'blog'      => [
            // 动态注册域名的路由规则
            ':id' => ['blog/read',['method'=>'GET'],['id'=>'\d+']],
            ':name'=>'blog/read',
        ],
    ],
    // 下面是其它的路由规则定义
]

URL生成

ThinkPHP5.0支持路由URL地址的统一生成,并且支持所有的路由方式,以及完美解决了路由地址的反转解析,无需再为路由定义和变化而改变URL生成。

URL生成使用 \think\Url::build() 方法或者使用系统提供的助手函数url(),参数一致:

Url::build('地址表达式',['参数'],['URL后缀'],['域名'])
url('地址表达式',['参数'],['URL后缀'],['域名'])

地址表达式和参数

对使用不同的路由地址方式,地址表达式的定义有所区别。参数单独通过第二个参数传入,假设我们定义了一个路由规则如下:

Route::rule('blog/:id','index/blog/read');

就可以使用下面的方式来生成URL地址:

Url::build('index/blog/read','id=5&name=thinkphp');
Url::build('index/blog/read',['id'=>5,'name'=>'thinkphp']);
url('index/blog/read','id=5&name=thinkphp');
url('index/blog/read',['id'=>5,'name'=>'thinkphp']);

下面我们统一使用第一种方式讲解。

使用模块/控制器/操作生成

如果你的路由方式是路由到模块/控制器/操作,那么可以直接写

// 生成index模块 blog控制器的read操作 URL访问地址
Url::build('index/blog/read','id=5&name=thinkphp');
// 使用助手函数
url('index/blog/read','id=5&name=thinkphp');

以上方法都会生成下面的URL地址:

/index.php/blog/5/name/thinkphp.html

注意,生成方法的第一个参数必须和路由定义的路由地址保持一致,如果写成下面的方式可能无法正确生成URL地址:Url::build('blog/read','id=5&name=thinkphp');

如果你的环境支持REWRITE,那么生成的URL地址会变为:

/blog/5/name/thinkphp.html

如果你配置了:

'url_common_param'=>true

那么生成的URL地址变为:

/index.php/blog/5.html?name=thinkphp

不在路由规则里面的变量会直接使用普通URL参数的方式。

需要注意的是,URL地址生成不会检测路由的有效性,只是按照给定的路由地址和参数生成符合条件的路由规则。

使用控制器的方法生成

如果你的路由地址是采用控制器的方法,并且路由定义如下:

// 这里采用配置方式定义路由 动态注册的方式一样有效
'blog/:id'  => '@index/blog/read'

那么可以使用如下方式生成:

// 生成index模块 blog控制器的read操作 URL访问地址
Url::build('@index/blog/read','id=5');
// 使用助手函数
url('@index/blog/read','id=5');

那么自动生成的URL地址变为:

/index.php/blog/5.html

使用类的方法生成

如果你的路由地址是路由到类的方法,并且做了如下路由规则定义:

// 这里采用配置方式定义路由 动态注册的方式一样有效
Route::rule(['blog','blog/:id'],'\app\index\controller\blog@read');

如果路由地址是到类的方法,需要首先给路由定义命名标识,然后使用标识快速生成URL地址。

那么可以使用如下方式生成:

// 生成index模块 blog控制器的read操作 URL访问地址
Url::build('blog?id=5');
url('blog?id=5');

那么自动生成的URL地址变为:

/index.php/blog/5.html

直接使用路由地址

我们也可以直接使用路由地址来生成URL,例如:

我们定义了路由规则如下:

'blog/:id' => 'index/blog/read'

可以使用下面的方式直接使用路由规则生成URL地址:

Url::build('/blog/5');

那么自动生成的URL地址变为:

/index.php/blog/5.html

URL后缀

默认情况下,系统会自动读取url_html_suffix配置参数作为URL后缀(默认为html),如果我们设置了:

url_html_suffix'   => 'shtml'

那么自动生成的URL地址变为:

/index.php/blog/5.shtml

如果我们设置了多个URL后缀支持

'url_html_suffix'   => 'html|shtml'

则会取第一个后缀来生成URL地址,所以自动生成的URL地址还是:

/index.php/blog/5.html

如果你希望指定URL后缀生成,则可以使用:

Url::build('index/blog/read','id=5','shtml');
url('index/blog/read','id=5','shtml');

域名生成

默认生成的URL地址是不带域名的,如果你采用了多域名部署或者希望生成带有域名的URL地址的话,就需要传入第四个参数,该参数有两种用法:

自动生成域名

Url::build('index/blog/read','id=5','shtml',true);
url('index/blog/read','id=5','shtml',true);

第四个参数传入true的话,表示自动生成域名,如果你开启了url_domain_deploy还会自动识别匹配当前URL规则的域名。

例如,我们注册了域名路由信息如下:

Route::domain('blog','index/blog');

那么上面的URL地址生成为:

http://blog.thinkphp.cn/read/id/5.shtml

指定域名

你也可以显式传入需要生成地址的域名,例如:

Url::build('index/blog/read','id=5','shtml','blog');
url('index/blog/read','id=5','shtml','blog');

或者传入完整的域名

Url::build('index/blog/read','id=5','shtml','blog.thinkphp.cn');
url('index/blog/read','id=5','shtml','blog.thinkphp.cn');

生成的URL地址为:

http://blog.thinkphp.cn/read/id/5.shtml

也可以直接在第一个参数里面传入域名,例如:

Url::build('index/blog/read@blog','id=5');
url('index/blog/read@blog','id=5');
url('index/blog/read@blog.thinkphp.cn','id=5');

生成锚点

支持生成URL的锚点,可以直接在URL地址参数中使用:

Url::build('index/blog/read#anchor@blog','id=5');
url('index/blog/read#anchor@blog','id=5');

锚点和域名一起使用的时候,注意锚点在前面,域名在后面。

生成的URL地址为:

http://blog.thinkphp.cn/read/id/5.html#anchor

隐藏或者加上入口文件

有时候我们生成的URL地址可能需要加上index.php或者去掉index.php,大多数时候系统会自动判断,如果发现自动生成的地址有问题,可以直接在调用build方法之前调用root方法,例如加上index.php:

Url::root('/index.php');
Url::build('index/blog/read','id=5');

或者隐藏index.php:

Url::root('/');
Url::build('index/blog/read','id=5');

root方法只需要调用一次即可。

参考文档