# 注解 注解是 Hyperf 非常强大的一项功能,可以通过注解的形式减少很多的配置,以及实现很多非常方便的功能。 ## 概念 ### 什么是注解什么是注释? 在解释注解之前我们需要先定义一下 `注解` 与 `注释` 的区别: - 注释:给程序员看,帮助理解代码,对代码起到解释、说明的作用。 - 注解:给应用程序看,用于元数据的定义,单独使用时没有任何作用,需配合应用程序对其元数据进行利用才有作用。 ### 注释型注解解析如何实现? Hyperf 使用了 [doctrine/annotations](https://github.com/doctrine/annotations) 包来对代码内的注解进行解析,注解必须写在下面示例的标准注释块才能被正确解析,其它格式均不能被正确解析。 注释块示例: ```php /** * @AnnotationClass() */ ``` 在标准注释块内通过书写 `@AnnotationClass()` 这样的语法即表明对当前注释块所在位置的对象(类、类方法、类属性)进行了注解的定义, `AnnotationClass` 对应的是一个 `注解类` 的类名,可写全类的命名空间,亦可只写类名,但需要在当前类 `use` 该注解类以确保能够根据命名空间找到正确的注解类。 ### 注解是如何发挥作用的? 我们有说到注解只是元数据的定义,需配合应用程序才能发挥作用,在 Hyperf 里,注解内的数据会被收集到 `Hyperf\Di\Annotation\AnnotationCollector` 类供应用程序使用,当然根据您的实际情况,也可以收集到您自定义的类去,随后在这些注解本身希望发挥作用的地方对已收集的注解元数据进行读取和利用,以达到期望的功能实现。 ### 忽略某些注解 在一些情况下我们可能希望忽略某些 注解,比如我们在接入一些自动生成文档的工具时,有不少工具都是通过注解的形式去定义文档的相关结构内容的,而这些注解可能并不符合 Hyperf 的使用方式,我们可以通过在 `config/autoload/annotations.php` 内将相关注解设置为忽略。 ```php return [ 'scan' => [ // ignore_annotations 数组内的注解都会被注解扫描器忽略 'ignore_annotations' => [ 'mixin', ], ], ]; ``` ## 使用注解 注解一共有 3 种应用对象,分别是 `类`、`类方法` 和 `类属性`。 ### 使用类注解 类注解定义是在 `class` 关键词上方的注释块内,比如常用的 `@Controller` 和 `@AutoController` 就是类注解的使用典范,下面的代码示例则为一个正确使用类注解的示例,表明 `@ClassAnnotation` 注解应用于 `Foo` 类。 - PHP 版本低于 8.0 时 ```php 注意注解类的 `@Annotation` 和 `@Target` 注解为全局注解,无需 `use` 其中 `@Target` 有如下参数: - `METHOD` 注解允许定义在类方法上 - `PROPERTY` 注解允许定义在类属性上 - `CLASS` 注解允许定义在类上 - `ALL` 注解允许定义在任何地方 - PHP 版本大于等于 8.0 时 ```php [ 'scan' => [ 'collectors' => [ CustomCollector::class, ], ], ], ]; ``` ### 原生注解(Attributes) 众所周知,在 PHP 8 增加了 `原生注解(Attributes)` 的特性,Hyperf 2.2 版本开始也支持了原生注解的写法,文档内的所有注解都可做对应的转换,如下: #### PHPDoc 注解 ```php /** * @ClassAnnotation() */ class Foo {} ``` #### PHP 原生注解 ```php #[ClassAnnotation()] class Foo {} ``` ### 利用注解数据 在没有自定义注解收集方法时,默认会将注解的元数据统一收集在 `Hyperf\Di\Annotation\AnnotationCollector` 类内,通过该类的静态方法可以方便的获取对应的元数据用于逻辑判断或实现。 ### ClassMap 功能 框架提供了 `class_map` 配置,可以方便用户直接替换需要加载的类。 比如以下我们实现一个可以自动复制协程上下文的功能: 首先,我们实现一个用于复制上下文的 `Coroutine` 类。其中 `create()` 方法,可以将父类的上下文复制到子类当中。 为了避免命名冲突,约定使用 `class_map` 做为文件夹名,后跟要替换的命名空间的文件夹及文件。 如: `class_map/Hyperf/Utils/Coroutine.php` ```php container = $container; $this->logger = $container->get(StdoutLoggerInterface::class); if ($container->has(FormatterInterface::class)) { $this->formatter = $container->get(FormatterInterface::class); } } /** * @return int Returns the coroutine ID of the coroutine just created. * Returns -1 when coroutine create failed. */ public function create(callable $callable): int { $id = Utils\Coroutine::id(); $result = SwooleCoroutine::create(function () use ($callable, $id) { try { // 按需复制,禁止复制 Socket,不然会导致 Socket 跨协程调用从而报错。 Utils\Context::copy($id, [ ServerRequestInterface::class, ]); call($callable); } catch (Throwable $throwable) { if ($this->formatter) { $this->logger->warning($this->formatter->format($throwable)); } else { $this->logger->warning((string) $throwable); } } }); return is_int($result) ? $result : -1; } } ``` 然后,我们实现一个跟 `Hyperf\Utils\Coroutine` 一模一样的对象。其中 `create()` 方法替换成我们上述实现的方法。 `class_map/Hyperf/Utils/Coroutine.php` ```php get(Co::class)->create($callable); } public static function inCoroutine(): bool { return Coroutine::id() > 0; } } ``` 然后配置一下 `class_map`,如下: ```php [ 'paths' => [ BASE_PATH . '/app', ], 'ignore_annotations' => [ 'mixin', ], 'class_map' => [ // 需要映射的类名 => 类所在的文件地址 Coroutine::class => BASE_PATH . '/class_map/Hyperf/Utils/Coroutine.php', ], ], ]; ``` 这样 `co()` 和 `parallel()` 等方法,就可以自动拿到父协程,上下文中的数据,比如 `Request`。 ## IDE 注解插件 因为 `PHP8.0` 以下并不是原生支持 `注解`,所以 `IDE` 不会默认增加注解支持。但我们可以添加第三方插件,来让 `IDE` 支持 `注解`。 ### PhpStorm 我们到 `Plugins` 中搜索 `PHP Annotations`,就可以找到对应的组件 [PHP Annotations](https://github.com/Haehnchen/idea-php-annotation-plugin)。然后安装组件,重启 `PhpStorm`,就可以愉快的使用注解功能了,主要提供了为注解类增加自动跳转和代码提醒支持,使用注解时自动引用注解对应的命名空间等非常便捷有用的功能。