注解(Annotations)是Swoft里面不少重要功能特别是AOP,IoC容器的基础。
注解的定义是:“附加在数据/代码上的元数据(metadata)。”框架能够基于这些元信息为代码提供各类额外功能。php
以另外一个框架PHPUnit为例,注解@dataProvider声明一个方法做为测试用例方法的数据提供器。当PHPUnit框架执行到某一个测试用例方法时,会迭代该数据提供器,并将其返回的数据做为参数传入测试用例方法,为测试用例方法提供一套用例所需的测试数据。git
//摘自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); }
通常而言,在编程届中注解是一种和注释平行的概念。
注释提供对可执行代码的说明,单纯用于开发人员阅读,不影响代码的执行;而注解每每充当着对代码的声明和配置的做用,为可执行代码提供机器可用的额外信息,在特定的环境下会影响程序的执行。github
可是因为官方对PHP的Annotation方案迟迟没有达成一致(最新进展能够在 PHP: rfc看到),目前PHP没有对注解的官方实现。主流的PHP框架中使用的注解都是借用T_DOC_COMMENT型注释块(/**型注释*/)中的@Tag,定义本身的注解机制。编程
想对PHP注解的发展史要有更多了解的朋友能够参考Rafael Dohms的这个PPT:https://www.slideshare.net/rdohms/annotations-in-php-they-exist/bootstrap
Swoft没有从新造轮子,搞一个新的的注解方案,而是选择使用Doctrine的注解引擎数组
Doctrine的注解方案也是基于T_DOC_COMMENT型注释的,Doctrine使用反射获取代码的T_DOC_COMMENT型注释,并将注释中的特定类型@Tag映射到对应注解类。为此,Swoft首先要为每个框架自定义的注解定义注解类。app
@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的注解类的定义工做就完成了。ssh
在框架的bootstap阶段,swoft会扫描全部的PHP源码文件获取并解析注解信息。ide
使用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); }
扫描各源码目录获取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须要自行处理注解对象获取注解中的信息。这一步有两个重要功能:
//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]; }
//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源码
<?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