ThinkPHP8学习篇(二):路由
ThinkPHP 支持传统的 MVC(Model-View-Controller)模式以及流行的 MVVM(Model-View-ViewModel)模式的应用开发,请求流程:路由 → 控制器 → 模型(DB操作) → 视图渲染。所以接下来 ThinkPHP 的学习核心内容将集中在路由、控制器、模型与数据库操作、视图与模板这四个核心内容上。本篇文章将记录ThinkPHP路由的学习过程。
一、路由定义文件
二、路由定义
1、注册路由
2、规则表达式
3、可选变量
4、完全匹配
5、额外参数
6、路由标识
三、变量规则
1、局部变量规则
2、全局变量规则
3、动态路由
四、路由地址
1、路由到控制器/操作
2、路由到类的方法
3、重定向路由
4、路由到模板
五、路由参数
六、路由分组
1、路由延迟解析
七、资源路由
八、MISS路由
1、全局MISS路由
2、分组MISS路由
九、注解路由
一、路由定义文件
路由规则的注册需要在应用的路由定义文件中完成。路由定义和检测是针对应用的,因此如果你采用的是多应用模式,每个应用的路由都是完全独立的,并且路由地址不能跨应用(除非采用重定向路由)。
route 目录用来存放路由定义文件。默认的路由定义文件是 route.php,但是可以将它更改为任意的文件名,或者添加多个路由定义文件。在我默认创建的项目中,route 目录下的路由文件名为 app.php。
├─route 路由定义目录
│ ├─route.php 路由定义
│ ├─api.php 路由定义
│ └─... 更多路由定义
路由的配置文件为 config 目录下的 route.php,可以在该文件中对路由配置进行更改。
二、路由定义
要定义路由,需要使用 Route 类。要使用 Route 类注册路由必须首先在路由定义文件开头添加引用:
use think\facade\Route;
1、注册路由
基础的路由注册方法是:
Route::rule('路由表达式', '路由地址', '请求类型');
示例
// 注册路由到 Index 控制器的 hello 操作
Route::rule('hello/:name', 'Index/hello');
可以在 rule 方法中指定请求类型(不指定的话默认为任何请求类型有效):
Route::rule('hello/:name', 'Index/hello', 'post');
请求类型参数不区分大小写。
表示定义的路由规则只有在 POST 请求下才有效。如果要定义 GET 和 POST 请求都支持的路由规则,可以在多个请求之间使用 | 分隔:
Route::rule('hello/:name', 'Index/hello', 'post|get');
为了应对不同请求的路由规则,ThinkPHP给我们提供了对应请求类型的快捷方法。具体方法如下表所示:
类型 | 描述 | 快捷方法 |
GET | GET请求 | get |
POST | POST请求 | post |
PUT | PUT请求 | put |
DELETE | DELETE请求 | delete |
PATCH | PATCH请求 | patch |
HEAD | HEAD请求 | head |
OPTIONS | OPTIONS请求 | options |
* | 任何请求类型 | any |
快捷方法的用法为:
Route::快捷方法名('路由表达式', '路由地址');
我们现在使用快捷方法对上面的示例进行改写:
Route::get('hello/:name', 'Index/hello');
注册多个路由规则后,系统会依次遍历注册过的满足请求类型的路由规则,一旦匹配到正确的路由规则后则开始执行最终的调度方法,后续规则就不再检测。
2、规则表达式
规则表达式通常包含静态规则和动态规则,以及两种规则的结合,例如下面都属于有效的规则表达式:
Route::rule('/', 'index'); // 首页访问路由
Route::rule('my', 'Member/myinfo'); // 静态地址路由
Route::rule('blog/<id>', 'Blog/read'); // 静态地址和动态地址结合
Route::rule('new/<year>/<month>/<day>', 'News/read'); // 静态地址和动态地址结合
Route::rule('<user>/<blog_id>', 'Blog/read'); // 全动态地址
规则表达式的定义以 / 为参数分割符
每个参数中可以包括动态变量,例如 :变量 或者 <变量> 都表示动态变量(官方推荐使用第二种方式,但是在文档中和创建的项目中,都是使用的第一种方式,不知道为什么),并且会自动绑定到操作方法的对应参数。
3、可选变量
ThinkPHP支持对路由参数的可选定义,即该参数可以有也可以没有,例如:
Route::get('hello/[:name]', 'Index/hello');
// 或者
Route::get('hello/<name?>', 'Index/hello');
变量用 [] 包含起来或 <变量> 形式后加 ? 号,则表示该变量是路由匹配的可选变量。
定义以上路由规则后,下面的URL访问地址都可以被正确的路由匹配:
http://serverName/hello
http://serverName/hello/zhangsan
采用可选变量定义后,之前需要定义两个或者多个路由规则才能处理的情况可以合并为一个路由规则。
注意:可选参数只能放到路由规则的最后,如果在中间使用了可选参数的话,后面的变量都会变成可选参数。
也可以使用 default() 方法给可选变量设置默认值:
Route::get('hello/<name?>', 'Index/hello')->default(['name' => 'world']);
4、完全匹配
规则匹配检测的时候默认只是对URL从头开始匹配,只要URL地址开头包含了定义的路由规则就会匹配成功。例如:我们访问 http://serverName/helloabc 和 http://serverName/hello 的效果是一样的,这显然不是我们想要的结果,我们期望的结果应该是完全匹配才可以访问,因为只有完全匹配才是正确的URL。
如果希望URL进行完全匹配,需要在路由表达式最后使用 $ 符号。
示例
Route::get('hello/<name?>$', 'Index/hello')->default(['name' => 'world']);
这样定义后,只有访问 http://serverName/hello 才会成功。
现在出现了一个问题,就是我们想每个URL都完全匹配。如果每个规则都这样设置,那岂不是太麻烦了,而且还有忘记的风险。
好在ThinkPHP给我们提供了URL全局完全匹配的设置,只要对其进行了设置,则全部的URL都为完全匹配。具体是在路由配置文件 config/route.php 中设置:
// 开启路由完全匹配
'route_complete_match' => true,
开启全局完全匹配后,如果需要对某个路由关闭完全匹配,需要使用 completeMatch() 方法,传入 false :
Route::get('hello/<name?>', 'Index/hello')->default(['name' => 'world'])->completeMatch(false);
5、额外参数
在路由跳转的时候支持额外传入参数对。额外参数指的是不在URL里面的参数,隐式传入需要的操作中,有时候能够起到一定的安全防护作用。
传入额外参数,使用的是 append() 方法,向该方法传入参数对即可。
示例
Route::get('hello/<name?>', 'Index/hello')->default(['name' => 'world'])->append(['status' => 1]);
示例中的路由规则定义的 status 参数是URL里面不存在的,属于隐式传值。可以针对不同的路由设置不同的额外参数。
注意:如果 append 方法中的变量和路由规则存在冲突的话,append 方法传入的优先。
6、路由标识
如果需要快速的根据路由生成URL地址,可以在定义路由的时候指定生成标识(但要确保唯一)。
name(标识名称) 方法用于给路由生成标识名称。
示例
Route::get('hello/<name?>', 'Index/hello')->default(['name' => 'world'])->append(['status' => 1])->name('hello_name');
这样,生成路由地址的时候就可以使用
url('hello_name');
如果不定义路由标识的话,系统会默认使用路由地址作为路由标识。
三、变量规则
ThinkPHP默认的变量规则设置是 \w+,只会匹配字母、数字、中文和下划线字符,并不会匹配特殊符号以及其它字符。如果要匹配这些字符,则需要定义变量规则或者调整默认变量规则。 可以在路由配置文件 config/route.php 中自定义默认的变量规则。
示例 在路由配置文件中增加中划线字符的匹配
'default_route_pattern' => '[\w\-]+',
支持在规则路由中指定变量规则,弥补了动态变量无法限制具体的类型问题。
1、局部变量规则
局部变量规则,仅在当前路由有效。可以通过局部变量规则来规定动态变量的数据类型是什么。
pattern(array) 方法用于设置局部变量规则。
示例
// 定义GET请求路由规则 并设置name变量规则
Route::get('hello/<name?>', 'Index/hello')->pattern(['name' => '[\w]+']);
2、全局变量规则
pattern(array) 方法也可以用于全局变量规则设置,此时不能指定路由规则。
示例
// 支持批量添加
Route::pattern(['name' => '\w+','id' => '\d+',
]);
通过全局变量规则设置,可以对通用的动态变量的数据类型进行统一管理。
pattern 方法仅支持对变量设置正则验证,V8.1.0+版本开始可以使用 when 方法替代,指定某个变量的多个验证规则(支持所有的内置验证类规则,参考内置验证规则),两种方式根据实际情况进行选择使用。
示例
// 验证 name 变量的值是否为字母和数字
Route::get('new/:name', 'News/read')->when('name', 'alphaNum');
// 验证 category 变量的值是否在允许的枚举值范围
Route::get('new/:category', 'News/category')->when('category', CategoryEnum::class);
3、动态路由
可以把路由规则中的变量传入路由地址中,就可以实现一个动态路由,例如:
Route::get('hello/:name', 'index/:name/hello');
name 变量的值作为路由地址传入。
四、路由地址
路由地址表示定义的路由表达式最终需要路由到的实际地址(或者响应对象)以及一些需要的额外参数,支持以下几种方式定义。
1、路由到控制器/操作
这是最常用的一种路由方式,把满足条件的路由规则路由到相关的控制器和操作,然后由系统调度执行相关的操作,格式为:
模块/控制器/操作
解析规则是从操作开始解析,然后解析控制器,例如:
// 路由到 Test 控制器
Route::get('test/:id','Test/read');
Test(文件地址:app/controller/Test.php) 类定义如下:
<?php
namespace app\controller;class Test
{function read($id){return "read: $id";}
}
还可以支持路由到动态的应用、控制器或者操作,例如:
// action变量的值作为操作方法传入
Route::get(':action/test/:id', 'Test/:action');
2、路由到类的方法
这种方式的路由可以支持执行任何类的方法,而不局限于执行控制器的操作方法。虽然ThinkPHP支持这种操作,但我个人不太建议使用这种方式,因为这种方式会造成结构不够清晰,出现普通类充当控制器的行为,可能导致到处都有路由执行的方法。
路由地址的格式为(动态方法):
\完整类名@方法名 或 [ 完整类名, 方法名 ]
或者(静态方法):
\完整类名::方法名
示例
Route::get('blog/:id','\app\service\Blog@read');
执行的是 \app\service\Blog 类的 read 方法。
Blog(文件地址:app/service/Blog.php) 类定义如下:
<?php
namespace app\service;class Blog
{function read($id){return "read: $id";}
}
3、重定向路由
redirect() 方法用于注册一个重定向路由。
示例
Route::redirect('blog/:id', 'http://blog.thinkphp.cn/read/:id', 302);
4、路由到模板
view() 方法支持路由直接渲染模板输出。
示例
// 路由到模板文件
Route::view('welcome/:name', 'index/hello');
该路由会渲染当前应用下的 view/index/hello.html 模板文件输出。
五、路由参数
路由分组及规则定义支持指定路由参数,这些参数主要完成路由匹配检测以及后续行为。
参数 | 说明 | 方法名 |
ext | URL后缀检测,支持匹配多个后缀 | ext |
deny_ext | URL禁止后缀检测,支持匹配多个后缀 | denyExt |
https | 检测是否https请求 | https |
domain | 域名检测 | domain |
complete_match | 是否完整匹配路由 | completeMatch |
model | 绑定模型 | model |
cache | 请求缓存 | cache |
ajax | Ajax检测 | ajax |
pjax | Pjax检测 | pjax |
json | JSON检测 | json |
validate | 绑定验证器类进行数据验证 | validate |
append | 追加额外的参数 | append |
middleware | 注册路由中间件 | middleware |
filter | 请求变量过滤 | filter |
match | 路由闭包检测 | match |
var_rule | 路由变量检测验证( V8.1.0+ ) | when |
default | 路由可选变量的默认值( V8.1.0+ ) | default |
header | Header检测( V8.1.3+ ) | header |
version | 路由版本检测( V8.1.3+ ) | version |
路由参数可以在定义路由规则的时候直接传入(批量),推荐使用方法名方式。
示例
Route::get('new/:id', 'News/read')->ext('html')->https();
上述示例表示 URL 后缀是 html 并且请求是 https 时该路由才有效。
路由参数可以混合使用,只要有任何一条参数检查不通过,该路由就不会生效。
如果需要批量设置路由参数,也可以使用 option 方法。
// 与上面的示例效果一样
Route::get('new/:id', 'News/read')->option(['ext' => 'html','https' => true]);
六、路由分组
路由分组功能允许把相同前缀的路由定义合并分组,这样可以简化路由定义,并且提高路由匹配的效率,不必每次都去遍历完整的路由规则。
使用 Route 类的 group 方法进行路由分组。
示例
Route::group('test', function () {Route::rule(':id', 'Test/readId'); // 等同于:(test/:id, Test/readId)Route::rule(':name', 'Test/readName'); // 等同于:(test/:name, Test/readName)
})->pattern(['id' => '\d+', 'name' => '\w+']);
分组路由支持所有的路由参数设置。
1、路由延迟解析
延迟路由解析,也就是说定义的路由规则在加载路由定义文件的时候并没有实际注册,而是在匹配到路由分组或者域名的情况下,才会实际进行注册和解析,这样就大大提高了路由注册和解析的性能。
路由延迟解析默认是关闭的,需要在路由配置文件中设置:
// 开启路由延迟解析
'url_lazy_route' => true,
一旦开启路由的延迟解析,将会对定义的域名路由和分组路由进行延迟解析,也就是说只有实际匹配到该域名或者分组后才会进行路由规则的注册,避免不必要的注册和解析开销。
七、资源路由
支持 RESTFul 请求的资源路由,方式如下:
// 注册一个blog资源路由指向Blog控制器
Route::resource('blog', 'Blog');
表示注册了一个名称为blog的资源路由到Blog控制器,系统会自动注册7个路由规则,如下:
标识 | 请求类型 | 生成路由规则 | 对应操作方法(默认) |
index | GET | blog | index |
create | GET | blog/create | create |
save | POST | blog | save |
read | GET | blog/:id | read |
edit | GET | blog/:id/edit | edit |
update | PUT | blog/:id | update |
delete | DELETE | blog/:id | delete |
具体指向的控制器由路由地址决定,只需要为Blog控制器创建以上对应的操作方法就可以支持下面的URL访问:
http://serverName/blog/
http://serverName/blog/128
http://serverName/blog/28/edit
Blog控制器中的对应方法如下:
<?php
namespace app\controller;class Blog
{public function index(){}public function read($id){}public function edit($id){}
}
可以改变默认的 id 参数名:
Route::resource('blog', 'Blog')->vars(['blog' => 'blog_id']);
此时Blog控制器的方法需要做如下调整:
<?php
namespace app\controller;class Blog
{public function index(){}public function read($blog_id){}public function edit($blog_id){}
}
为了遵循变量规范,控制器的变量可以改成驼峰命名:
<?php
namespace app\controller;class Blog
{public function index(){}public function read($blogId){}public function edit($blogId){}
}
也可以在定义资源路由的时候限定执行的方法(标识),如:
// 只允许index read edit update 四个操作
Route::resource('blog', 'Blog')->only(['index', 'read', 'edit', 'update']);// 排除index和delete操作
Route::resource('blog', 'Blog')->except(['index', 'delete']);
资源路由的标识不可更改,但生成的路由规则和对应的操作方法可以修改。
如果要更改某个资源路由标识的对应操作,可以使用下面方法:
Route::rest('create', ['GET', '/add','add']); // 路由变为 /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'],
]);
八、MISS路由
1、全局MISS路由
如果希望在没有匹配到所有的路由规则后执行一条设定的路由,可以注册一个单独的 MISS 路由:
Route::miss('public/miss');
或者使用闭包定义:
Route::miss(function() {return '404 Not Found!';
});
当所有已经定义的路由规则都不匹配的话,会路由到miss方法定义的路由地址。
可以限制 MISS 路由的请求类型:
// 只有 GET 请求下 MISS 路由有效
Route::miss('public/miss', 'get');
2、分组MISS路由
分组支持独立的MISS路由,例如:
Route::group('blog', function () {Route::rule(':id', 'blog/read');Route::rule(':name', 'blog/read');Route::miss('blog/miss');
});
九、注解路由
支持使用PHP8的注解方式定义路由(也称为注解路由),如果要使用注解路由需要安装额外的扩展:
composer require topthink/think-annotation
然后只需要直接在控制器类的方法中定义,例如:
<?php
namespace app\controller;use think\annotation\route\Route;class Index
{/*** @param string $name 数据名称* @return mixed*/#[Route("GET", "hello/:name")]public function hello($name){return 'hello,'.$name;}
}
注解Route的第一个参数支持 GET/POST/PUT/DELETE/PATCH/OPTIONS/HEAD/*(注意必须大写),也可以直接使用请求名简化定义:
<?php
namespace app\controller;use think\annotation\route\Get;class Index
{/*** @param string $name 数据名称* @return mixed*/#[Get("hello/:name")]public function hello($name){return 'hello,'.$name;}
}
请务必注意注解的定义规范,不能在注解路由里面使用单引号,否则可能导致注解路由解析失败。
如果有路由参数需要定义,可以在后面传入路由参数,例如:
<?php
namespace app\controller;use think\annotation\route\Route;class Index
{/*** @param string $name 数据名称* @return mixed*/[#Route("GET", "hello/:name", ["https"=>1, "ext"=>"html"])]public function hello($name){return 'hello,'.$name;}
}
同样,支持在类的注解里面定义资源路由,例如:
<?php
namespace app\controller;use think\annotation\route\Resource;#[Resource("blog")]
class Blog
{public function index(){}public function read($id){}public function edit($id){}
}
也可以定义路由分组,例如:
<?php
namespace app\controller;use think\annotation\route\Group;
use think\annotation\route\Route;#[Group("blog")]
class Blog
{/*** @param string $name 数据名称* @return mixed*/#[Route("GET","hello/:name")]public function hello($name){return 'hello,'.$name;}
}
当前控制器中的注解路由会自动加入blog分组下面,最终,会注册一个 blog/hello/:name 的路由规则。