之前写过一篇正则匹配实现的注解功能,见PHP实现注解功能,现在PHP 8已经原生支持了。注解可以让部分逻辑脱离当前的代码。如果你想,你可以使用在路由、模板或者参数注入等各种场景。PHP 8新增了注解反射ReflectionAttribute,使用ReflectionAttribute获取到注解类实例,我们可以很灵活地进行自己需要的操作。

之前在Linux上编译过一个alpha版本,注解写法为<>,但是正式发布的8.0.0最终确定下来的注解写法为#[Attr],[]中可以允许多个注解类。使用这种写法还能向前兼容,在不支持注解的PHP版本中,这样的写法不会报错,仅仅是被当作行注释。初看觉得不如Java的@Attr好看,但是看习惯了觉得也还行。

定义

注解类与普通的类定义几乎一样,唯一区别就在于注解类都存在一个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

标签: 注解, PHP 8, Attribute

添加新评论