註解(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