PHP中的注解
注解(Annotations)是Swoft里面很多重要功能特别是AOP,IoC容器的基础。
注解的定义是:“附加在数据/代码上的元数据(metadata)。”框架可以基于这些元信息为代码提供各种额外功能。
以另一个框架PHPUnit为例,注解@dataProvider声明一个方法作为测试用例方法的数据提供器。当PHPUnit框架执行到某一个测试用例方法时,会迭代该数据提供器,并将其返回的数据作为参数传入测试用例方法,为测试用例方法提供一套用例所需的测试数据。
//摘自phpseclib库的单元测试
public function formatLogDataProvider()
{
return array(
array(
//该参数会作为$message_log参数传到testFormatLog()测试用例方法中
array('hello world'),
array('<--'), //$message_number_log
"<--\r\n00000000 68:65:6c:6c:6f:20:77:6f:72:6c:64 hello world\r\n\r\n"//$expected
),
array(
array('hello', 'world'),
array('<--', '<--'),
"<--\r\n00000000 68:65:6c:6c:6f hello\r\n\r\n" .
"<--\r\n00000000 77:6f:72:6c:64 world\r\n\r\n"
),
);
}
/**
* @dataProvider formatLogDataProvider
*/
public function testFormatLog(array $message_log, array $message_number_log, $expected)
{
$ssh = $this->createSSHMock();
$result = $ssh->_format_log($message_log, $message_number_log);
$this->assertEquals($expected, $result);
}
一般而言,在编程届中注解是一种和注释平行的概念。
注释提供对可执行代码的说明,单纯用于开发人员阅读,不影响代码的执行;而注解往往充当着对代码的声明和配置的作用,为可执行代码提供机器可用的额外信息,在特定的环境下会影响程序的执行。
但是由于官方对PHP的Annotation方案迟迟没有达成一致(最新进展可以在 PHP: rfc看到),目前PHP没有对注解的官方实现。主流的PHP框架中使用的注解都是借用T_DOC_COMMENT型注释块(/**型注释*/)中的@Tag,定义自己的注解机制。
想对PHP注解的发展史要有更多了解的朋友可以参考Rafael Dohms的这个PPT:https://www.slideshare.net/rdohms/annotations-in-php-they-exist/
Doctrine注解引擎
Swoft没有重新造轮子,搞一个新的的注解方案,而是选择使用Doctrine的注解引擎
Doctrine的注解方案也是基于T_DOC_COMMENT型注释的,Doctrine使用反射获取代码的T_DOC_COMMENT型注释,并将注释中的特定类型@Tag映射到对应注解类。为此,Swoft首先要为每一个框架自定义的注解定义注解类。
注解定义
@Breaker注解的注解类定义如下。
<?php
//Swoft\Sg\Bean\Annotation\Breaker.php
namespace Swoft\Sg\Bean\Annotation;
/**
* the annotation of breaker
*
* @Annotation //声明这是一个注解类
* @Target("CLASS")//声明这个注解只可用在class级别的注释中
*/
class Breaker
{
/**
* the name of breaker
*
* @var string //@var是PHPDoc标准的常用的tag,定义了属性的类型\
* Doctrine会根据该类型额外对注解参数进行检查
*/
private $name = "";
/**
* 若注解类提供构造器,Doctrine会调用,一般会在此处对注解类对象的private属性进行赋值
* Breaker constructor.
*
* @param array $values //Doctrine注解使用处的参数数组,
*/
public function __construct(array $values)
{
if (isset($values['value'])) {
$this->name = $values['value'];
}
if (isset($values['name'])) {
$this->name = $values['name'];
}
}
//按需写的getter setter code....
}
简单几行,一个@Breaker的注解类的定义工作就完成了。
注解类加载器的注册
在框架的bootstap阶段,swoft会扫描所有的PHP源码文件获取并解析注解信息。
使用Doctrine首先需要提供一个类的自动加载方法,这里直接使用了swoft当前的类加载器。Swoft的类加载器由Composer自动生成,这意味着注解类只要符合PSR-4规范即可自动加载。
//Swoft\Bean\Resource\AnnotationResource.php
/**
* 注册加载器和扫描PHP文件
*
* @return array
*/
protected function registerLoaderAndScanBean()
{
// code code....
AnnotationRegistry::registerLoader(function ($class) {
if (class_exists($class) || interface_exists($class)) {
return true;
}
return false;
});
// coco....
return array_unique($phpClass);
}
使用Doctrine获取注解对象
扫描各源码目录获取PHP类后,Sworft会遍历类列表加载类,获取类级别,方法级别,属性级别的所有注解对象。结果存放在AnnotationResource的$annotations成员中。
//Swoft\Bean\Resource\AnnotationResource.php
/**
* 解析bean注解
*
* @param string $className
*
* @return null
*/
public function parseBeanAnnotations(string $className)
{
if (!class_exists($className) && !interface_exists($className)) {
return null;
}
// 注解解析器
$reader = new AnnotationReader();
$reader = $this->addIgnoredNames($reader);//跳过Swoft内部注解
$reflectionClass = new \ReflectionClass($className);
$classAnnotations = $reader->getClassAnnotations($reflectionClass);
// 没有类注解不解析其它注解
if (empty($classAnnotations)) {
return;
}
foreach ($classAnnotations as $classAnnotation) {
$this->annotations[$className]['class'][get_class($classAnnotation)] = $classAnnotation;
}
// 解析属性
$properties = $reflectionClass->getProperties();
foreach ($properties as $property) {
if ($property->isStatic()) {
continue;
}
$propertyName = $property->getName();
$propertyAnnotations = $reader->getPropertyAnnotations($property);
foreach ($propertyAnnotations as $propertyAnnotation) {
$this->annotations[$className]['property'][$propertyName][get_class($propertyAnnotation)] = $propertyAnnotation;
}
}
// 解析方法
$publicMethods = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC);
foreach ($publicMethods as $method) {
if ($method->isStatic()) {
continue;
}
$methodName = $method->getName();
// 解析方法注解
$methodAnnotations = $reader->getMethodAnnotations($method);
foreach ($methodAnnotations as $methodAnnotation) {
$this->annotations[$className]['method'][$methodName][get_class($methodAnnotation)][] = $methodAnnotation;
}
}
}
注解的解析
doctrine完成的功能仅仅是将注解映射到将用@Annotation声明的注解类。swoft需要自行处理注解对象获取注解中的信息。这一步有两个重要功能:
- 扫描搜集Bean的所有信息包括Bean名,类名以及该Bean各个需要注入的属性信息等,存放到ObjectDefinition数组中。
//Swoft\Bean\Wrapper\AbstractWrapper.php
/**
* 封装注解
*
* @param string $className
* @param array $annotations 注解3剑客,包含了类级别,方法级别,属性级别的注解对象,注解解析流程你会一直看到他
*
* @return array|null
*/
public function doWrapper(string $className, array $annotations)
{
$reflectionClass = new \ReflectionClass($className);
// 解析类级别的注解
$beanDefinition = $this->parseClassAnnotations($className, $annotations['class']);
//code...
// parser bean annotation
list($beanName, $scope, $ref) = $beanDefinition;
// 初始化Bean结构,并填充该Bean的相关信息
$objectDefinition = new ObjectDefinition();
$objectDefinition->setName($beanName);
$objectDefinition->setClassName($className);
$objectDefinition->setScope($scope);
$objectDefinition->setRef($ref);
if (!$reflectionClass->isInterface()) {
// 解析属性,并获取属性相关依赖注入的信息
$properties = $reflectionClass->getProperties();
$propertyAnnotations = $annotations['property']??[];
$propertyInjections = $this->parseProperties($propertyAnnotations, $properties, $className);
$objectDefinition->setPropertyInjections($propertyInjections);//PropertyInjection对象
}
// 解析方法
$publicMethods = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC);
$methodAnnotations = $annotations['method'] ??[];
$this->parseMethods($methodAnnotations, $className, $publicMethods);
return [$beanName, $objectDefinition];
}
- 在注解解析时Parser会调用相关的Collector搜集功能所需的信息,譬如进行事件注册。
举个例子,BootstrapParser的解析仅仅就是搜集注解。Collector在Swoft中是注解信息的最终装载容器。一般而言@XXXX注解对应的Parser和Collect就是XXXXParser和XXXXCollect,知道这个惯例会大大方便你对Swoft源码的阅读。
//Swoft\Bean\Parser\BootstrapParser.php
/**
* the parser of bootstrap annotation
*
* @uses BootstrapParser
* @version 2018年01月12日
* @author stelin <phpcrazy@126.com>
* @copyright Copyright 2010-2016 swoft software
* @license PHP Version 7.x {@link http://www.php.net/license/3_0.txt}
*/
class BootstrapParser extends AbstractParser
{
/**
* @param string $className
* @param Bootstrap $objectAnnotation
* @param string $propertyName
* @param string $methodName
* @param mixed $propertyValue
*
* @return array
*/
public function parser(string $className, $objectAnnotation = null, string $propertyName = "", string $methodName = "", $propertyValue = null)
{
$beanName = $className;
$scope = Scope::SINGLETON;
BootstrapCollector::collect($className, $objectAnnotation, $propertyName, $methodName, $propertyValue);
return [$beanName, $scope, ""];
}
}
由于框架执行前必须完整的获取各种注解到Collertor和生成Bean定义集合,所以Swoft是不进行lazyload的。
注解的使用
现在我们终于可以用一个的例子来讲解注解是如何运行。InitMbFunsEncoding是一个实现了Bootable的类,他的作用是在应用启动时候设定系统的编码。但是仅仅实现了Bootable接口并不会让框架在启动时自动调用他。
因此我们需要InitMbFunsEncoding为添加一个@Bootstrap(order=1)
类注解,让他成为一个Bootstrap型的Bean。
//Swoft\Bootstrap\Boots.InitMbFunsEncoding.php
<?php
namespace Swoft\Bootstrap\Boots;
use Swoft\Bean\Annotation\Bootstrap;
/**
* @Bootstrap(order=1)
* @uses InitMbFunsEncoding
* @version 2017-11-02
* @author huangzhhui <huangzhwork@gmail.com>
* @copyright Copyright 2010-2017 Swoft software
* @license PHP Version 7.x {@link http://www.php.net/license/3_0.txt}
*/
class InitMbFunsEncoding implements Bootable
{
/**
* bootstrap
*/
public function bootstrap()
{
mb_internal_encoding("UTF-8");
}
}
我们在上文已经提过框架启动时会扫描PHP源码
- 将Bean的定义信息存放到ObjectDefinition数组中
- 将注解信息存放到各个Collector中
因此在框架的Bootstrap阶段,可以从BootstrapCollector中直接获取所有@Bootstrap型的Bean,实例化并Bean执行。
<?php
\\Swoft\Bootstrap\Bootstrap.php;
//code ...
/**
* bootstrap
*/
public function bootstrap()
{
$bootstraps = BootstrapCollector::getCollector();
//根据注解类型的不同,注解中的属性会有不同的作用,譬如@Bootstrap的order就影响各个Bean的执行顺序。
array_multisort(array_column($bootstraps, 'order'), SORT_ASC, $bootstraps);
foreach ($bootstraps as $bootstrapBeanName => $name){
//使用Bean的ObjectDefinition信息构造实例或获取现有实例
/* @var Bootable $bootstrap*/
$bootstrap = App::getBean($bootstrapBeanName);
$bootstrap->bootstrap();
}
}
//code ...
以上就是Swoft注解机制的整体实现了。
Swoft源码剖析系列目录:https://www.jianshu.com/p/2f679e0b4d58
网友评论