Yii2 事件监听使用详解
Yii2 事件监听使用详解
一、Yii2 事件系统概述
Yii2 的事件系统基于观察者模式,允许你在特定时刻执行自定义代码。主要组件包括:
- 事件 (Event) - 触发动作的信号
- 事件处理器 (Event Handler) - 响应事件的代码
- 事件监听器 (Event Listener) - 将处理器附加到事件的机制
二、基本使用案例
案例1:类级别事件
// 自定义事件类
namespace app\events;
use yii\base\Event;
class UserSignupEvent extends Event
{
public $user;
public $timestamp;
public $ip;
}
// 触发事件的类
namespace app\models;
use yii\base\Component;
use app\events\UserSignupEvent;
class User extends Component
{
const EVENT_AFTER_SIGNUP = 'afterSignup';
public function signup()
{
// 注册逻辑...
$this->username = 'john_doe';
$this->save();
// 触发事件
$event = new UserSignupEvent();
$event->user = $this;
$event->timestamp = time();
$event->ip = Yii::$app->request->userIP;
$this->trigger(self::EVENT_AFTER_SIGNUP, $event);
}
}
// 监听器配置 - 在配置文件中
return [
// ...
'components' => [
'user' => [
'class' => 'app\models\User',
'on afterSignup' => function ($event) {
// 发送欢迎邮件
Yii::$app->mailer->compose()
->setTo($event->user->email)
->setSubject('Welcome!')
->send();
// 记录日志
Yii::info("User {$event->user->id} signed up from IP: {$event->ip}");
},
],
],
];
案例2:全局事件(使用事件触发器)
// 在 bootstrap 或配置文件中绑定全局事件
Yii::$app->on('app.start', function ($event) {
echo "Application started!";
});
// 在适当的地方触发全局事件
Yii::$app->trigger('app.start');
三、多种监听方式
方式1:匿名函数
$user = new User();
// 绑定匿名函数
$user->on(User::EVENT_AFTER_SIGNUP, function ($event) {
// 事件处理逻辑
echo "User {$event->user->username} signed up!";
});
// 触发事件
$user->signup();
方式2:对象方法
class SignupNotifier
{
public function sendWelcomeEmail($event)
{
$user = $event->user;
// 发送邮件逻辑
echo "Sending welcome email to {$user->email}";
}
public function logSignup($event)
{
Yii::info("New user: {$event->user->username}");
}
}
// 使用对象方法作为处理器
$notifier = new SignupNotifier();
$user->on(User::EVENT_AFTER_SIGNUP, [$notifier, 'sendWelcomeEmail']);
$user->on(User::EVENT_AFTER_SIGNUP, [$notifier, 'logSignup']);
方式3:静态类方法
class SignupHandler
{
public static function handleSignup($event)
{
// 静态方法处理事件
$user = $event->user;
// 处理逻辑
}
}
// 绑定静态方法
$user->on(User::EVENT_AFTER_SIGNUP, ['app\handlers\SignupHandler', 'handleSignup']);
方式4:配置文件中配置
// config/web.php
return [
'components' => [
'db' => [
'class' => 'yii\db\Connection',
'on afterOpen' => function ($event) {
// 数据库连接后执行
$event->sender->createCommand("SET time_zone = '+08:00'")->execute();
},
],
'response' => [
'class' => 'yii\web\Response',
'on beforeSend' => function ($event) {
$response = $event->sender;
// 统一格式化响应
if ($response->format === 'json') {
$response->data = [
'success' => $response->statusCode === 200,
'data' => $response->data,
];
}
},
],
],
'on beforeRequest' => function ($event) {
// 应用请求前处理
Yii::info("Request started: " . Yii::$app->request->url);
},
];
四、高级用法案例
案例1:模型事件监听
// 在 ActiveRecord 模型中监听事件
namespace app\models;
use yii\db\ActiveRecord;
class Product extends ActiveRecord
{
public function init()
{
parent::init();
// 监听 ActiveRecord 内置事件
$this->on(self::EVENT_BEFORE_INSERT, [$this, 'beforeInsertHandler']);
$this->on(self::EVENT_AFTER_UPDATE, [$this, 'afterUpdateHandler']);
$this->on(self::EVENT_BEFORE_DELETE, [$this, 'beforeDeleteHandler']);
}
public function beforeInsertHandler($event)
{
// 插入前设置默认值
if (empty($this->created_at)) {
$this->created_at = time();
}
$this->updated_at = time();
}
public function afterUpdateHandler($event)
{
// 更新后记录变更日志
$changedAttributes = $event->changedAttributes;
if (!empty($changedAttributes)) {
$log = new ProductChangeLog();
$log->product_id = $this->id;
$log->changes = json_encode($changedAttributes);
$log->save();
}
}
public function beforeDeleteHandler($event)
{
// 删除前检查依赖
if ($this->hasOrders()) {
Yii::$app->session->setFlash('error', 'Cannot delete product with existing orders');
$event->isValid = false; // 阻止删除
}
}
public function behaviors()
{
return [
[
'class' => 'yii\behaviors\TimestampBehavior',
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
],
],
];
}
}
案例2:控制器事件监听
namespace app\controllers;
use yii\web\Controller;
use yii\filters\AccessControl;
class SiteController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::class,
'rules' => [
['allow' => true, 'roles' => ['@']],
],
],
];
}
public function init()
{
parent::init();
// 监听控制器事件
$this->on(self::EVENT_BEFORE_ACTION, [$this, 'beforeActionHandler']);
$this->on(self::EVENT_AFTER_ACTION, [$this, 'afterActionHandler']);
}
public function beforeActionHandler($event)
{
// 记录用户访问日志
$action = $event->action->id;
$controller = $event->action->controller->id;
Yii::info("User " . Yii::$app->user->id .
" accessing $controller/$action");
// 可以修改或阻止动作执行
// $event->isValid = false; // 阻止动作
}
public function afterActionHandler($event)
{
// 动作执行后的处理
$action = $event->action;
$result = $event->result;
// 记录执行时间等
}
}
案例3:模块级别事件
namespace app\modules\admin;
class AdminModule extends \yii\base\Module
{
public function init()
{
parent::init();
// 模块初始化时绑定事件
Yii::$app->user->on(\yii\web\User::EVENT_AFTER_LOGIN, function ($event) {
// 管理员登录后记录
if ($event->identity->isAdmin()) {
AdminLog::log('login', Yii::$app->request->userIP);
}
});
// 监听模块内控制器事件
$this->on('controller.init', function ($event) {
// 所有Admin模块控制器初始化时的处理
});
}
}
五、事件处理器优先级
// 设置处理器执行顺序(优先级)
$user->on(User::EVENT_AFTER_SIGNUP,
[$notifier, 'sendWelcomeEmail'],
['priority' => 10] // 优先级越高越先执行
);
$user->on(User::EVENT_AFTER_SIGNUP,
[$notifier, 'logSignup'],
['priority' => 5] // 后执行
);
// 也可以使用 prepend 方法将处理器添加到队列开头
$user->on(User::EVENT_AFTER_SIGNUP,
function ($event) {
echo "This handler will execute first!";
},
false // false 表示添加到开头
);
六、事件解绑和通配符
// 1. 解绑特定处理器
$user->off(User::EVENT_AFTER_SIGNUP, [$notifier, 'sendWelcomeEmail']);
// 2. 解绑所有处理器
$user->off(User::EVENT_AFTER_SIGNUP);
// 3. 解绑对象的所有事件处理器
$user->off();
// 4. 通配符事件(需要自定义实现)
class EventManager extends Component
{
public function on($name, $handler, $data = null, $append = true)
{
// 支持通配符,如 'user.*'
parent::on($name, $handler, $data, $append);
}
public function trigger($name, Event $event = null)
{
// 触发匹配通配符的事件
$pattern = str_replace('*', '.*', $name);
foreach ($this->_events as $eventName => $handlers) {
if (preg_match("#^{$pattern}$#", $eventName)) {
parent::trigger($eventName, $event);
}
}
}
}
七、实际应用案例:用户注册完整流程
// 1. 定义多个事件类
namespace app\events;
use yii\base\Event;
class UserBeforeRegisterEvent extends Event
{
public $user;
public $isValid = true;
public $validationErrors = [];
}
class UserAfterRegisterEvent extends Event
{
public $user;
public $activationToken;
}
// 2. 用户服务类
namespace app\services;
use Yii;
use app\models\User;
use app\events\UserBeforeRegisterEvent;
use app\events\UserAfterRegisterEvent;
class UserService extends \yii\base\Component
{
const EVENT_BEFORE_REGISTER = 'beforeRegister';
const EVENT_AFTER_REGISTER = 'afterRegister';
public function register($data)
{
$user = new User();
$user->load($data, '');
// 触发注册前事件
$beforeEvent = new UserBeforeRegisterEvent(['user' => $user]);
$this->trigger(self::EVENT_BEFORE_REGISTER, $beforeEvent);
if (!$beforeEvent->isValid) {
$user->addErrors($beforeEvent->validationErrors);
return false;
}
if ($user->save()) {
// 生成激活令牌
$activationToken = Yii::$app->security->generateRandomString();
$user->updateAttributes(['activation_token' => $activationToken]);
// 触发注册后事件
$afterEvent = new UserAfterRegisterEvent([
'user' => $user,
'activationToken' => $activationToken,
]);
$this->trigger(self::EVENT_AFTER_REGISTER, $afterEvent);
return true;
}
return false;
}
}
// 3. 事件监听器类
namespace app\listeners;
use Yii;
use app\events\UserAfterRegisterEvent;
use app\events\UserBeforeRegisterEvent;
class UserEventListener
{
public static function handleBeforeRegister(UserBeforeRegisterEvent $event)
{
$user = $event->user;
// 检查用户名是否已存在
if (User::find()->where(['username' => $user->username])->exists()) {
$event->validationErrors['username'] = ['Username already exists'];
$event->isValid = false;
}
// 检查邮箱格式
if (!filter_var($user->email, FILTER_VALIDATE_EMAIL)) {
$event->validationErrors['email'] = ['Invalid email format'];
$event->isValid = false;
}
}
public static function handleAfterRegister(UserAfterRegisterEvent $event)
{
$user = $event->user;
// 发送激活邮件
self::sendActivationEmail($user, $event->activationToken);
// 发送欢迎消息
self::sendWelcomeMessage($user);
// 记录注册统计
self::logRegistration($user);
}
private static function sendActivationEmail($user, $token)
{
$activationLink = Yii::$app->urlManager->createAbsoluteUrl([
'site/activate',
'token' => $token
]);
Yii::$app->mailer->compose('activation', ['link' => $activationLink])
->setTo($user->email)
->setSubject('Account Activation')
->send();
}
private static function sendWelcomeMessage($user)
{
// 发送系统消息
$message = new Message();
$message->user_id = $user->id;
$message->title = 'Welcome!';
$message->content = 'Thank you for registering!';
$message->save();
}
private static function logRegistration($user)
{
Yii::info("New user registered: {$user->username} ({$user->email})", 'registration');
}
}
// 4. 配置监听器 - 在应用配置中
return [
'components' => [
'userService' => [
'class' => 'app\services\UserService',
'on beforeRegister' => ['app\listeners\UserEventListener', 'handleBeforeRegister'],
'on afterRegister' => ['app\listeners\UserEventListener', 'handleAfterRegister'],
],
// 也可以使用行为来附加事件
'mailer' => [
'class' => 'yii\swiftmailer\Mailer',
'as mailLogger' => [
'class' => 'app\behaviors\MailLoggerBehavior',
'on beforeSend' => function ($event) {
// 记录邮件发送
Yii::info('Sending email: ' . $event->subject);
},
],
],
],
];
// 5. 控制器中使用
namespace app\controllers;
use Yii;
use app\services\UserService;
class UserController extends \yii\web\Controller
{
public function actionRegister()
{
$userService = Yii::$app->userService;
// 可以动态添加更多监听器
$userService->on(UserService::EVENT_AFTER_REGISTER, function ($event) {
// 发送短信通知(如果有手机号)
if (!empty($event->user->phone)) {
// 调用短信服务
}
});
if ($userService->register(Yii::$app->request->post())) {
Yii::$app->session->setFlash('success', 'Registration successful!');
return $this->redirect(['site/index']);
}
return $this->render('register');
}
}
八、最佳实践
-
命名规范
- 事件名使用驼峰命名:
EVENT_BEFORE_ACTION - 事件类名以 Event 结尾:
UserSignupEvent
- 事件名使用驼峰命名:
-
性能考虑
- 避免在频繁触发的事件中执行耗时操作
- 合理使用事件优先级
-
调试技巧
// 查看已绑定的事件处理器 Yii::debug(\yii\helpers\VarDumper::dumpAsString( Yii::$app->user->getEventHandlers('afterLogin') )); -
测试事件
// 单元测试中的事件测试 public function testRegisterEvent() { $userService = Yii::$app->userService; $eventTriggered = false; $userService->on(UserService::EVENT_AFTER_REGISTER, function ($event) use (&$eventTriggered) { $eventTriggered = true; }); $userService->register($testData); $this->assertTrue($eventTriggered, 'Event should be triggered'); }
总结
Yii2 的事件系统提供了强大的解耦机制,通过合理使用事件,可以使代码更加模块化、可维护和可扩展。建议将事件用于:
- 记录日志和审计
- 发送通知(邮件、短信等)
- 数据验证和过滤
- 业务逻辑扩展点
- 状态变化监听
欢迎大家学习,交流
浙公网安备 33010602011771号