ThinkPHP 8.1版本功能盘点
流年 · 1年前
ThinkPHP `V8.1`版本是一个大的改进版本,目前尚未正式发布,即将在11月份发布,针对`V8.0`版本的一些不足进行了大量的完善,主要包含路由和验证方面,并且兼容最新的PHP`8.4`版本。
> 使用8.0版本的可以通过升级到`8.x-dev`版本升级体验或等正式发版后更新体验。
本篇内容我们来盘点一下`8.1`版本的相关改进(仅核心框架部分,不含ORM部分的改进,ORM的更新可以参考[更新日志](https://doc.thinkphp.cn/@think-orm/changelog.html)),看看有没有你期待的功能改进吧。
## 兼容PHP`8.4+`
最新框架对PHP最新的8.4+版本做了兼容处理,确保可以正常运行。并增加了`array_is_list`、`json_validate`兼容助手函数,不依赖对应PHP版本。
## 优化事件订阅者
现在可以支持给事件订阅者增加标识,提高事件响应效率(无需逐个注册事件监听和订阅),在事件定义文件使用:
```
return [
// ...
// 定义事件订阅者
'subscribe' => [
'user' => 'app\subscribe\UserSubscribe',
],
];
```
在`UserSubscribe`订阅类中定义相应的事件方法
```
<?php
namespace app\observer;
use app\model\User;
class UserSubscribe
{
public function onUserLogin(User $user)
{
// UserLogin事件响应处理
}
public function onUserLogout(User $user)
{
// UserLogout事件响应处理
}
}
```
事件方法的参数支持依赖注入,可以添加额外的参数。
假如需要触发`user_login`事件,触发代码如下:
```
event('user.user_login');
```
> 出于方法命名规范考虑,对于小写和下划线的事件名会自动触发驼峰命名的事件方法名。
## 增加`Macroable`方法注入
增加`Macroable`支持,只需要在你需要实现`Macroable`的类引入`Trait`即可使用。
```
use think\helper\Macroable;
```
然后通过`macro`静态方法注入闭包方法实现,注意由于`Macroable`机制采用PHP的`__call`以及`__callStatic`方法实现,如果已经存在则无法支持。
```
ClassName::macro('test', function(...$parms) {
// 判断是否已经定义
if ( !static::hasMacro('test')) {
// 方法逻辑实现
}
});
```
## 新的路由变量验证方法
增加了`when`方法用于指定某个路由变量的多个验证规则,当验证不通过的话当前路由规则将不会匹配。区别于`pattern`方法只能使用正则表达式验证,`when`方法可以支持所有的内置验证类规则。
```
Route::get('new/:name', 'News/read')->when('name', 'alphaNum');
Route::get('new/:category', 'News/category')->when('category', CategoryEnum::class);
```
并且当验证规则为数字类型的话,最终的路由变量会自动转换为整型或浮点型。
## 路由可选变量支持默认值
支持对可选路由变量设置默认值,对于动态路由尤其有用。
```
Route::get('blog/[:category]', 'blog/:category')
->default(['category' => 'all']);
```
## 路由中间件排除
在分组的路由规则中,可以排除或不使用中间件,使用下面的方式
```
Route::group('blog', function(){
Route::get(':id/edit','blog/edit');
Route::put(':id','blog/update');
Route::delete(':id','blog/delete');
Route::get(':id','blog/read')->withoutMiddleware(); // 无需鉴权
})->middleware(\app\middleware\Auth::class);
```
`withoutmiddleware`也支持通过数组方式传入需要排除的部分中间件。
## 路由分组绑定
路由分组可以支持绑定到命名空间、控制器或某个类,当分组下的定义路由都没有匹配成功会自动按绑定规则进行默认URL调度(不受强制路由影响),优先级高于分组MISS路由,并且支持多级分组,分组相关参数包括中间件仍然有效。
### 绑定到命名空间
```
Route::group('blog', function () {
Route::get(':id', 'blog/read');
Route::post(':id', 'blog/update');
Route::delete(':id', 'blog/delete');
})->pattern(['id' => '\d+'])->namespace('app\controller\blog');
```
分组绑定到指定命名空间后,对没有匹配的请求会自动执行`app\controller\blog` 命名空间下面的类和方法。
### 绑定到控制器
```
Route::group('blog', function () {
Route::get(':id', 'blog/read');
Route::post(':id', 'blog/update');
Route::delete(':id', 'blog/delete');
})->pattern(['id' => '\d+'])->controller('blog');
```
分组绑定到控制器(支持分级控制器)后,对没有匹配的请求会自动执行该控制器类下面的操作方法。
### 绑定到控制器分级
```
Route::group('blog', function () {
Route::get(':id', 'blog/read');
Route::post(':id', 'blog/update');
Route::delete(':id', 'blog/delete');
})->pattern(['id' => '\d+'])->layer('blog');
```
分组绑定到某个控制器分级后,对没有匹配的请求会自动执行分级目录下的控制器和方法。
### 绑定到类
```
Route::group('blog', function () {
Route::get(':id', 'blog/read');
Route::post(':id', 'blog/update');
Route::delete(':id', 'blog/delete');
})->pattern(['id' => '\d+'])->class(Blog::class);
```
分组绑定到类后,对没有匹配的请求会自动执行该类的方法。
### 简单绑定
如果你不需要定义分组路由,仅仅是需要进行分组绑定,可以直接使用下面的方式。
```
Route::group('blog')->layer('blog'); // 绑定到blog 控制器分级
Route::group('blog')->controller('blog'); // 绑定到blog控制器
Route::group('blog')->class(Blog::class); // 绑定到blog类
Route::group('blog')->namespace('app\controller\blog'); // 绑定到命名空间
```
## 自动URL调度
可以为某个分组开启自动URL调度(内部采用绑定机制,即绑定到控制器分级)。
```
Route::group('blog', function () {
Route::get(':id', 'blog/read');
Route::post(':id', 'blog/update');
Route::delete(':id', 'blog/delete');
})->pattern(['id' => '\d+'])->auto(); // 为blog分组启用自动自动URL调度
```
默认的URL调度规则为
```
https://domainName/groupName.../controllerName/actionName
```
其中`groupName`可能为多级分组,内部实现为多级控制器机制,`groupName`相当于控制器的多级子目录。
### 多模块URL自动调度
默认的URL解析规则为
```
https://domainName/controllerName/actionName
```
如需开启类似TP5的多模块访问URL的话,可以在路由定义文件的最后添加下面的一行代码
```
// 开启多模块URL自动解析
Route::auto();
```
如果没有匹配到任何路由规则的话,会按照下面的默认URL规则解析
```
https://domainName/moduleName/controllerName/actionName
```
如果访问下面的URL地址
```
https://tp.com/admin/index/hello
```
则会自动调用 app\\controller\\admin\\Index 控制器的`hello`操作方法。
如果你的默认URL解析规则需要根据业务做一些调整,例如加上接口的版本定义,也可以自定义默认规则
```
// 自定义URL解析规则
Route::auto('v<version>/[:controller]/[:action]', 'v<version>/:controller/:action')
->pattern(['version' => '\d+']);
```
可以在auto方法后调用路由的设置方法。
还可以在开启自动多模块路由的情况下,开启自动中间件加载。
```
Route::auto(middleware:true);
```
会自动加载和模块名同名的中间件别名(你可以通过别名定义一系列模块中间件)。
中间件别名定义在应用目录下的`middleware.php`文件,添加类似于下面的模块别名即可:
```
return [
'alias' => [
// 为admin模块定义中间件别名
'admin' => [
app\middleware\Auth::class,
app\middleware\Check::class,
],
...
],
];
```
## 资源路由扩展规则
可以在资源路由的默认规则之外,额外注册路由,比如需要使用下面的路由定义:
```
// 注册资源路由
Route::resource('space.bot', 'bot/index');
// 在资源路径下追加注册相关路由
Route::post('space/:space_id/bot/:id/chat', 'bot.chat/index');
Route::post('space/:space_id/bot/:id/chat/upload', 'bot.chat/upload');
Route::post('space/:space_id/bot/:id/chat/suggestion', 'bot.chat/suggestion');
Route::post('space/:space_id/bot/:id/chat/speech', 'bot.chat/speech');
```
我们可以使用资源路由的扩展路由定义方法来简化注册(用法类似于分组路由的定义,仅支持使用闭包定义)。
```
Route::resource('space.bot', 'bot/index', function() {
Route::post('chat', 'bot.chat/index');
Route::post('chat/upload', 'bot.chat/upload');
Route::post('chat/suggestion', 'bot.chat/suggestion');
Route::post('chat/speech', 'bot.chat/speech');
});
```
或者使用`extend`方法注册,效果一致。
```
Route::resource('space.bot', 'bot/index')->extend(function() {
Route::post('chat', 'bot.chat/index');
Route::post('chat/upload', 'bot.chat/upload');
Route::post('chat/suggestion', 'bot.chat/suggestion');
Route::post('chat/speech', 'bot.chat/speech');
});
```
并且一样可以支持分组,例如可以进一步简化成
```
Route::resource('space.bot', 'bot/index', function() {
Route::group('chat', function() {
Route::post('/', 'bot.chat/index');
Route::post('upload', 'bot.chat/upload');
Route::post('suggestion', 'bot.chat/suggestion');
Route::post('speech', 'bot.chat/speech');
});
});
```
通过资源路由的扩展路由注册的优势是在路由匹配上效率更高,另外资源路由采用完整匹配模式,所以下面的扩展路由也会自动采用完整匹配模式。
## 数组数据验证
最新的验证类增加了数组数据的验证,可以对多维数组的数据指定相关验证规则。
```
<?php
namespace app\validate;
use think\Validate;
class User extends Validate
{
protected $rule = [
'name' => 'require|max:25',
'age' => 'number|between:1,120',
// info是一个索引数组
'info.email' => 'email',
'info.score' => 'number',
];
}
```
并且支持多维数组验证
```
<?php
namespace app\validate;
use think\Validate;
class User extends Validate
{
protected $rule = [
'name' => 'require|max:25',
'age' => 'number|between:1,120',
// info是一个二维数组
'info.*.email' => 'email',
'info.*.score' => 'number',
];
}
```
## 必须验证字段
如果你的必须验证字段比较多,可以无需在验证规则中一一定义,只需要在验证类中设置`must`属性即可。
例如,下面定义了`name`和`email`为必须验证。
```
<?php
namespace app\validate;
use think\Validate;
class User extends Validate
{
protected $must = ['name', 'email'];
protected $rule = [
'name' => 'max:25',
'age' => 'number|between:1,120',
'email' => 'email',
];
}
```
## 支持枚举验证
新版可以支持对枚举类进行验证,验证某个字段的值是否在允许的枚举值范围(支持普通枚举、回退枚举和自定义枚举类),例如:
```
<?php
namespace app\validate;
use think\Validate;
use app\enum\StatusEnum;
class User extends Validate
{
protected $rule = [
'name' => 'max:25',
'age' => 'number|between:1,120',
'status' => StatusEnum::class,
];
}
```
如果是自定义枚举类,需要实现`think\contract\Enumable`接口。或者使用下面的方式定义:
```
<?php
namespace app\validate;
use think\Validate;
use app\enum\StatusEnum;
class User extends Validate
{
protected $rule = [
'name' => 'max:25',
'age' => 'number|between:1,120',
'status' => ['enum' => StatusEnum::class],
];
}
```
## 增加验证规则
增加`accepted`、`acceptedIf`、`declined`、`declinedIf`、`multipleOf`等验证规则。
## 请求对象增加`layer`方法
`layer`方法用于获取当前的控制器分级,当控制器分级为多级的情况,`layer`方法的返回值会包含多级目录。
```
request()->layer();
```
当存在多级控制器的时候,`controller`方法也会包含控制器分级,如仅需获取当前的控制器类名,可以使用下面的方式:
```
request()->controller(); // 返回 admin/Blog
request()->controller(base:true); // 仅返回 Blog
```
## 操作方法参数绑定
支持对控制器的操作方法(包括路由到类的方法)的参数绑定范围支持设置,默认情况下仅包含当前请求的GET变量和路由变量,如果你需要包含其它变量(例如POST变量),可以在路由配置文件中设置
```
'action_bind_param' => 'param',
```
该参数支持设置的值包括`route`(仅支持绑定路由变量)、`get`(支持绑定get请求变量和路由变量,默认为get)、`param`(支持请求变量和路由变量)。
## 增加配置获取器功能
支持配置获取器功能用于远程配置中心,你可以注册一个配置获取器
```
think\facade\Config::hook(function($name, $value) {
// 对配置参数和值进行处理后返回最终的配置值
// ...
return $value;
});
```
## 公共环境变量文件
可以设置一个公共环境变量文件,会优先加载,然后在公共环境变量文件中定义部署的环境变量名称,完成动态切换不同的预设环境变量文件。
```
// 执行HTTP应用并响应
$http = (new App())->setBaseEnvName('base')->http;
$response = $http->run();
$response->send();
$http->end($response);
```
## 日志备份文件规则调整
对日志备份文件的命名规则做了适当调整,当设置了`max_files`后,不影响过期日志文件的删除。
## Cookie设置实时生效
现在对Cookie对象的设置将会实时生效,而不是下次请求才会有效。
```
Cookie::set('name', 'value');
// 马上获取数据
Cookie::get('name'); // 正常获取
```
## 改进依赖注入的对象默认值处理
如果对依赖注入的对象参数设置了默认值(包括`Null`),那么该对象在注入的时候不会自动创建新的实例。
## 缓存获取默认值支持闭包
对缓存`get`方法或`pull`方法支持传入闭包作为默认值参数。
```
Cache::get('name',function(){
// 动态返回数据
});
```
## 其它更新
* 优化异常处理对`json`的判断
* 改成系统初始化阶段的异常处理
* 改进验证场景处理
* 调整`invokeAfter`位置
* 改进缓存反序列化的异常处理
* Request `only`方法支持强制类型转换
* 缓存增加`fail_delete`配置参数 用于在获取缓存发生异常的时候是否强制删除
* 优化验证类的验证规则判断
* 改进缓存`serialize`/`unserialize`方法
<br>
资讯来源:https://support.topthink.com/@thinkphp/thinkphp-8.1-inventory.html
推荐资讯
-
保持初心,一起拥抱AI时代——聊聊ThinkPHP的2026和未来
2026年01月30日
-
ThinkPHP8.1.4版本发布——兼容PHP8.5及多项优化
2026年01月16日
-
你和专业文档手册之间,只差一个“录制”按钮
2025年11月12日
-
客服团队的效率革命:培训文档制作时间立省80%,告别无效内耗
2025年11月12日
-
从AI焦虑到AI从容:给企业的AI转型心理指南
2025年11月12日
最新资讯
-
保持初心,一起拥抱AI时代——聊聊ThinkPHP的2026和未来
2026年01月30日
-
ThinkPHP8.1.4版本发布——兼容PHP8.5及多项优化
2026年01月16日
-
你和专业文档手册之间,只差一个“录制”按钮
2025年11月12日
-
客服团队的效率革命:培训文档制作时间立省80%,告别无效内耗
2025年11月12日
-
从AI焦虑到AI从容:给企业的AI转型心理指南
2025年11月12日