兼容PHP8.4+

最新框架对PHP最新的8.4+版本做了兼容处理,确保可以正常运行。并增加了array_is_list、json_validate兼容助手函数,不依赖对应PHP版本。主要对路由和验证方面做了大量的改进,并且兼容PHP8.4+。

优化事件订阅者

现在可以支持给事件订阅者增加标识,提高事件响应效率(无需逐个注册事件监听和订阅),在事件定义文件使用:

  1. return [
  2. // ...
  3. // 定义事件订阅者
  4. 'subscribe' => [
  5. 'user' => 'app\subscribe\UserSubscribe',
  6. ],
  7. ];

在UserSubscribe订阅类中定义相应的事件方法

  1. <?php
  2. namespace app\observer;
  3. use app\model\User;
  4. class UserSubscribe
  5. {
  6. public function onUserLogin(User $user)
  7. {
  8. // UserLogin事件响应处理
  9. }
  10. public function onUserLogout(User $user)
  11. {
  12. // UserLogout事件响应处理
  13. }
  14. }

事件方法的参数支持依赖注入,可以添加额外的参数。

假如需要触发user_login事件,触发代码如下:

  1. event('user.user_login');

出于方法命名规范考虑,对于小写和下划线的事件名会自动触发驼峰命名的事件方法名。

增加Macroable方法注入

增加Macroable支持,只需要在你需要实现Macroable的类引入Trait即可使用。

use think\helper\Macroable; 然后通过macro静态方法注入闭包方法实现,注意由于Macroable机制采用PHP的call以及callStatic方法实现,如果已经存在则无法支持。

  1. ClassName::macro('test', function(...$parms) {
  2. // 判断是否已经定义
  3. if ( !static::hasMacro('test')) {
  4. // 方法逻辑实现
  5. }
  6. });

新的路由变量验证方法

增加了when方法用于指定某个路由变量的多个验证规则,当验证不通过的话当前路由规则将不会匹配。区别于pattern方法只能使用正则表达式验证,when方法可以支持所有的内置验证类规则。

  1. Route::get('new/:name', 'News/read')->when('name', 'alphaNum');
  2. Route::get('new/:category', 'News/category')->when('category', CategoryEnum::class);

并且当验证规则为数字类型的话,最终的路由变量会自动转换为整型或浮点型。

路由可选变量支持默认值

支持对可选路由变量设置默认值,对于动态路由尤其有用。

  1. Route::get('blog/[:category]', 'blog/:category')
  2. ->default(['category' => 'all']);

路由中间件排除

在分组的路由规则中,可以排除或不使用中间件,使用下面的方式

  1. Route::group('blog', function(){
  2. Route::get(':id/edit','blog/edit');
  3. Route::put(':id','blog/update');
  4. Route::delete(':id','blog/delete');
  5. Route::get(':id','blog/read')->withoutMiddleware(); // 无需鉴权
  6. })->middleware(\app\middleware\Auth::class);

withoutmiddleware也支持通过数组方式传入需要排除的部分中间件。

路由分组绑定

路由分组可以支持绑定到命名空间、控制器或某个类,当分组下的定义路由都没有匹配成功会自动按绑定规则进行默认URL调度(不受强制路由影响),优先级高于分组MISS路由,并且支持多级分组,分组相关参数包括中间件仍然有效。

绑定到命名空间

  1. Route::group('blog', function () {
  2. Route::get(':id', 'blog/read');
  3. Route::post(':id', 'blog/update');
  4. Route::delete(':id', 'blog/delete');
  5. })->pattern(['id' => '\d+'])->namespace('app\controller\home');

分组绑定到指定命名空间后,对没有匹配的请求会自动执行app\controller\home 命名空间下面的类和方法。

绑定到控制器

  1. Route::group('blog', function () {
  2. Route::get(':id', 'read');
  3. Route::post(':id', 'update');
  4. Route::delete(':id', 'delete');
  5. })->pattern(['id' => '\d+'])->controller('blog');

分组绑定到控制器(支持分级控制器)后,对没有匹配的请求会自动执行该控制器类下面的操作方法。

绑定到控制器分级

  1. Route::group('admin', function () {
  2. Route::get('blog/:id', 'blog/read');
  3. })->pattern(['id' => '\d+'])->layer('admin');

分组绑定到某个控制器分级后,对没有匹配的请求会自动执行分级目录下的控制器和方法。

绑定到类

  1. Route::group('blog', function () {
  2. Route::get(':id', 'read');
  3. Route::post(':id', 'update');
  4. Route::delete(':id', 'delete');
  5. })->pattern(['id' => '\d+'])->class(Blog::class);

分组绑定到类后,对没有匹配的请求会自动执行该类的方法。

简单绑定

如果你不需要定义分组路由,仅仅是需要进行分组绑定,可以直接使用下面的方式。

  1. Route::group('blog')->layer('blog'); // 绑定到blog 控制器分级
  2. Route::group('blog')->controller('blog'); // 绑定到blog控制器
  3. Route::group('blog')->class(Blog::class); // 绑定到blog类
  4. Route::group('blog')->namespace('app\controller\blog'); // 绑定到命名空间

路由分组绑定在你需要增加或迁移URL路由规则的时候也非常有用。由于域名路由继承分组路由,所以,可以对域名进行类似的绑定操作。

自动URL调度

可以为某个分组开启自动URL调度(内部采用绑定机制,即绑定到控制器分级)。

  1. Route::group('blog', function () {
  2. Route::get(':id', 'blog/read');
  3. Route::post(':id', 'blog/update');
  4. Route::delete(':id', 'blog/delete');
  5. })->pattern(['id' => '\d+'])->auto(); // 为blog分组启用自动自动URL调度

默认的URL调度规则为

  1. https://domainName/groupName.../controllerName/actionName

其中groupName可能为多级分组,内部实现为多级控制器机制,groupName相当于控制器的多级子目录。

多模块URL自动调度

默认的URL解析规则为

  1. https://domainName/controllerName/actionName

如需开启类似TP5的多模块访问URL的话,可以在路由定义文件的最后添加下面的一行代码

  1. // 开启多模块URL自动解析
  2. Route::auto();

如果没有匹配到任何路由规则的话,会按照下面的默认URL规则解析

  1. https://domainName/moduleName/controllerName/actionName

如果访问下面的URL地址

  1. https://tp.com/admin/index/hello

则会自动调用 app\controller\admin\Index 控制器的hello操作方法。

如果你的默认URL解析规则需要根据业务做一些调整,例如加上接口的版本定义,也可以自定义默认规则

  1. // 自定义URL解析规则
  2. Route::auto('v<version>/[:controller]/[:action]', 'v<version>/:controller/:action')
  3. ->pattern(['version' => '\d+']);

可以在auto方法后调用路由的设置方法。

还可以在开启自动多模块路由的情况下,开启自动中间件加载。

  1. Route::auto(middleware:true);

会自动加载和模块名同名的中间件别名(你可以通过别名定义一系列模块中间件)。

中间件别名定义在应用目录下的middleware.php文件,添加类似于下面的模块别名即可:

  1. return [
  2. 'alias' => [
  3. // 为admin模块定义中间件别名
  4. 'admin' => [
  5. app\middleware\Auth::class,
  6. app\middleware\Check::class,
  7. ],
  8. ...
  9. ],
  10. ];

资源路由扩展规则

可以在资源路由的默认规则之外,额外注册路由,比如需要使用下面的路由定义:

  1. // 注册资源路由
  2. Route::resource('space.bot', 'bot/index');
  3. // 在资源路径下追加注册相关路由
  4. Route::post('space/:space_id/bot/:id/chat', 'bot.chat/index');
  5. Route::post('space/:space_id/bot/:id/chat/upload', 'bot.chat/upload');
  6. Route::post('space/:space_id/bot/:id/chat/suggestion', 'bot.chat/suggestion');
  7. Route::post('space/:space_id/bot/:id/chat/speech', 'bot.chat/speech');

我们可以使用资源路由的扩展路由定义方法来简化注册(用法类似于分组路由的定义,仅支持使用闭包定义)。

  1. Route::resource('space.bot', 'bot/index', function() {
  2. Route::post('chat', 'bot.chat/index');
  3. Route::post('chat/upload', 'bot.chat/upload');
  4. Route::post('chat/suggestion', 'bot.chat/suggestion');
  5. Route::post('chat/speech', 'bot.chat/speech');
  6. });

或者使用extend方法注册,效果一致。

  1. Route::resource('space.bot', 'bot/index')->extend(function() {
  2. Route::post('chat', 'bot.chat/index');
  3. Route::post('chat/upload', 'bot.chat/upload');
  4. Route::post('chat/suggestion', 'bot.chat/suggestion');
  5. Route::post('chat/speech', 'bot.chat/speech');
  6. });

并且一样可以支持分组,例如可以进一步简化成

  1. Route::resource('space.bot', 'bot/index', function() {
  2. Route::group('chat', function() {
  3. Route::post('/', 'bot.chat/index');
  4. Route::post('upload', 'bot.chat/upload');
  5. Route::post('suggestion', 'bot.chat/suggestion');
  6. Route::post('speech', 'bot.chat/speech');
  7. });
  8. });

通过资源路由的扩展路由注册的优势是在路由匹配上效率更高,另外资源路由采用完整匹配模式,所以下面的扩展路由也会自动采用完整匹配模式。

数组数据验证

最新的验证类增加了数组数据的验证,可以对多维数组的数据指定相关验证规则。

  1. <?php
  2. namespace app\validate;
  3. use think\Validate;
  4. class User extends Validate
  5. {
  6. protected $rule = [
  7. 'name' => 'require|max:25',
  8. 'age' => 'number|between:1,120',
  9. // info是一个索引数组
  10. 'info.email' => 'email',
  11. 'info.score' => 'number',
  12. ];
  13. }

并且支持多维数组验证

  1. <?php
  2. namespace app\validate;
  3. use think\Validate;
  4. class User extends Validate
  5. {
  6. protected $rule = [
  7. 'name' => 'require|max:25',
  8. 'age' => 'number|between:1,120',
  9. // info是一个二维数组
  10. 'info.*.email' => 'email',
  11. 'info.*.score' => 'number',
  12. ];
  13. }

必须验证字段

如果你的必须验证字段比较多,可以无需在验证规则中一一定义,只需要在验证类中设置must属性即可。

例如,下面定义了name和email为必须验证。

  1. <?php
  2. namespace app\validate;
  3. use think\Validate;
  4. class User extends Validate
  5. {
  6. protected $must = ['name', 'email'];
  7. protected $rule = [
  8. 'name' => 'max:25',
  9. 'age' => 'number|between:1,120',
  10. 'email' => 'email',
  11. ];
  12. }

支持枚举验证

新版可以支持对枚举类进行验证,验证某个字段的值是否在允许的枚举值范围(支持普通枚举、回退枚举和自定义枚举类),例如:

  1. <?php
  2. namespace app\validate;
  3. use think\Validate;
  4. use app\enum\StatusEnum;
  5. class User extends Validate
  6. {
  7. protected $rule = [
  8. 'name' => 'max:25',
  9. 'age' => 'number|between:1,120',
  10. 'status' => StatusEnum::class,
  11. ];
  12. }

如果是自定义枚举类,需要实现think\contract\Enumable接口。或者使用下面的方式定义:

  1. <?php
  2. namespace app\validate;
  3. use think\Validate;
  4. use app\enum\StatusEnum;
  5. class User extends Validate
  6. {
  7. protected $rule = [
  8. 'name' => 'max:25',
  9. 'age' => 'number|between:1,120',
  10. 'status' => ['enum' => StatusEnum::class],
  11. ];
  12. }

增加验证规则

增加accepted、acceptedIf、declined、declinedIf、multipleOf等验证规则。

请求对象增加layer方法

layer方法用于获取当前的控制器分级,当控制器分级为多级的情况,layer方法的返回值会包含多级目录。

  1. request()->layer();

当存在多级控制器的时候,controller方法也会包含控制器分级,如仅需获取当前的控制器类名,可以使用下面的方式:

  1. request()->controller(); // 返回 admin/Blog
  2. request()->controller(base:true); // 仅返回 Blog

操作方法参数绑定

支持对控制器的操作方法(包括路由到类的方法)的参数绑定范围支持设置,默认情况下仅包含当前请求的GET变量和路由变量,如果你需要包含其它变量(例如POST变量),可以在路由配置文件中设置

  1. 'action_bind_param' => 'param',

该参数支持设置的值包括route(仅支持绑定路由变量)、get(支持绑定get请求变量和路由变量,默认为get)、param(支持请求变量和路由变量)。

增加配置获取器功能

支持配置获取器功能用于远程配置中心,你可以注册一个配置获取器

  1. think\facade\Config::hook(function($name, $value) {
  2. // 对配置参数和值进行处理后返回最终的配置值
  3. // ...
  4. return $value;
  5. });

公共环境变量文件

可以设置一个公共环境变量文件,会优先加载,然后在公共环境变量文件中定义部署的环境变量名称,完成动态切换不同的预设环境变量文件。

  1. // 执行HTTP应用并响应
  2. $http = (new App())->setBaseEnvName('base')->http;
  3. $response = $http->run();
  4. $response->send();
  5. $http->end($response);

日志备份文件规则调整

对日志备份文件的命名规则做了适当调整,当设置了max_files后,不影响过期日志文件的删除。

Cookie设置实时生效

现在对Cookie对象的设置将会实时生效,而不是下次请求才会有效。

  1. Cookie::set('name', 'value');
  2. // 马上获取数据
  3. Cookie::get('name'); // 正常获取

改进依赖注入的对象默认值处理

如果对依赖注入的对象参数设置了默认值(包括Null),那么该对象在注入的时候不会自动创建新的实例。

缓存获取默认值支持闭包

对缓存get方法或pull方法支持传入闭包作为默认值参数。

  1. Cache::get('name',function(){
  2. // 动态返回数据
  3. });

其它更新

  • 优化异常处理对json的判断
  • 改成系统初始化阶段的异常处理
  • 改进验证场景处理
  • 调整invokeAfter位置
  • 改进缓存反序列化的异常处理
  • Request only方法支持强制类型转换
  • 缓存增加fail_delete配置参数 用于在获取缓存发生异常的时候是否强制删除
  • 优化验证类的验证规则判断
  • 改进缓存serialize/unserialize方法