在Java中,注解能控制程序的执行,这是天生自带的特性,给方法一些其他的标识,实现一些看起来很妙的操作。那么我们PHP天生不自带,有没有什么办法能实现呢?答案肯定是有的,你可以读取PHP文件为字符串,再利用正则来匹配,将注解与方法绑定在一起,肯定是能实现的。当然了,这种方法实现太硬核,代码执行起来相对来说也比较低效,本文要说的是使用正则匹配注释实现注解这个功能。

反射机制是PHP重要组成部分,虽然反射内容较多,但是PHP文档关于反射写得很清楚。今天会用到有ReflectionClassReflectionMethod两个反射类来获取到类和方法的注释。

创建一个Demo类到Demo.php

/**
 * Class Demo
 */
class Demo
{
    /**
     * method1
     */
    public function method1()
    {
    }

    /**
     * method2
     */
    public function method2()
    {
    }
}

先来获取一下注释,创建一个测试脚本main.php

$reflection = new ReflectionClass(Demo::class);
print_r($reflection->getDocComment());

打印结果如下

/**
 * Class Demo
 */

可以看到,我们获取到了类的注释,接下来我们来获取方法的注释

$reflection = new ReflectionClass(Demo::class);
print_r($reflection->getDocComment());
echo PHP_EOL;
foreach ($reflection->getMethods() as $method) {
    print_r($method->getDocComment());
    echo PHP_EOL;
}

打印结果如下

/**
 * Class Demo
 */
/**
 * method1
 */
/**
 * method2
 */

我们已经获取到了这个类以及方法的注释,假设我们将注解的格式定义为"@字母 值"的形式,然后我们试着增加几个这样格式的自定义注释

/**
 * Class Demo
 * @className Demo
 */
class Demo
{
    /**
     * method1
     * @requestMethod POST
     */
    public function method1()
    {
    }

    /**
     * method2
     * @requestMethod GET
     */
    public function method2()
    {
    }
}

我们给类增加了注释className,给每个方法增加了methodName。前面我们已经试过,能通过反射拿到这些注释,接下来用正则解析

$reflection = new ReflectionClass(Demo::class);
$annotation = [];
preg_match_all('/@([a-zA-Z]+)\s+(.*)/', $reflection->getDocComment(), $matches);
for ($i = 0; $i < count($matches[0]); $i++) {
    $annotation['_class'][$matches[1][$i]] = $matches[2][$i];
}
$i = $matches = null;

foreach ($reflection->getMethods() as $method) {
    preg_match_all('/@([a-zA-Z]+)\s+(.*)/', $method->getDocComment(), $matches);
    for ($i = 0; $i < count($matches[0]); $i++) {
        $annotation['_methods'][$method->getName()][$matches[1][$i]] = $matches[2][$i];
    }
    $i = $matches = null;
}
print_r($annotation);

打印测试,得到如下结果

Array
(
    [_class] => Array
        (
            [className] => Demo
        )

    [_methods] => Array
        (
            [method1] => Array
                (
                    [requestMethod] => POST
                )

            [method2] => Array
                (
                    [requestMethod] => GET
                )

        )

)

接下来将这些代码进行一下封装

class Annotation
{
    /**
     * 获取解析到的注解
     * @param $className
     * @param null $method
     * @return array
     * @throws \ReflectionException
     */
    public static function parse($className, $method = null)
    {
        $annotation = [];
        $ref = new \ReflectionClass($className);
        $annotation['_class'] = self::_parseAnnotation($ref->getDocComment());
        if ($method) {
            $annotation['_method'][$method] = self::_parseAnnotation($ref->getMethod($method)->getDocComment());
        } else {
            foreach ($ref->getMethods() as $method) {
                $methodName = $method->getName();
                $annotation['_methods'][$methodName] = self::_parseAnnotation($ref->getMethod($methodName)->getDocComment());
                $methodName = null;
            }
        }
        return $annotation;
    }

    /**
     * 解析出注解
     * @param $doc
     * @return array
     */
    private static function _parseAnnotation($doc)
    {
        $result = [];
        preg_match_all('/@([a-zA-Z]+)\s+(.*)/', $doc, $matches);
        if (!empty($matches)) {
            for ($i = 0; $i < count($matches[0]); $i++) {
                $result[$matches[1][$i]] = $matches[2][$i];
            }
        }
        $matches = null;
        return $result;
    }
}

测试一下

// 如果不传入方法名称则解析全部方法的注释
$annotation = Annotation::parse(Demo::class);
print_r($annotation);

最终打印结果同上。至此,已经获取到了我们想得到的信息,可以利用这些数据对程序进行一些控制了。

注:1、反射相关内容在https://www.php.net/manual/zh/book.reflection.php

标签: php, 反射机制, 注解

添加新评论