swoft2.0 源码剖析系列 01-注解器

swoft2.0源码剖析01-注解器

前言

本人认为学习一个框架正确的方式是先看框架的文档,再进入实战动手写代码写一些demo,然后就是看框架的源码,深入了解框架是怎么启动运行的,整个框架的流程最好用debug跑一遍,看一下有哪些可以值得学习和扩展的地方,比如框架源码中用到的一些设计模式,思考为什么用这个设计模式;比如框架源码中有一些可扩展的方法,是文档中没有提及到的;比如想要写框架的扩展库,必须要深入了解框架源码和运行流程。

下面主要是对swoft这个框架注解器组件方面做一个流程剖析。

swoft注解

注解(Annotations) 是Swoft里面很多重要功能的基础。特别是AOP,IoC容器、事件监听的基础。
注解的定义是: “附加在数据/代码上的元数据(metadata)”
swoft框架可以基于这些元数据为代码提供各种额外功能。

注解 VS 注释

一般而言,在编程届中注解是一种和注释平行的概念。

  • 注释提供对可执行代码的说明,单纯用于开发人员阅读,不影响代码的执行;
  • 而注解往往充当着对代码的声明和配置的作用,为可执行代码提供机器可用的额外信息,在特定的环境下会影响程序的执行;

php注解与swoft注解

目前PHP没有对注解的官方实现,主流的PHP框架中使用的注解都是借用T_DOC_COMMENT型注释块(/**型注释*/)中的@Tag,定义自己的注解机制。

Swoft没有重新造轮子,搞一个新的的注解方案,而是选择使用Doctrine的注解引擎

Doctrine的注解方案也是基于T_DOC_COMMENT型注释的,Doctrine使用反射获取代码的T_DOC_COMMENT型注释,并将注释中的特定类型@Tag映射到对应注解类

如果使用

在过源码流程之前,先说一下作为swoft核心之一的注解器是怎么使用的,就像我们日常开发写注解一样,只需在类、方法或成员变量上方按规则添加注解即可,如定义一个监听器:

namespace SwoftTest\Event;

use Swoft\Event\Annotation\Mapping\Listener;
use Swoft\Event\EventHandlerInterface;
use Swoft\Event\EventInterface;

/**
 * Class SendMessageListener
 *
 * @Listener("order.created")
 */
class SendMessageListener implements EventHandlerInterface
{
    /**
     * @param EventInterface $event
     */
    public function handle(EventInterface $event): void
    {
        $pos = __METHOD__;
        echo "handle the event '{$event->getName()}' on the: $pos\n";
    }
}

和laravel必须要写事件监听类绑定不一样,swoft只需要在类注解写上@Listener这个注解就能将这个类定义为一个监听器,并绑定到具体的事件。
order.created是这个监听类绑定的具体事件,任何地方调用Swoft::trigger("order.created")创建订单事件能触发到这个发送消息的监听器,执行handle()方法。

实现注解

那么怎么使得这些注解附有意义呢?这时候要引出强大的ReflectionClass反射类,通过反射类的getDocComment()方法能获取某个类的类注释,方法和属性的注释。

/**
 * Gets doc comment
 * @link https://php.net/manual/en/reflectionproperty.getdoccomment.php
 * @return string|bool The doc comment if it exists, otherwise <b>FALSE</b>
 * @since 5.1.0
 */
public function getDocComment () {}

不妨设想一下,程序在开始运行的时候(php bin/swoft http start)应该是把全部文件都扫描一遍,通过反射获取类注解,如果类注解为Swoft\Event\Annotation\Mapping\Listener就把该类(SwoftTest\Event\SendMessageListener)和对应的事件名(“order.created”)绑定到事件管理器,然后触发事件就能找到对应的监听器。其实就是1、先识别注解 ==> 再到2、让注解起作用两个步骤。

下面来看一下程序启动的运行流程,可以一边看代码一边看文章,看一下注解是怎么起作用的。

先看命令启动的入口文件 bin/swoft

// Bootstrap
require_once __DIR__ . '/bootstrap.php';

Swoole\Coroutine::set([
    'max_coroutine' => 300000,
]);

// 启动App
(new \App\Application())->run();

主要就是程序的入口,实例化Application,并执行run()方法,我们知道Application是继承与SwoftApplication,Application没有构造方法,自然就会调用父类SwoftApplication的构造方法,父类的构造方法如下:

/**
 * Class constructor.
 *
 * @param array $config
 */
public function __construct(array $config = [])
{
    ......

    // Init application
    $this->init();

    ......
}

前半部分主要是程序初始化的一些校验,日志器的初始化,省略掉,主要是$this->init()初始化方法

protected function init(): void
{
    ......
    $processors = $this->processors();

    $this->processor = new ApplicationProcessor($this);
    $this->processor->addFirstProcessor(...$processors);
}

......

/**
 * swoft 六大处理器
 * @return ProcessorInterface[]
 */
protected function processors(): array
{
    return [
        new EnvProcessor($this), // 处理环境变量
        new ConfigProcessor($this), // 处理配置
        new AnnotationProcessor($this), // 处理注解(本文章重点)
        new BeanProcessor($this), // 处理容器对象
        new EventProcessor($this), // 处理事件
        new ConsoleProcessor($this), // 处理命令
    ];
}

初始化里面主要是把swoft六大处理器添加到应用程序的主处理器ApplicationProcessor中,本文章主要解读的是AnnotationProcessor注解处理器,通过这个处理器实现对类注解的解析。添加处理器后,Application初始化结束。下面再来调用的run()方法。

/**
 * Run application
 */
public function run(): void
{
    try {
        if (!$this->beforeRun()) {
            return;
        }
        $this->processor->handle();
    } catch (Throwable $e) {
        ......
    }
}

run()方法主要执行了应用程序的主处理器ApplicationProcessor的handle()方法,handle()方法把前面添加的六大处理器的handle()方法都执行了一遍(代码省略),下面主要看AnnotationProcessor注解处理器中的handle()方法。

/**
 * Handle annotation
 *
 * @return bool
 * @throws Exception
 */
public function handle(): bool
{
    ......

    $app = $this->application;

    AnnotationRegister::load([
        'inPhar'               => IN_PHAR,
        'basePath'             => $app->getBasePath(),
        'notifyHandler'        => [$this, 'notifyHandle'],
        'disabledAutoLoaders'  => $app->getDisabledAutoLoaders(),
        'disabledPsr4Prefixes' => $app->getDisabledPsr4Prefixes(),
    ]);

    ......
}

注解处理器handle()方法中调用了load方法,AnnotationRegister::load()方法调用了AnnotationResource实例化对象的load()方法,AnnotationResource类用途就是收集整合注解资源的,load方法的主要作用是:

  • 查找目录中的AutoLoader.php文件,如果没有的话就不解析(所以swoft扩展库的src目录必须有AutoLoader.php文件,否则注解器等功能不能生效)。
  • 解析每个目录下每个文件并收集带有解析的类或属性方法。
/**
 * 遍历查找目录中的AutoLoader.php文件,如果没有就不解析,如果有就根据这个文件指定的目录文件解析
 *
 * @throws AnnotationException
 * @throws ReflectionException
 */
public function load(): void
{
    // 获取composer里面autoload的psr-4映射目录,包括了app目录和vendor第三方库的目录
    $prefixDirsPsr4 = $this->classLoader->getPrefixesPsr4();

    foreach ($prefixDirsPsr4 as $ns => $paths) {
        ......

        // 循环每个目录,查找Autoloader.php文件
        foreach ($paths as $path) {
            $loaderFile = $this->getAnnotationClassLoaderFile($path);
            .......
            $loaderClass = $this->getAnnotationLoaderClassName($ns);
            $isEnabled  = true;
            $autoLoader = new $loaderClass();
            .......
            // 如果Autoloader类没有允许加载,则不能加载注解,通过isEnable()控制
            if (isset($this->disabledAutoLoaders[$loaderClass]) || !$autoLoader->isEnable()) {
                $isEnabled = false;

                $this->notify('disabledLoader', $loaderFile);
            } else {
                AnnotationRegister::addAutoLoaderFile($loaderFile);
                $this->notify('addLoaderClass', $loaderClass);

                // 加载并收集注解类(核心)
                $this->loadAnnotation($autoLoader);
            }

            // Storage autoLoader instance to register
            AnnotationRegister::addAutoLoader($ns, $autoLoader, $isEnabled);
        }
    }
}

load()方法已经完成了Autoloader.php文件的发现,这就找到了允许被swoft解析注解的目录,有了目录,接下来就是遍历目录中的每个文件,收集并解析注解,这一核心流程交给了$this->loadAnnotation($autoLoader)方法去实现。

/**
 * 循环解析目录下每个文件的注解
 *
 * @param LoaderInterface $loader
 *
 * @throws AnnotationException
 * @throws ReflectionException
 */
private function loadAnnotation(LoaderInterface $loader): void
{
    // 获取Autoloader类中设置的目录
    $nsPaths = $loader->getPrefixDirs();

    foreach ($nsPaths as $ns => $path) {
        // 迭代生成目录下文件迭代器,然后遍历每个文件
        $iterator = DirectoryHelper::recursiveIterator($path);

        foreach ($iterator as $splFileInfo) {
            ......
            $suffix    = sprintf('.%s', $this->loaderClassSuffix);
            $pathName  = str_replace([$path, '/', $suffix], ['', '\\', ''], $filePath);
            $className = sprintf('%s%s', $ns, $pathName);
            // 解析某个类,查看某个类有没有类注解,方法注解,属性注解等。
            $this->parseAnnotation($ns, $className);
        }
    }
}

/**
 * 解析某个类的注解
 *
 * @param string $namespace
 * @param string $className
 *
 * @throws AnnotationException
 * @throws ReflectionException
 */
private function parseAnnotation(string $namespace, string $className): void
{
    // **核心**:实例化某类的ReflectionClass类,比如说上面的SwoftTest\Event\SendMessageListener类
    $reflectionClass = new ReflectionClass($className);

    // Fix ignore abstract
    if ($reflectionClass->isAbstract()) {
        return;
    }
    // 根据反射类ReflectionClass解析某个类并查找某个类注解,返回了某个类整合完的注解(数组形式)
    $oneClassAnnotation = $this->parseOneClassAnnotation($reflectionClass);

    // 如果某个类整合完的注解不为空,就注册到AnnotationRegister类中
    if (!empty($oneClassAnnotation)) {
        AnnotationRegister::registerAnnotation($namespace, $className, $oneClassAnnotation);
    }
}

核心流程:实例化了某类的ReflectionClass类,比如说上面的SwoftTest\Event\SendMessageListener类,然后把反射类传递给parseOneClassAnnotation()方法去处理。

/**
 * 解析某个类的注解
 *
 * @param ReflectionClass $reflectionClass
 *
 * @return array
 * @throws AnnotationException
 * @throws ReflectionException
 */
private function parseOneClassAnnotation(ReflectionClass $reflectionClass): array
{
    // Annotation reader 注解阅读器
    $reader    = new AnnotationReader();
    $className = $reflectionClass->getName();

    $oneClassAnnotation = [];
    // $reader获取类注解
    $classAnnotations   = $reader->getClassAnnotations($reflectionClass);

    // Register annotation parser 注册注解的解析器,这里的解析器不是getDocComment(),而是把上面例子中监听器和时间绑定的解析器。通常在src\Annotation\Parser目录中。
    foreach ($classAnnotations as $classAnnotation) {
        if ($classAnnotation instanceof AnnotationParser) {
            // * 如果是AnnotationParser解析类,则把该类注册到AnnotationRegister类的$parsers(解析器)属性中,这个解析类后文作用重大,用来让注解真正起作用的,一个注解类对应一个解析类
            $this->registerParser($className, $classAnnotation);

            return [];
        }
    }

    // Class annotation
    if (!empty($classAnnotations)) {
        $oneClassAnnotation['annotation'] = $classAnnotations;
        $oneClassAnnotation['reflection'] = $reflectionClass;
    }

    // 获取类属性 => 遍历类属性 => 获取属性注解
    $reflectionProperties = $reflectionClass->getProperties();
    foreach ($reflectionProperties as $reflectionProperty) {
        $propertyName        = $reflectionProperty->getName();
        // $reader获取属性注解
        $propertyAnnotations = $reader->getPropertyAnnotations($reflectionProperty);

        if (!empty($propertyAnnotations)) {
            $oneClassAnnotation['properties'][$propertyName]['annotation'] = $propertyAnnotations;
            $oneClassAnnotation['properties'][$propertyName]['reflection'] = $reflectionProperty;
        }
    }

    // 获取类方法 => 遍历类方法 => 获取方法注解
    $reflectionMethods = $reflectionClass->getMethods();
    foreach ($reflectionMethods as $reflectionMethod) {
        $methodName        = $reflectionMethod->getName();
        // $reader获取方法注解
        $methodAnnotations = $reader->getMethodAnnotations($reflectionMethod);

        if (!empty($methodAnnotations)) {
            $oneClassAnnotation['methods'][$methodName]['annotation'] = $methodAnnotations;
            $oneClassAnnotation['methods'][$methodName]['reflection'] = $reflectionMethod;
        }
    }

    $parentReflectionClass = $reflectionClass->getParentClass();
    if ($parentReflectionClass !== false) {
        $parentClassAnnotation = $this->parseOneClassAnnotation($parentReflectionClass);
        if (!empty($parentClassAnnotation)) {
            $oneClassAnnotation['parent'] = $parentClassAnnotation;
        }
    }

    return $oneClassAnnotation;
}

AnnotationParser解析类,与注解类一一对应,有Swoft\Event\Annotation\Mapping\Listener注解类就有对应的Swoft\Event\Annotation\Parser\ListenerParser解析类,一般都存放在app或者库src目录下的Annotation目录中。

$reader = new AnnotationReader()注解阅读器是引用了Doctrine注解引擎这个包里面的类。

parseOneClassAnnotation()方法解析某个类的注解,并根据类、属性、方法整合到了$oneClassAnnotation数组中,并返回。AnnotationReader类能解析(类、属性、方法)注解,下面看看这个类是怎么通过getClassAnnotations()方法获取注解的。

/**
 * {@inheritDoc}
 */
public function getClassAnnotations(ReflectionClass $class)
{
    $this->parser->setTarget(Target::TARGET_CLASS);
    $this->parser->setImports($this->getClassImports($class));
    $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
    $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);

    return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
    }

使用ReflectionClass的getDocComment()方法获取类注释,再传递给$this->parser,$this->parser->parse()方法根据@关键字把注解格式化为数组形式,数组里面是具体的注解类实例化对象例如:

/**
 * Class SendMessageListener
 *
 * @since 2.0
 *
 * @Listener(DbEvent::MODEL_SAVED)
 */
 class SendMessageListener{
     ......
 }

经过parser()处理后变成如下形式,由于只有一个注解@Listener,所以数组只有一个元素。

array(1) {
  [0]=>
  object(Swoft\Event\Annotation\Mapping\Listener)#1479 (2) {
    ["event":"Swoft\Event\Annotation\Mapping\Listener":private]=>
    string(17) "swoft.model.saved"
    ["priority":"Swoft\Event\Annotation\Mapping\Listener":private]=>
    int(0)
  }
}

同样的方式获取属性的注解,方法的注解,并组装成$oneClassAnnotation数组,然后通过AnnotationRegister::registerAnnotation()方法,将类名(例中的SwoftTest\Event\SendMessageListener)、命名空间、和生成的注解数组$oneClassAnnotation注册到然后通过AnnotationRegister类的$annotations属性中。

整个目录文件扫描的流程完成后,最终会把所有的注解(格式化后 => 注解类实例化对象)添加在AnnotationRegister类的$annotations属性中。

/**
 * Class AnnotationRegister
 *
 * @since 2.0.0
 */
final class AnnotationRegister
{
    /**
     * @var array
     *
     * @example
     * [
     *        // 命名空间 SwoftTest\Event
     *    'loadNamespace' => [
     *           // 类名,例子中的SwoftTest\Event\SendMessageListener
     *        'className' => [
     *                // 类注解
     *             'annotation' => [
     *                     // 例子中的Swoft\Event\Annotation\Mapping\Listener注解类对象
     *                  new ClassAnnotation(),
     *                  new ClassAnnotation(),
     *                  new ClassAnnotation(),
     *             ]
     *             'reflection' => new ReflectionClass(),
     *             // 属性注解
     *             'properties' => [
     *                  'propertyName' => [
     *                      'annotation' => [
     *                          new PropertyAnnotation(),
     *                          new PropertyAnnotation(),
     *                          new PropertyAnnotation(),
     *                      ]
     *                     'reflection' => new ReflectionProperty(),
     *                  ]
     *             ],
     *             // 方法注解
     *            'methods' => [
     *                  'methodName' => [
     *                      'annotation' => [
     *                          new MethodAnnotation(),
     *                          new MethodAnnotation(),
     *                          new MethodAnnotation(),
     *                      ]
     *                     'reflection' => new ReflectionFunctionAbstract(),
     *                  ]
     *            ]
     *        ]
     *    ]
     * ]
     */
    private static $annotations = [];
}

一般来说,每个不同的注解类都会有不同的属性,比如Swoft\Event\Annotation\Mapping\Listener注解类就保存了事件名和优先级属性,而Swoft\Bean\Annotation\Mapping\Bean这个注解类就保存了名称、作用域、别名等属性,这些属性时在解析类中获取的,解析类的作用下文说。

至此,AnnotationProcessor任务完成。但是有个疑问的是,上面的流程只是把注解格式化出来(步骤1识别注解),具体怎么样让注解起作用好像是还没有涉及到,那得继续看下一个处理器BeanProcessor的handle()方法了。

先说一下BeanProcessor处理器的功能,这个处理器的功能如下:

  • Bean 就是一个类的一个对象实例。 容器Container就是一个巨大的工厂,用于存放和管理 Bean 生命周期。所以这个处理器是用来生成Bean并放入容器中的。
  • 把app目录下的bean.php文件的数组AutoLoader的beans()方法返回的数组合并再实例化后放入容器Container中。
  • 把用了@Bean注解的类实例化后放入Container中,所以必须要让注解起作用后才能进行Bean的实例化。
/**
 * Class BeanProcessor
 *
 * @since 2.0
 */
class BeanProcessor extends Processor
{
    /**
     * Handle bean
     *
     * @return bool
     * @throws ReflectionException
     * @throws AnnotationException
     */
    public function handle(): bool
    {
        ......

        $handler     = new BeanHandler();
        // 获取bean.php文件的数组和AutoLoader的beans()方法返回的数组
        $definitions = $this->getDefinitions();
        // 获取$parsers(解析类)
        $parsers     = AnnotationRegister::getParsers();
        // 获取$annotations(所有格式化后注解的结果,注解类)
        $annotations = AnnotationRegister::getAnnotations();

        // 把上面获取到的都添加到BeanFactory的属性中
        BeanFactory::addDefinitions($definitions);
        BeanFactory::addAnnotations($annotations);
        BeanFactory::addParsers($parsers);
        BeanFactory::setHandler($handler);
        // Bean工厂初始化
        BeanFactory::init();

        ......
    }
    ......
}

/**
 * Class BeanFactory
 *
 * @since 2.0
 */
class BeanFactory
{
    /**
     * Init bean container
     *
     * @return void
     * @throws AnnotationException
     * @throws ReflectionException
     */
    public static function init(): void
    {
        // 调用容器的初始化方法
        Container::getInstance()->init();
    }
    ......
}

/**
 * Class Container
 */
class Container implements ContainerInterface
{

    /**
     * Init
     *
     * @throws AnnotationException
     * @throws ReflectionException
     */
    public function init(): void
    {
        // 解析注解
        $this->parseAnnotations();

        // 解析Bean对象
        $this->parseDefinitions();

        // 实例化Bean对象
        $this->initializeBeans();
    }
}

我们重点看$this->parseAnnotations()这个方法,这个方法是解析注解类的,让注解起作用的(步骤2),下面的是解析和实例化Bean,和注解类怎么起作用的无关,就不理它了。至于为什么把解析注解类的流程放在BeanProcessor处理器上而不放在AnnotationProcessor处理器上,可能是解析类处理完要返回一个结果提供给Bean,为什么这么做,猜想是要兼容很多解析类有关吧,具体我也不太明白,继续看代码。

/**
 * Class Container
 */
class Container implements ContainerInterface
{

    /**
     * Parse annotations
     *
     * @throws AnnotationException
     */
    private function parseAnnotations(): void
    {
        $annotationParser = new AnnotationObjParser(
            $this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases
        );
        // 实例化AnnotationObjParser对象,用这个对象去解析注解类
        $annotationData  = $annotationParser->parseAnnotations($this->annotations, $this->parsers);

        [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases] = $annotationData;
    }
}


/**
 * Class AnnotationParser
 *
 * @since 2.0
 */
class AnnotationObjParser extends ObjectParser
{
    /**
     * Parse annotations
     *
     * @param array $annotations
     * @param array $parsers
     *
     * @return array
     * @throws AnnotationException
     */
    public function parseAnnotations(array $annotations, array $parsers): array
    {
        $this->parsers     = $parsers;
        $this->annotations = $annotations;

        foreach ($this->annotations as $loadNameSpace => $classes) {
            foreach ($classes as $className => $classOneAnnotations) {
                $this->parseOneClassAnnotations($className, $classOneAnnotations);
            }
        }

        return [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases];
    }
}

AnnotationObjParser类的parseAnnotations方法循环了所有的注解$annotations,调用了parseOneClassannotations(),并且把类名$className(SwoftTest\Event\SendMessageListener)和这个类的所有注解类$classOneAnnotations (包括类注解,方法注解,属性注解)作为参数传递给了这个方法。

/**
 * 解析某类的所有注解
 *
 * @param string $className
 * @param array  $classOneAnnotations
 *
 * @throws AnnotationException
 */
private function parseOneClassAnnotations(string $className, array $classOneAnnotations): void
{
    ......

    // Parse class annotations
    $classAnnotations = $classOneAnnotations['annotation'];
    $reflectionClass  = $classOneAnnotations['reflection'];

    $classAry = [
        $className,
        $reflectionClass,
        $classAnnotations
    ];

    // 解析类注解
    $objectDefinition = $this->parseClassAnnotations($classAry);

    // 解析属性注解
    $propertyInjects        = [];
    $propertyAllAnnotations = $classOneAnnotations['properties'] ?? [];
    foreach ($propertyAllAnnotations as $propertyName => $propertyOneAnnotations) {
        $proAnnotations = $propertyOneAnnotations['annotation'] ?? [];
        $propertyInject = $this->parsePropertyAnnotations($classAry, $propertyName, $proAnnotations);
        if ($propertyInject) {
            $propertyInjects[$propertyName] = $propertyInject;
        }
    }

    // 解析方法注解
    $methodInjects        = [];
    $methodAllAnnotations = $classOneAnnotations['methods'] ?? [];
    foreach ($methodAllAnnotations as $methodName => $methodOneAnnotations) {
        $methodAnnotations = $methodOneAnnotations['annotation'] ?? [];

        $methodInject = $this->parseMethodAnnotations($classAry, $methodName, $methodAnnotations);
        if ($methodInject) {
            $methodInjects[$methodName] = $methodInject;
        }
    }

    ......
}

我们只看怎么解析类注解$this->parseClassAnnotations($classAry)

/**
 * @param array $classAry
 *
 * @return ObjectDefinition|null
 */
private function parseClassAnnotations(array $classAry): ?ObjectDefinition
{
    [, , $classAnnotations] = $classAry;

    $objectDefinition = null;
    foreach ($classAnnotations as $annotation) {
        $annotationClass = get_class($annotation);
        if (!isset($this->parsers[$annotationClass])) {
            continue;
        }

        // 去解析类数组里面根据注解类名找到对应的解析类名(前面说了注解类和解析类一一对应的)
        $parserClassName  = $this->parsers[$annotationClass];
        // 根据解析类名获取示例化后的解析类(例子中是Swoft\Event\Annotation\Parser\ListenerParser)
        $annotationParser = $this->getAnnotationParser($classAry, $parserClassName);
        // 调用解析类的parse()
        $data = $annotationParser->parse(Parser::TYPE_CLASS, $annotation);

        ......
    }

    return $objectDefinition;
}

调用解析类的parse()方法,例子中就是调用Swoft\Event\Annotation\Parser\ListenerParser这个解析类的parse()方法,这个方法就是实现了步骤2让注解起作用,具体看一下parse()方法。

class ListenerParser extends Parser
{
    /**
     * @param int      $type
     * @param Listener $annotation
     *
     * @return array
     * @throws AnnotationException (注解类,例子中的Swoft\Event\Annotation\Mapping\Listener)
     */
    public function parse(int $type, $annotation): array
    {
        if ($type !== self::TYPE_CLASS) {
            throw new AnnotationException('`@Listener` must be defined on class!');
        }

        // 注册监听器,key为为SwoftTest\Event\SendMessageListener,值为["order.created" => 0]
        ListenerRegister::addListener($this->className, [
            // event name => listener priority
            $annotation->getEvent() => $annotation->getPriority()
        ]);

        return [$this->className, $this->className, Bean::SINGLETON, ''];
    }
}

ListenerParser类的parse()方法把注解类(Swoft\Event\Annotation\Mapping\Listener)实例化对象中保存的事件名和优先级注册到了ListenerRegister类的$listeners属性中,从而使得SwoftTest\Event\SendMessageListener和”order.created”绑定了关系,后续程序中触发了order.created事件就能找到对应的监听器了。

通过识别注解 ==> 到解析注解这么一个流程,类注解成功绑定了事件和监听器了。

自定义注解

swoft框架的注解流程讲解完了,如果要自定义一个注解怎么做应该也清晰多了
主要是编写注解类和解析类:
注解类写在App\Annotation\Mapping目录,比如编写要编写一个门面Facades的注解类App\Annotation\Mapping\FacadesAnnotation类,让类变成门面类(不了解门面的可以看一下laravel门面的文档)。

namespace App\Annotation\Mapping;

use Doctrine\Common\Annotations\Annotation\Attribute;
use Doctrine\Common\Annotations\Annotation\Attributes;

/**
 * Class Facades
 *
 * @since 2.0
 *
 * @Annotation  //声明这是一个注解类
 * @Target("CLASS") //声明这个注解只可用在class级别的注释中
 * @Attributes({
 *     @Attribute("alias",type="string")
 * })
 */
class Facades
{
    /**
     * @var string
     */
    private $alias = '';

    /**
     * StringType constructor.
     *
     * @param array $values
     */
    public function __construct(array $values)
    {
        if (isset($values['value'])) {
            $this->message = $values['value'];
        }
        if (isset($values['alias'])) {
            $this->alias = $values['alias'];
        }
    }

    /**
     * @return string
     */
    public function getAlias(): string
    {
        return $this->alias;
    }
}

在程序任何有Autoload.php文件的目录下的类都能在类注解上按格式写上自定义的注解类(记得要use 注解类,phpstorm有注解的插件可以直接引入)比如:

use App\Annotation\Mapping\Facades

/**
 * Class Calculate
 *
 * @Facades()
 */
 class Calculate{

    /**
     * 执行
     *
     * @return mixed
     * @throws Exception
     */
    public function execute()
    {
        ......
    }
 }

解析类写在App\Annotation\Parser目录下,编写App\Annotation\Parser\FacadesParser类:

<?php declare(strict_types=1);
/**
 * This file is part of Swoft.
 *
 * @link     https://swoft.org
 * @document https://swoft.org/docs
 * @contact  group@swoft.org
 * @license  https://github.com/swoft-cloud/swoft/blob/master/LICENSE
 */

namespace App\Annotation\Parser;

use ReflectionException;
use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
use Swoft\Annotation\Annotation\Parser\Parser;
use App\Annotation\Mapping\Facades;
use Swoft\Validator\Exception\ValidatorException;
use Swoft\Validator\ValidatorRegister;

/**
 * Class FacadesParser
 *
 * @AnnotationParser(annotation=Facades::class) // 参数值写上注解类
 */
class FacadesParser extends Parser
{
    /**
     * @param int $type
     * @param object $annotationObject
     *
     * @return array
     * @throws ReflectionException
     * @throws ValidatorException
     */
    public function parse(int $type, $annotationObject): array
    {
        // 可以获取到目标类Calculate,用$this->className获取
        // 可以获取到注解类对象$annotationObject
        // 这里是把目标类Calculate怎么变成门面的流程,我也没有实现,有兴趣的可以自己写一个
        return [];
    }
}

以上就是Swoft注解器的源码流程剖析。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 1

swoft新版本 已经不用swoole了,而且维护也一直不积极

4年前 评论
L学习不停 4年前
_杭城浪子 (作者) 4年前
Ewing (楼主) 4年前
L学习不停 4年前
_杭城浪子 (作者) 4年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!