原标题:手把手走入注解——注解收集
本文转载于思否社区专栏:Grace development
作者:LoyaltyLu
前言
随着 Swoole 的不断的迭代,相应一些 Swoole 的协程框架也逐渐进入了大家的视野,比如:Hyperf、Swoft 等;常驻内存的实现让 PHP 性能比传统 PHP-FPM 模式的框架有质的提升,依据 Swoole 开源的框架都提供了全面的开发组件,看过或使用过 Hyperf、Swoft 框架的小伙伴应该都知道,这些框架当中有类似 SpringCloud 框架灵活的注解,本文就以一个简单的 demo 实现一个注解的实现,方便大家更快速的了解注解。
什么是注解
注解的定义是:附加在数据/代码上的元数据(metadata)。
框架可以基于这些元信息为代码提供各种额外功能,本质上注解就是理解注解只是配置的另一种展现方式。
注解如何工作的
注解是如何在代码里面被识别,又是如何被调用的呢?带着疑问咱们一起来看代码。
GitHub:
https://github.com/LoyaltyLu/annotation_demo
这是我写的一个 demo,里面包含了注解以及实现容器的一个简单的示例方便大家参看,再看文章前建议大家先看下这两个文档:
Doctrine Annotations:
https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html
PHP 反射:
https://www.php.net/manual/zh/book.reflection.php
代码解析
注解收集
带大家通过 demo 简单了解下注解是如何被收集的。
类注解收集
index.php
$loader = require__DIR__. "/vendor/autoload.php";
CoreApplication::init($loader);
......
var_dump(CoreRoute::dispatch( '/index/test'));
首先在index.php中使用composer自动加载传入封装好的Application一个处理器类;调用init方法初始化。
CoreApplication.php
namespaceCore;
useAnnotationParserRequestMappingParser;
useDoctrineCommonAnnotationsAnnotationRegistry;
useDoctrineCommonAnnotations;
/**
* 相当于一个处理器,做一些初始化工作
* Class Application
*
* @packageCore
*/
classApplication
{
publicstatic$beans = [];
publicstaticfunctioninit($loader)
{
AnnotationRegistry::registerLoader([$loader, 'loadClass']);
self::loadAnnotationRoute;
self::loadAnnotationBean;
}
......
}
这里引用了doctrine/annotations包,更多参考Doctrine Annotations
init方法首先自动加载,然后调用静态方法self::loadAnnotationRoute;
注意这里同时调用了 self::loadAnnotationBean; 方法这是模拟容器的一个方法咱们先看注解的实现逻辑
CoreApplication.php
......
publicstaticfunctionloadAnnotationRoute
{
//自动加载注解类(规则)到组件当中
$reader = newAnnotationsAnnotationReader;
//这里采用手动实例化类、可以利用 glob 遍历文件
$obj = newAppHttpControllerHomeController;
$re = newReflectionClass($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;反射实例化
( newRequestMappingParser)->parse($routePrefix,$routePath, $re->newInstance, $method->name);
}
}
}
......
如何定制规则可翻阅 文档:
https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/custom.html#custom-annotation-classes
规则存放在:./Annotation/Mapping/目录中;
AnnotationReader类中的获取类注解的方法getClassAnnotations($re)方法需要一个反射类,更多反射请参考 PHP 反射;
手动实例化类:
$obj = new AppHttpControllerHomeController;
然后获取反射类:
$re = new ReflectionClass($obj);
因为是 demo 就没有做过于复杂,这里大家可以继续完善
调用getClassAnnotations($re)方法获取类所有注解
$class_annos = $reader->getClassAnnotations($re);
打印$class_annos的结果如下:
array( 1) {
[ 0]=>
object(AnnotationMappingController) #15 (1) {
[ "prefix": "AnnotationMappingController": private]=>
string( 6) "/index"
}
}
这个结果是获取到的哪里的参数呢?接下来分别看下实例化的HomeController和定义的注解规则类AnnotationMappingController;
AnnotationMappingController.php
namespaceAnnotationMapping;
useDoctrineCommonAnnotationsAnnotationAttribute;
useDoctrineCommonAnnotationsAnnotationAttributes;
useDoctrineCommonAnnotationsAnnotationRequired;
useDoctrineCommonAnnotationsAnnotationTarget;
/**
* Class Controller
* @Annotation
* @Target("CLASS")
* @Attributes({
* @Attribute("prefix", type="string"),
* })
* @since2.0
*/
finalclassController
{
private$prefix = '';
publicfunction__construct(array $values)
{
if( isset($values[ 'value'])) {
$this->prefix = $values[ 'value'];
}
if( isset($values[ 'prefix'])) {
$this->prefix = $values[ 'prefix'];
}
}
publicfunctiongetPrefix: string
{
return$this->prefix;
}
}
为了节省篇幅这里删除了一部分没用的代码和注释,大家可以去 GitHub 中查看:
https://github.com/LoyaltyLu/annotation_demo
注解类中的类注解是设置规则的地方:
/**
* Class Controller
* @Annotation
* @Target("CLASS")
* @Attributes({
* @Attribute("prefix", type="string"),
* })
* @since2.0
*/
@Target 指示种类元件,其注释类型是适用的。然后,你可以定义一个或多个目标:
CLASS 允许在类的 docblock
PROPERTY 允许在属性的 docblock
METHOD 允许在该方法的 docblock
ALL 允许类,属性和方法的 docblock
ANNOTATION 允许其他注释里面
更多介绍还请大家移步 Doctrine Annotations:
https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/custom.html#custom-annotation-classes
HomeController.php
namespaceAppHttpController;
useAnnotationMappingController;
useAnnotationMappingRequestMapping;
/**
* Class HomeController
* @Controller(prefix="/index")
*/
classHomeController
{
......
}
根据注解类获取类规则设置的属性,我们可以在HomeController中设置@Controller(prefix="/index"),声明我们要被获取的内容,所以在打印$class_annos时我们获取到的数组中可以看得到注解已经被收集到:
array( 1) {
[ 0]=>
object(AnnotationMappingController) #15 (1) {
[ "prefix": "AnnotationMappingController": private]=>
string( 6) "/index"
}
}
到此类注解的收集工作就完成了,方法的注解获取逻辑与类注解获取逻辑基本相同。
方法注解收集
当类注解收集完成之后,我们可以继续利用反射类获取所有类当中的方法,具体操作如下:
$obj = newAppHttpControllerHomeController;
$re = newReflectionClass($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;反射实例化
( newRequestMappingParser)->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) "AppHttpControllerHomeController"
}
[ 1]=>object(ReflectionMethod) #13 (2) {
[ "name"]=>string( 2) "demo"
[ "class"]=>string( 34) "AppHttpControllerHomeController"
}
}
利用注解类的getMethodAnnotations($method); 方法可以根据设置的方法获取注解规则来读取出每个方法设置的注解:
array( 1) {
[ 0]=>object(AnnotationMappingRequestMapping) #18 (1) {
[ "route": "AnnotationMappingRequestMapping": private]=>
string( 5) "/test"
}
}
array( 1) {
[ 0]=>object(AnnotationMappingRequestMapping) #14 (1) {
[ "route": "AnnotationMappingRequestMapping": private]=>
string( 5) "/demo"
}
}
方法注解收集规则配置:
/**
* HTTP action method annotation
* @Annotation
* @Target("METHOD")
*
* @since2.0
*/
classRequestMapping
{
......
}
至此注解的的所有收集工作全部完成,后面的话就是业务处理逻辑,如何分发路由等等,大家可以先看下 demo 中的源码,后面会在跟大家写一个关于注解调用。
结束语
这是在自己学习过程中的一点点总结,文中有错误的地方请大家加帮忙指出,及时改正,希望能帮助到大家,祝各位新年快乐,身体健康,薪资翻倍~谢谢~
SegmentFault 思否社区和文章作者展开更多互动和交流。
责任编辑: