Swoft Source Code Analysis-Annotation Mechanism in SW OFT

  php, swoole

Author:bromine
Links:https://www.jianshu.com/p/ef7 …
Source: Simple Book
The copyright belongs to the author. This article has been reprinted with the authorization of the author and the original text has been rearranged.
Swoft Github:https://github.com/swoft-clou …

Comments in PHP

AnnotationsIt is the foundation of many important functions in Swoft, especially AOP and IoC containers.
Comments are defined as follows: “Metadata attached to data/code. The framework can provide various additional functions for the code based on the meta-information.

Taking PHPUnit, another framework, as an example, annotation @dataProvider declares a method as a data provider for test case methods. When PHPUnit framework executes a test case method, it will iterate the data provider and pass the returned data into the test case method as parameters to provide a set of test data required by the test case method.

//摘自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);
}

In general, during the programming sessionannotationIs a kind of andNotesThe concept of parallelism.
Comments provide instructions for executable code, which are only for developers to read and do not affect the execution of the code. However, annotations often act as declarations and configurations of codes, providing additional information available to the machine for executable codes, which will affect the execution of programs under specific circumstances.

However, due to the delay in reaching an official agreement on PHP’s Annotation plan (the latest progress can be found inPHP: rfcSee),Currently PHP has no official implementation of annotations.. Comments used in mainstream PHP frameworks use T_DOC_COMMENT type comment blocks (/* type notes/) to define its own annotation mechanism.

For those who want to know more about the history of PHP annotation, please refer to Rafael Dohms’s PPT:https://www.slideshare.net/rd …

Doctrine annotation engine

Swoft did not rebuild the wheel and make a new annotation scheme, but chose to use it instead.Doctrine’s Annotation Engine

Doctrine’s annotation scheme is also based on T_DOC_COMMENT annotation. Doctrine uses reflection to obtain the T_DOC_COMMENT annotation of the code and maps the specific type @Tag in the annotation to the corresponding annotation class. To this end, Swoft must first define annotation classes for each frame’s customized annotations.

Annotation definition

The annotation class of @Breaker annotation is defined as follows.

<?php
//Swoft\Sg\Bean\Annotation\Breaker.php
namespace Swoft\Sg\Bean\Annotation;

/**
 * @Annotation //声明这是一个注解类
 * @Target("CLASS")//声明这个注解只可用在class级别的注释中
 */
class 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....
}

In a few simple lines, the definition of an @Breaker annotation class is completed.

Registration of Annotation Class Loader

In the bootstap phase of the framework, swoft scans all PHP source files to obtain and parse annotation information.

The use of Doctrine requires first to provide an automatic class loading method, which directly uses swoft’s current class loader. Swoft’s class loader is automatically generated by Composer, which means that annotation classes can be automatically loaded as long as they conform to PSR-4 specifications.

//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;
        });

        // code code....

    return array_unique($phpClass);
}

Use Doctrine to get annotation objects

After scanning each source directory to obtain PHP classes, Sworft will traverse the class list to load classes and obtain all annotation objects at class level, method level and attribute level. The results are stored in the $annotations member of the AnnotationResource.

//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;
        }
    }
}

Interpretation of Annotations

Doctrine only maps annotations to annotation classes declared with @Annotation. Swoft needs to process the annotation object itself to obtain the information in the annotation. This step has two important functions:

  • All information of the Bean collected by scanning, including the Bean name, class name and attribute information to be injected into the Bean, is stored in the ObjectDefinition array.
//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 will call the relevant Collector to collect the information needed by the function during annotation parsing, such as event registration.

For example, BootstrapParser’s analysis is just to collect annotations. Collector is the final loading container for annotation information in Swoft. Generally speaking, the Parser and Collect corresponding to the @XXXX annotation are XXXXParser and XXXXCollect. knowing this convention will greatly facilitate your reading of the Swoft source code.

//Swoft\Bean\Parser\BootstrapParser.php
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, ""];
    }
}

Swoft does not perform lazyload because the framework must completely obtain various annotations to Collertor and generate Bean definition sets before execution.

Use of annotations

Now we can finally use an example to explain how annotations work. InitMbFunsEncoding is a class that implements Bootable. Its function is to set the system code when the application starts. However, merely implementing the Bootable interface does not allow the framework to call it automatically at startup.
So we need InitMbFunsEncoding to add a@Bootstrap(order=1)Class annotation, making him a Bootstrap Bean.

//Swoft\Bootstrap\Boots.InitMbFunsEncoding.php
<?php

namespace Swoft\Bootstrap\Boots;
use Swoft\Bean\Annotation\Bootstrap;

/**
 * @Bootstrap(order=1)
 */
class InitMbFunsEncoding implements Bootable
{
    /**
     * bootstrap
     */
    public function bootstrap()
    {
        mb_internal_encoding("UTF-8");
    }
}

We mentioned earlier that PHP source code will be scanned when the framework starts

  • Store Bean definition information into ObjectDefinition array
  • Store comment information in each Collector

Therefore, in the Bootstrap phase of the framework, all @Bootstrap Beans can be directly obtained from the BootstrapCollector, instantiated and bean executed.

<?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 ...

The above is the overall implementation of the Swoft annotation mechanism.

Swoft source code analysis series catalog:https://segmentfault.com/a/11 …