swoft2.0 原始碼剖析系列 01-註解器

Ewing發表於2020-02-11

前言

本人認為學習一個框架正確的方式是先看框架的文件,再進入實戰動手寫程式碼寫一些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 協議》,轉載必須註明作者和本文連結

相關文章