手把手走入注解之注解收集

 阅读约 16 分钟

前言

随着Swoole的不断的迭代,相应一些Swoole的协程框架也逐渐进入了大家的视野,比如:HyperfSwoft等;常驻内存的实现让PHP性能比传统PHP-FPM模式的框架有质的提升,依据Swoole开源的框架都提供了全面的开发组件,看过或使用过Hyperf、Swoft框架的小伙伴应该都知道,这些框架当中有类似SpringCloud框架灵活的注解,本文就以一个简单的demo实现一个注解的实现,方便大家更快速的了解注解。

什么是注解

注解的定义是:附加在数据/代码上的元数据(metadata)。

框架可以基于这些元信息为代码提供各种额外功能,本质上注解就是理解注解只是配置的另一种展现方式。

注解如何工作的

注解是如何在代码里面被识别,又是如何被调用的呢?带着疑问咱们一起来看代码,GitHubhttps://github.com/LoyaltyLu/annotation_demo这是我写的一个demo,里面包含了注解以及实现容器的一个简单的示例方便大家参看,再看文章前建议大家先看下这两个文档:
Doctrine Annotations
PHP反射

代码解析

注解收集

带大家通过demo简单了解下注解是如何被收集的。

类注解收集

index.php


<?php

$loader = require __DIR__ . "/vendor/autoload.php";

Core\Application::init($loader);

......

var_dump(\Core\Route::dispatch('/index/test'));

首先在index.php中使用composer 自动加载传入封装好的Application一个处理器类;调用init方法初始化。

Core\Application.php


<?php

namespace Core;

use Annotation\Parser\RequestMappingParser;

use Doctrine\Common\Annotations\AnnotationRegistry;

use \Doctrine\Common\Annotations;

/**

* 相当于一个处理器,做一些初始化工作

* Class Application

*

* @package Core

*/

class Application

{

    public static $beans = [];

    public static function init($loader)

    {

        AnnotationRegistry::registerLoader([$loader, 'loadClass']);

        self::loadAnnotationRoute();

        self::loadAnnotationBean();

    }

......

}

这里引用了doctrine/annotations包,更多参考Doctrine Annotations

init()方法首先自动加载,然后调用静态方法self::loadAnnotationRoute();

注意这里同时调用了self::loadAnnotationBean();方法这是模拟容器的一个方法咱们先看注解的实现逻辑

Core\Application.php


......

public static function loadAnnotationRoute()

{

    //自动加载注解类(规则)到组件当中

    $reader = new Annotations\AnnotationReader();

    //这里采用手动实例化类、可以利用 glob() 遍历文件

    $obj = new \App\Http\Controller\HomeController();

    $re = new \ReflectionClass($obj);

    //获取类注解

    $class_annos = $reader->getClassAnnotations($re);

    foreach ($class_annos as $class_anno) {

        $routePrefix = $class_anno->getPrefix();

        //通过反射得到所有的方法

        $refMethods = $re->getMethods();

        foreach ($refMethods as $method) {

            $methodAnnos = $reader->getMethodAnnotations($method);

            foreach ($methodAnnos as $methodAnno) {

                $routePath = $methodAnno->getRoute();

                //把某个逻辑放到在某个解析类当中处理逻辑

                //$re->newInstance();反射实例化

                (new RequestMappingParser())->parse($routePrefix,$routePath, $re->newInstance(), $method->name);
            }
       }

}

......

如何定制规则可翻阅文档

规则存放在:./Annotation/Mapping/目录中;

AnnotationReader类中的获取类注解的方法getClassAnnotations($re)方法需要一个反射类,更多反射请参考PHP反射

手动实例化类:$obj = new \App\Http\Controller\HomeController();

然后获取反射类:$re = new \ReflectionClass($obj);

因为是demo就没有做过于复杂,这里大家可以继续完善

调用getClassAnnotations($re)方法获取类所有注解

$class_annos = $reader->getClassAnnotations($re);

打印$class_annos的结果如下:


array(1) {

[0]=>

    object(Annotation\Mapping\Controller)#15 (1) {

        ["prefix":"Annotation\Mapping\Controller":private]=>

        string(6) "/index"

    }

}

这个结果是获取到的哪里的参数呢?接下来分别看下实例化的HomeController和定义的注解规则类Annotation\Mapping\Controller;

Annotation\Mapping\Controller.php


<?php declare(strict_types=1);

namespace Annotation\Mapping;

use Doctrine\Common\Annotations\Annotation\Attribute;

use Doctrine\Common\Annotations\Annotation\Attributes;

use Doctrine\Common\Annotations\Annotation\Required;

use Doctrine\Common\Annotations\Annotation\Target;

/**

* Class Controller

* @Annotation

* @Target("CLASS")

* @Attributes({

* @Attribute("prefix", type="string"),

* })

* @since 2.0

*/

final class Controller

{

    private $prefix = '';

    public function __construct(array $values)

    {

        if (isset($values['value'])) {
            $this->prefix = $values['value'];
        }

        if (isset($values['prefix'])) {
            $this->prefix = $values['prefix'];
        }
    }

    public function getPrefix(): string

    {

        return $this->prefix;
    }

}
为了节省篇幅这里删除了一部分没用的代码和注释,大家可以去GitHub中查看

注解类中的类注解是设置规则的地方:


/**
* Class Controller
* @Annotation
* @Target("CLASS")
* @Attributes({
* @Attribute("prefix", type="string"),
* })
* @since 2.0
*/

@Target指示种类元件,其注释类型是适用的。然后,你可以定义一个或多个目标:

  • CLASS 允许在类的docblock
  • PROPERTY 允许在属性的docblock
  • METHOD 允许在该方法的docblock
  • ALL 允许类,属性和方法的docblock
  • ANNOTATION 允许其他注释里面

更多介绍还请大家移步Doctrine Annotations

HomeController.php


<?php declare(strict_types=1);

namespace App\Http\Controller;

use Annotation\Mapping\Controller;

use Annotation\Mapping\RequestMapping;

/**
* Class HomeController
* @Controller(prefix="/index")
*/
class HomeController
{
......
}

根据注解类获取类规则设置的属性,我们可以在HomeController中设置@Controller(prefix="/index"),声明我们要被获取的内容,所以在打印$class_annos时我们获取到的数组中可以看得到注解已经被收集到:


array(1) {
[0]=>
    object(Annotation\Mapping\Controller)#15 (1) {
        ["prefix":"Annotation\Mapping\Controller":private]=>
            string(6) "/index"
    }
}

到此类注解的收集工作就完成了,方法的注解获取逻辑与类注解获取逻辑基本相同。

方法注解收集:

当类注解收集完成之后,我们可以继续利用反射类获取所有类当中的方法,具体操作如下:

$obj = new \App\Http\Controller\HomeController();

$re = new \ReflectionClass($obj);

//获取类注解
$class_annos = $reader->getClassAnnotations($re);

foreach ($class_annos as $class_anno) {

    $routePrefix = $class_anno->getPrefix();//获取所有注解
    
    //通过反射得到所有的方法
    $refMethods = $re->getMethods();
    
    foreach ($refMethods as $method) {
    
        $methodAnnos = $reader->getMethodAnnotations($method);
        foreach ($methodAnnos as $methodAnno) {
        
            $routePath = $methodAnno->getRoute();
            
            //把某个逻辑放到在某个解析类当中处理逻辑
            //$re->newInstance();反射实例化
            
            (new RequestMappingParser())->parse($routePrefix,$routePath, $re->newInstance(), $method->name)
        }
   }
}

通过反射类中的getMethods方法获取类中所有方法,var_dump($refMethods)数据如下:


array(2) {

    [0]=>object(ReflectionMethod)#16 (2) {
            ["name"]=>string(5) "index"
            ["class"]=>string(34) "App\Http\Controller\HomeController"
        }
    [1]=>object(ReflectionMethod)#13 (2) {
        ["name"]=>string(2) "demo"
        ["class"]=>string(34) "App\Http\Controller\HomeController"
      }
}

利用注解类的getMethodAnnotations($method);方法可以根据设置的方法获取注解规则来读取出每个方法设置的注解:


array(1) {
    [0]=>object(Annotation\Mapping\RequestMapping)#18 (1) {
    
        ["route":"Annotation\Mapping\RequestMapping":private]=>
                string(5)"/test"
    }
}

array(1) {

    [0]=>object(Annotation\Mapping\RequestMapping)#14 (1) {
         ["route":"Annotation\Mapping\RequestMapping":private]=>
            string(5)"/demo"
    }
}

方法注解收集规则配置:

/**
* HTTP action method annotation
* @Annotation
* @Target("METHOD")
*
* @since 2.0
*/
class RequestMapping
{

    ......

}

至此注解的的所有收集工作全部完成,后面的话就是业务处理逻辑,如何分发路由等等,大家可以先看下demo中的源码,后面会在跟大家写一个关于注解调用。

结束语

这是在自己学习过程中的一点点总结,文中有错误的地方请大家加帮忙指出,及时改正,希望能帮助到大家,这是2020年的第一篇文章相信这只是一个开始,春节马上来临,在这里提前预祝各位新年快乐,身体健康,薪资翻倍~谢谢~

阅读 739更新于 1月14日

推荐阅读
Grace development
用户专栏

记录分享开发、学习中的点点滴滴

4292 人关注
86 篇文章
专栏主页
目录