PHP的原生注解使用
之前写过一篇正则匹配实现的注解功能,见PHP实现注解功能,现在PHP 8已经原生支持了。注解可以让部分逻辑脱离当前的代码。如果你想,你可以使用在路由、模板或者参数注入等各种场景。PHP 8新增了注解反射ReflectionAttribute,使用ReflectionAttribute获取到注解类实例,我们可以很灵活地进行自己需要的操作。
之前在Linux上编译过一个alpha版本,注解写法为<
定义
注解类与普通的类定义几乎一样,唯一区别就在于注解类都存在一个Attribute注解,Attribute构造方法的参数为注解在何处使用。PHP中注解被允许使用在以下目标
类:Attribute::TARGET_CLASS
方法:Attribute::TARGET_METHOD
类属性:Attribute::TARGET_PROPERTY
类常量:Attribute::TARGET_CLASS_CONSTANT
函数:Attribute::TARGET_FUNCTION
形参:Attribute::TARGET_PARAMETER
任何以上目标:Attribute::TARGET_ALL
定义一个在类上使用的Attr注解,Attr拥有一个公开的成员变量name(成员变量不是必须,这里作演示用)
#[Attribute(Attribute::TARGET_CLASS)]
class Attr
{
public function __construct(
public string $name,
) {
}
}
使用
新建一个Student类,并使用Attr注解,向注解的构造方法传入参数
#[Attr('学生')]
class Student
{
}
使用反射来获取Student类的注解
$reflectionClass = new ReflectionClass(Student::class);
// 获取到所有Attr注解
$attributes = $reflectionClass->getAttributes('Attr');
// 得到Attr实例,这里也可以调用注解类里的方法
$attributeInstance = $attributes[0]->newInstance();
echo $attributeInstance->name; // 学生
类的注解大致用法就是这样,其他位置的注解同理,都是使用反射来获取。
更进一步
现在我们来实现一个基础的注解路由配置功能。
新建Group、Route、Middlewares三个注解类,分别为路由组、路由、中间件。
Group
/**
* 路由组
*/
#[Attribute(Attribute::TARGET_CLASS)]
class Group
{
public function __construct(
private string $group,
) {
}
public function getGroup(): string
{
return $this->group;
}
}
Route
/**
* 路由
*/
#[Attribute(Attribute::TARGET_METHOD)]
class Route
{
public function __construct(
private string $pattern,
private string $requestMethod,
) {
}
public function getRoute(): array
{
return [
'pattern' => $this->pattern,
'request_method' => $this->requestMethod,
];
}
}
Middlewares
/**
* 中间件
*/
#[Attribute(Attribute::TARGET_METHOD)]
class Middlewares
{
public function __construct(
private array $middlewares = [],
) {
}
public function getMiddlewares(): array
{
return $this->middlewares;
}
}
三个简单的注解类建好后,再新建一个Student类,给类加上Group注解、方法加上Route、Middlewares注解
Student
#[Group('student')]
class Student
{
#[
Route('/study', 'GET'),
Middlewares([Auth::class, Log::class])
]
public function study(): string
{
return 'Success';
}
}
现在来获取注解
$className = Student::class;
$methodName = 'study';
$data = [];
$refClass = new ReflectionClass($className);
$refMethod = $refClass->getMethod($methodName);
// 获取Group注解
$groupAttributes = $refClass->getAttributes('Group');
$classAttributeInstance = $groupAttributes[0]->newInstance();
$groupName = $classAttributeInstance->getGroup();
$data['group'] = $groupName;
// 获取Route注解
$routeAttributes = $refMethod->getAttributes('Route');
$methodAttributeInstance = $routeAttributes[0]->newInstance();
$route = $methodAttributeInstance->getRoute();
$data['route'] = $route;
// 获取Middlewares注解
$middlewaresAttributes = $refMethod->getAttributes('Middlewares');
$middlewaresInstance = $middlewaresAttributes[0]->newInstance();
$middlewares = $middlewaresInstance->getMiddlewares();
$data['middlewares'] = $middlewares;
print_r($data);
执行,输出如下
Array
(
[group] => student
[route] => Array
(
[pattern] => /study
[request_method] => GET
)
[middlewares] => Array
(
[0] => Auth
[1] => Log
)
)
到这里我们已经拿到路由的相关配置,以上代码仅为了演示注解的基础使用,请不要在生产环境直接使用。
由于注解路由配置的特性,在真实的业务逻辑中,需要遍历所有可能访问到的方法,获取到所有路由配置并缓存,才能使注解路由生效,这样做是因为我们无法预先知道用户请求什么地址。当然了,路由相关功能不在本文讨论范围内。
注:1、本文所用PHP版本为8.0.0。 2、文中都假设只存在一个同名注解,所以直接用[0]拿到。 3、路由相关功能不在本文讨论范围内。 4、反射相关内容在https://www.php.net/manual/zh/book.reflection.php