在国内PHP框架中,Laravel和ThinkPHP绝对是使用人数最多的两个框架了。PHP目前的最新版本也已经升级到PHP8.5。还有多少人在坚持使用PHP5.x和PHP7.x呢?
升级到PHP8之后,PHP终于支持注解了。对于使用java语言的小伙伴来说,太熟悉不过了。
那什么是注解呢?PHP官方的解释:
PHP注解为类、方法、函数、参数、属性和常量提供了结构化且机器可读的元数据。它们可以通过反射API在运行时进行检查,从而实现动态行为而无需修改代码。注解提供了声明式的方式来为代码添加元数据注释。
注解使得功能实现与其使用之间实现解耦。接口通过强制定义方法来规范结构,而注解则为方法、函数、属性和常量在内的多个元素提供元数据。与接口不同,接口强制实现方法,而注解则在不改变代码结构的情况下为其添加注释。
简单点说,注解就是在不改变代码原有逻辑的基础上,为简化代码和实现解耦,在类、方法、函数、参数、常量、属性上提供的一套声明式的元数据注释。
最近的项目正好用到了注解,博主就以ThinkPHP8注解路由聊聊如何使用。
ThinkPHP8注解路由
Thinkphp从8.0开始,也紧随PHP8的版本,加入了PHP8原生注解的支持。(Laravel对应版本同理可用)
注解路由长啥样,先上图:
要使用注解路由,先要安装对应的扩展:
composer require topthink/think-annotation
安装完成后,会在 config 目录下多出一个 annotation.php 的配置项文件。如果使用的是单应用模式,就可以直接开始使用注解了。但是博主使用的是多应用模式。所以,遇到的第一个问题是,如何让注解路由支持多应用模式?官方文档并没有说明。
于是乎,通过AI + 百度搜索,让我试出了正确的方法:
(注:ai给出的也不一定正确,需要分辨清楚)
多应用模式下,需要在annotation.php中配置多应用的路径:
有的文章这里配置的比较繁琐,但是博主的配置经过实践,是可行的。
然后,在 config/router.php中,开启强制路由模式。
// 是否强制使用路由'url_route_must' => true,
配置完成后,就可以使用注解路由了。
之前的写法,路由是需要在router文件夹下(或多应用的应用目录下的router文件夹中)写一大堆的路由文件,非常繁琐,像这样:
注解路由下,这些router文件,就不用再写了。只需要在对应的controller文件中加注释即可。
以下图为例:
1.基本写法
index方法,需要以Get方式访问,只需要在方法上写如下注释即可:
//必须用双引号#[Route("GET","list")] //或者#[Get("list")]
两种写法:
访问url
带参数的同理,只需更改访问地址即可,和之前的路由写法一致
#[Route("GET","list/:id")] //必须用双引号//或者#[Get("list/:id")] //必须用双引号
访问url
2.路由分组:
如果要使用路由分组,只需要在类名上面加分组注解:
#[Group("admin")] #admin为分组名classAdmin{ }
同上,Group首字母也要大写。
3.中间件middleware
如果是作用于类,就写在类的上面,如果是作用于方法,就写在方法的上面
#[Middleware([TokenMiddleware::class,PermissionMiddleware::class])]// 或#[Middleware(TokenMiddleware::class)]
如果是多个路由中间件,需要用方括号括起来,如果只需要一个,可以省略掉方括号,直接写类的地址
4.资源路由
需要在类上加资源路由注解
#[Resource("admin")]classAdmin{}
至此,基本的用法就都学会了。
但是,也有例外。
特殊的中间件例外
如果,中间件middleware作用于类上,但是又不想让所有的方法都走中间件怎么办?PHP官方的注解写法中没有 except 的写法,因此,只能按照Thinkphp的控制器中间件的写法才可以。
#[Group("admin")]classadminextendsBaseController{ protected $middleware = [ TokenMiddleware::class=>["except"=>["processData","delData"]], //或 Token2Middleware::class=>["only"=>["index","getList"]], ]; #[Route("GET","list")] public function index(){}}
except 数组中是哪些方法不走TokenMiddleware
only 数组中是只有哪些方法走TokenMiddleware
同一个中间件,except 和 only 只能二选一,不能两者同时存在。
基本用法告一段落。但官发文档中不是说用反射api在运行时检查从而实现动态行为吗?能不能自定义注解呢?
当然可以!
自定义注解
以博主之前的项目为例,需求是权限管理。需要在登录后,对每个路由方法鉴权,无权访问返回403无权访问json
这里用到了中间件。
1.先创建自定义注解
新建 app/attribute/Permission.php类
<?phpnamespace app\attribute;#[\Attribute(\Attribute::TARGET_METHOD)]classPermission{ public function __construct( public string $code, //权限码 public string $msg='无访问权限' ) {}}
这里创建了 Permission 类,构造方法中,code即为权限码,后面会在方法上使用。msg为提示信息,可以不写,走默认。
2.在中间件中使用反射api获取自定义注解类
有了权限注解类,然后就需要创建一个权限中间件,让每个方法都需要去鉴权。
核心代码如下:
public function handle($request, \Closure $next) { $controller = $request->controller(); $controller = "app\adminapi\controller\\".str_replace('.', "\\", $controller); $action = $request->action(); try { $reflectionMethod = new ReflectionMethod($controller, $action); $attributes = $reflectionMethod->getAttributes(Permission::class); $access = []; if (!empty($attributes)) { /** @var permission $permission */ $permission = $attributes[0]->newInstance(); $adminRoleService = app()->make(AdminRoleService::class); $menuService = app()->make(MenuService::class); $roleInfo = $adminRoleService->getOneByPk((int)$request->role_id, 'roles'); $menu = $menuService->getListAll([['menu_id', 'in', $roleInfo['roles']]], '*', 'menu_id asc'); if (!$menu->isEmpty()) { foreach ($menu as $item) { $access[] = $item->permission; } } if (!in_array($permission->code,$access)) { return json(['code' => 403, 'msg' => $permission->msg]); } } } catch (\ReflectionException $e) { Log::error("permissionMiddleware--".$e->getMessage()); } return $next($request); }
先用 $request对象获取到类和方法, 然后new一个 ReflectionMethod 类,通过 getAttributes 方法,获取到属性类,再通过 newInstance() 获取属性类的实例,也就是Permission类。这时,就可以获取到注解Permission的参数。
3.根据注解类的参数,判断是否有权访问接口
在类上加中间件注解,PermissionMiddleware就是刚创建的权限中间件。
#[Middleware(PermissionMiddleware::class)]classAdmin{}
//方法上加如下。adminuser 为权限名称#[Permission("adminUser")]
完成如上操作,在看中间件中的代码,从menu表中获取当前用户的权限列表,然后去查找注解中权限(adminUser,通过$permission->code 获取)是否在权限列表中,不存在则返回403。
至此,自定义注解的创建与使用也告一段落了。
总结
使用注解路由,极大地节省了繁琐代码的书写,也使得代码更加便于阅读与查找。只要遵循一定的规范,比如类名、方法名与路由地址保持一定的对应关系或保持名称结构一致,注解是非常方便的。无论哪种语言,书写逻辑都是相通的。
文章有不足之处,望诸君批评指正。
诸君留步,下期再会~