# 事件和注解

# 注解事件概念

我们知道事件,是一个底层的 event loop 收到消息后调用对应的各类方法的一个模型,比如给机器人发送消息后框架要做的就是指定到一个你定义的函数上,处理你的业务逻辑代码。比如在默认模块中,提供了 你好 的回复:** 你好啊,我是由炸毛框架构建的机器人!**。这项简单回复的任务就是一个事件的触发到响应的全过程。

注解(Annotation)又称标注,Java 最早在 2004 年的 JDK 5 中引入的一种注释机制。目前 PHP 官方版本并未提供内置元注解和注解概念,但我们通过 ReflectionClass 反射类解析 PHP 代码注释从而实现了自己的一套注解机制。如果你没有写过 Java,并且不了解注解是什么,你可以理解为对 function 或 class 的一个修饰,因为传统的 PHP 代码逻辑我们都知道,不能简单给原先存在的函数贴标签,就比如,你不能在原本的 PHP 代码中给函数贴上一个可以影响它一生并且改变它行为的标签,而有了注解,就相当于有了给函数贴标签的机会。

在常见框架如 Spring,Swoft 等代码结构里面,注解更是其核心的存在。

在炸毛框架中,我们所有事件的绑定均采用这一方式进行调用模块内各个方法。包括 Swoole 自身的框架启动事件、WebSocket 连接握手事件、HTTP 请求事件等等,也包括 CQHTTP 发来的事件,如messagenoticerequest 等。

# 如何使用注解

就像我们日常开发写注释一样,只需在类、方法或成员变量上方按规则添加注释即可,这里以默认自带的 Hello 模块类为例子:

<?php
namespace Module\Example;
use ZM\Annotation\CQ\CQCommand;
class Hello {
    /**
     * @CQCommand(match="你好")
     * @return string
     */
    public function hello(){
        return "你好啊,我是由炸毛框架构建的机器人!";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

其中 @CQCommand() 就是一个基本的注解应用。注意需引入相关注解(Annotation)类,且必须/** 开始并以 */ 结束,否则会导致无法解析!上方 @return 为 IDE 自动生成的 PHPDoc,不需要管。

有什么用?大有妙用!这个例子内注解类的用途是收到 QQ 消息后如果消息第一个词匹配到 你好 的话,框架就会自动处理,最终执行调用此 hello() 方法。注意 CQCommand 和其他任何后面讲到的注解类一样,需先 use ZM\Annotation\ 下的对应注解类,否则也不能正常使用。

# 基本语法

先 use!先 use!先 use!重要的事情说三遍!use ZM\Annotation\xxxx;

必须/** 开始并以 */ 结束。

@注解类名(参数名1="参数1的值"[,参数名2="参数2的值"])
1

对于只使用或只有一个参数的注解类,@注解类名("参数的值") 可以省略参数名。

对于没有参数的注解类,@参数名() 直接使用即可。

# PHP8 原生 Attribute 使用

在 2.7.0 版本更新后,框架已经完全支持使用原生 Attribute 替代此前的 Annotation。如果你使用 PHP8.0 或以上版本,即可使用 Attribute 以取得最佳的原生编程体验。两者的参数和效果完全一致。

@CQCommand 为例,在 PHP8 中,你可以使用以下代码达成相同的效果。

#[CQCommand('帮助', alias=['帮助列表', '菜单'])]
public function help() {
1
2

由于两者几乎完全一致,文档将不会加以区分,统称为注解。

关于原生注解的更多信息,请查阅官方文档 (opens new window)

# 注解和事件的关系

在炸毛框架里,注解常常被当作事件分发的一个重要角色,但注解本身又不是事件,更恰当的说,是注解代表了事件。

机器人开发过程中常见的 @CQCommand,或者是 HTTP 服务器路由绑定 @RequestMapping 都是相当于由对应注解代表了事件,而 @Middleware@Closed 等这类注解显然不代表任何事件,只能当作这个函数或类的修饰属性而已。代表了事件的注解,我们称之为注解事件,它会在某种事件达成条件后触发注解下方的函数本身。

值得注意的是,注解事件本身概念是我凭空捏造的,我不好解释所以只能创造这么一个词来代指这一抽象的概念,硬要解释的话,大致就好比一个社区里有一个卖牛奶的,有几家人订阅了每日上门送牛奶的服务,只要你打了“给我配送牛奶”的注解,他就会上门。而它送的不止一种奶,可以给你个性化定制,比如让卖牛奶的给你带包糖带瓶水,而描述这个的注解就只能做一个之前注解的修饰。假设你只写了带包糖的注解,没有写给我配送牛奶的注解,那他永远也不会给你送牛奶和糖过来。

# 阻断事件

由于炸毛框架内的注解事件统一由一个通用的事件分发器进行分发,所以你在任何注解事件内都可以用通用的方式阻断当前正在运行的事件。

首先就是要记得先 use 事件分发器的类:use ZM\Event\EventDispatcher;

EventDispatcher::interrupt();
EventDispatcher::interrupt($data); // 也可以带返回值,自定义注解事件时有用。
1
2