### 2019 年 4 月 21 日 发布 在最新的`6.0`版本中引入了新的事件系统用以替代`5.1`版本的行为,同时也接管了数据库事件和模型事件。 本篇主要描述下新版的事件系统以及查询事件、模型事件的使用。 ## 定义事件 >[danger] 事件系统的所有操作都通过`think\facade\Event`类进行静态调用 事件系统使用了观察者模式,提供了解耦应用的更好方式。在你需要监听事件的位置,添加如下代码: ``` Event::trigger('UserLogin'); ``` 或者使用助手函数 ``` event('UserLogin'); ``` 这里`UserLogin`表示一个事件标识,如果你定义了单独的事件类,你可以使用事件类名,甚至可以传入一个事件类实例。 ``` event('app\event\UserLogin'); ``` 事件类可以通过命令行快速生成 ``` php think make:event UserLogin ``` 默认会生成一个`app\event\UserLogin`事件类,也可以指定完整类名生成。 我们可以给事件类添加方法 ``` namespace app\event; use app\model\User; class UserLogin { public $user; public function __construct(User $user) { $this->user = $user; } } ``` 一般事件类无需继承任何其它类。 你可以给事件类绑定一个事件标识 ``` Event::bind('UserLogin', 'app\event\UserLogin'); ``` 或者在应用的`event.php`事件定义文件中批量绑定。 ``` return [ 'bind' => [ 'UserLogin' => 'app\event\UserLogin', // 更多事件绑定 ], ]; ``` 如果你没有定义事件类的话,则无需绑定。 >[info] ThinkPHP的事件系统不依赖事件类,如果没有额外的需求,仅通过事件标识也可以使用。 你可以在`event`方法中传入一个事件参数 ``` event('UserLogin', $user); ``` ## 事件监听 你可以手动注册一个事件监听 ``` Event::listen('UserLogin', function($user) { // }); ``` 或者使用监听类 ``` Event::listen('UserLogin', 'app\listener\UserLogin'); ``` 可以通过命令行快速生成一个事件监听类 ``` php think make:listener UserLogin ``` 默认会生成一个`app\listener\UserLogin`事件监听类,也可以指定完整类名生成。 事件监听类只需要定义一个`handler`方法,支持依赖注入。 ~~~ <?php namespace app\listener; class UserLogin { public function handle($user) { // 事件监听处理 } } ~~~ 在`handler`方法中如果返回了`false`,则表示监听中止,将不再执行该事件后面的监听。 一般建议直接在事件定义文件中定义对应事件的监听。 ``` return [ 'bind' => [ 'UserLogin' => 'app\event\UserLogin', // 更多事件绑定 ], 'listen' => [ 'UserLogin' => ['\app\listener\UserLogin'], // 更多事件监听 ], ]; ``` ## 事件订阅 可以通过事件订阅机制,在一个监听器中监听多个事件,例如通过命令行生成一个事件订阅者类, ``` php think make:subscribe User ``` 默认会生成`app\subscribe\User`类,或者你可以指定完整类名生成。 然后你可以在事件订阅类中添加不同事件的监听方法,例如。 ~~~ <?php namespace app\subscribe; class User { public function onUserLogin($user) { // 事件响应处理 } public function onUserLogout($user) { // 事件响应处理 } } ~~~ 监听事件的方法命名规范是`on`+事件标识(驼峰命名),然后注册该事件订阅 ``` Event::subscribe('app\subscribe\User'); ``` 一般建议直接在事件定义文件中定义 ``` return [ 'bind' => [ 'UserLogin' => 'app\event\UserLogin', // 更多事件绑定 ], 'listen' => [ 'UserLogin' => ['\app\listener\UserLogin'], // 更多事件监听 ], 'subscribe' => [ '\app\subscribe\User', // 更多事件订阅 ], ]; ``` ## 内置事件 内置的系统事件包括: | 事件| 描述 | 参数 | | --- | --- | --- | | AppInit | 应用初始化标签位 | 无 | | HttpRun | 应用开始标签位 | 无 | | HttpEnd | 应用结束标签位 | 当前响应对象实例 | | LogWrite | 日志write方法标签位 | 当前写入的日志信息 | | LogLevel | 日志写入标签位 | 包含日志类型和日志信息的数组 | >[danger] `AppInit`事件定义必须在全局事件定义文件中定义,其它事件支持在应用的事件定义文件中定义。 原来`5.1`的一些行为标签已经废弃,所有取消的标签都可以使用中间件更好的替代。可以把中间件看成处理请求以及响应输出相关的特殊事件。事实上,中间件的`handle`方法只是具有特殊的参数以及返回值而已。 ## 查询事件 数据库操作的回调也称为查询事件,是针对数据库的CURD操作而设计的回调方法,主要包括: | 事件 | 描述 | | --- | --- | | before\_select | `select`查询前回调 | | before\_find | `find`查询前回调 | | after\_insert | `insert`操作成功后回调 | | after\_update | `update`操作成功后回调 | | after\_delete | `delete`操作成功后回调 | 使用下面的方法注册数据库查询事件 ~~~ \think\facade\Db::event('before_select', function ($query) { // 事件处理 return $result; }); ~~~ 同一个查询事件可以注册多个响应执行。查询事件在新版里面也已经被事件系统接管了,因此如果你注册了一个`before_select`查询事件监听,底层其实是向标识为`db.before_select`的事件注册了一个监听。 >[danger] 查询事件的方法参数只有一个:当前的查询对象。但你可以通过依赖注入的方式添加额外的参数。 ## 模型事件 模型事件是指在进行模型的查询和写入操作的时候触发的操作行为。 >[danger] 模型事件只在调用模型的方法生效,使用查询构造器操作是无效的 模型支持如下事件: |事件|描述|事件方法名| |---|---|---| |after_read | 查询后 |onAfterRead| |before_insert | 新增前 |onBeforeInsert| |after_insert | 新增后 |onAfterInsert| |before_update | 更新前 |onBeforeUpdate| |after_update| 更新后 |onAfterUpdate| |before_write| 写入前 |onBeforeWrite| |after_write | 写入后 |onAfterWrite| |before_delete | 删除前 |onBeforeDelete| |after_delete | 删除后 |onAfterDelete| |before_restore | 恢复前 |onBeforeRestore| |after_restore | 恢复后 |onAfterRestore| 注册的回调方法支持传入一个参数(当前的模型对象实例),但支持依赖注入的方式增加额外参数。 >[info] 如果`before_write`、`before_insert`、 `before_update` 、`before_delete`事件方法中返回`false`或者抛出`think\exception\ModelEventException`异常的话,则不会继续执行后续的操作。 ### 模型事件定义 最简单的方式是在模型类里面定义静态方法来定义模型的相关事件响应。 ~~~ <?php namespace app\index\model; use think\Model; use app\index\model\Profile; class User extends Model { public static function onBeforeUpdate($user) { if ('thinkphp' == $user->name) { return false; } } public static function onAfterDelete($user) { Profile::destroy($user->id); } } ~~~ 参数是当前的模型对象实例,支持使用依赖注入传入更多的参数。 ### 模型事件观察者 如果希望模型的事件单独管理,可以给模型注册一个事件观察者,例如: ~~~ <?php namespace app\index\model; use think\Model; class User extends Model { protected $observerClass = 'app\index\observer\User'; } ~~~ `User`观察者类定义如下: ~~~ <?php namespace app\index\observer; use app\index\model\Profile; class User { public function onBeforeUpdate($user) { if ('thinkphp' == $user->name) { return false; } } public function onAfterDelete($user) { Profile::destroy($user->id); } } ~~~ >[info] 观察者类的事件响应方法的第一个参数就是模型对象实例,你依然可以通过依赖注入传入其它的对象参数。