事件可以将自定义代码“注入”到现有代码中的特定执行点。 附加自定义代码到某个事件,当这个事件被触发时,这些代码就会自动执行。 例如,邮件程序对象成功发出消息时可触发事件。 如想追踪成功发送的消息,可以附加相应追踪代码到
messageSent
事件。
messageSent
上边是官方文档上对事件的解释,刚读的时候感觉有点绕口,读不懂上边说的是啥,其实事件就是php观察者模式的一种应用,我自己的理解就是当你的代码逻辑较多时候可以把你写的代码分成几块进行封装,然后在你需要调用的地方进行调用,这样搞的好处就是代码可以达到解耦的效果,有利于代码的后期维护,当然你的代码也变得优雅了。
对观察者模式不太了解的话可以看一下我之前写的一篇文章,其中有一个简单的例子,有助于理解它,观察者模式(php实现)
在介绍yii的事件前需要先介绍一个php函数call_user_func,这个函数的作用就是通过他可以执行其他函数,他会把第一个参数作为回调函数(callback),并且将其余的参数作为回调函数的参数。第一个参数可以是函数名,后面的均为作为该函数使用的参数。 用法如下:
function say($content){
echo 'I am '.$content
}
call_user_func('say','awen');
call_user_func('say','Jack');
输出如下
I am awen
I am Jack
如果你了解了php的观察者模式以及call_user_func这个函数,yii的事件机制就非常容易理解了。Yii 支持事件的基类是yii\base\Component,你创建模型和控制器时继承的model和controller都已经继承过Component了,所以你可以直接调用Component中的方法来进行事件的使用,我下边先搞一个简单的例子说明一下
我们的项目做得是分销系统,上下级关系是在你被邀请注册的时候就定下来了,并没有以购买商品为主线进行上下级关系的确定,但是现在有个需求就是希望提供一个接口可以修改用户的上级,这样会有许多关联的内容会被修改,比如该用户的关系树、该用户下级的关系树都需要被修改,以后可能还有其他的要改,比如用户级别等等,如果这些操作全部堆到一个方法里边的话,感觉后期的维护同学要骂娘了,所以就使用了yii的事件,步骤分为以下几步:
- 注册事件
- 多次向事件绑定内容(处理器)
- 触发事件
- 移除事件
代码如下:
<?php
namespace api\classes;
use common\models\ShopOrders;
use common\models\User;
/**
* 用户关系
*
* 用户上下级关系
* @author awen
*/
class MemberRelation extends \yii\db\ActiveRecord
{
//定义事件名(注册事件)
const EVENT_USER_PARENT_UPDATE = 'user_parent_update';
/**
* 修改用户上级主逻辑
*
* @access public
* @param string $mobile 用户手机号
* @param string $newParentMobile 新上级
* @return array
*/
public static function userParentUpdate($mobile,$newParentMobile)
{
$userInfo = User::findOne(['mobile'=>$mobile]);
$parentInfo = User::findOne(['mobile'=>$newParentMobile]);
$oldParentInfo = User::findOne(['id'=>$userInfo->pid]);
$transaction = \Yii::$app->db->beginTransaction();
try {
$model = new self;
//向事件中添加订单状态检查的处理器
$model->on(self::EVENT_USER_PARENT_UPDATE,[$model,'orderCheck'],['userInfo'=>$userInfo,'parentInfo'=>$parentInfo]);
//向事件中添加修改用户关系的处理器
$model->on(self::EVENT_USER_PARENT_UPDATE,[$model,'userUpdate'],['userInfo'=>$userInfo,'parentInfo'=>$parentInfo]);
//向事件中添加修改下级用户关系的处理器
$model->on(self::EVENT_USER_PARENT_UPDATE,[$model,'childUpdate'],['userInfo'=>$userInfo,'parentInfo'=>$parentInfo]);
//向事件中添加直邀人数修改的处理器
$model->on(self::EVENT_USER_PARENT_UPDATE,[$model,'directInvitationsUpdate'],['userInfo'=>$userInfo,'parentInfo'=>$parentInfo,'oldParentInfo'=>$oldParentInfo]);
//触发事件
$model->trigger(self::EVENT_USER_PARENT_UPDATE);
//移除事件
$model->off(self::EVENT_USER_PARENT_UPDATE);
$transaction->commit();
return ['status'=>1,'msg'=>'修改成功'];
} catch (\Exception $e) {
$transaction->rollBack();
return ['status'=>0,'msg'=>$e->getMessage()];
}
}
/*
* 查看订单状态
* 用户和下级用户有订单的话就不能修改
* */
public function orderCheck($event)
{
$userInfo = $event->data['userInfo'];
$where = ['or', 'id='.$userInfo->id, 'pid='.$userInfo->id];
$userIdArr = User::find()->where($where)->select('id')->column();
$orders = ShopOrders::find()
->where(['in', 'uid', $userIdArr])
->andWhere(['cancel_order'=>0,'is_settlement'=>0])
->select('uid')
->column();
if($orders){
throw new \Exception('用户'.implode(',',$orders).'有订单正在交易');
}
}
/*
* 修改用户关系
* */
public function userUpdate($event)
{
$userInfo = $event->data['userInfo'];
$parentInfo = $event->data['parentInfo'];
//修改用户关系相关逻辑
//...
}
/*
* 修改下级用户关系
* */
public function childUpdate($event)
{
$userInfo = $event->data['userInfo'];
$parentInfo = $event->data['parentInfo'];
//修改下级用户关系相关逻辑
//...
}
/*
* 直邀人数修改
* */
public function directInvitationsUpdate($event)
{
$userInfo = $event->data['userInfo'];
$parentInfo = $event->data['parentInfo'];
$oldParentInfo = $event->data['oldParentInfo'];
//直邀人数修改相关逻辑
//...
}
}
在控制器中调用userParentUpdate()方法就可以使用了。
on方法中第三个参数就是要传输的数据,可以不传,在绑定内容中就收参数的方式就像我上边例子的形式接受就可以。向事件绑定内容一共可以有以下几种方式
$foo = new Foo;
// 处理器是全局函数
$foo->on(Foo::EVENT_HELLO, 'function_name');
// 处理器是对象方法
$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);
// 处理器是静态类方法
$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);
// 处理器是匿名函数
$foo->on(Foo::EVENT_HELLO, function ($event) {
//事件处理逻辑
});
当然不光可以直接移除事件,如果经过判断发现某个绑定的内容是多余的或者无效的,也可以移除事件绑定内容,方式如下
// 处理器是全局函数
$foo->off(Foo::EVENT_HELLO, 'function_name');
// 处理器是对象方法
$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']);
// 处理器是静态类方法
$foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);
// 处理器是匿名函数
$foo->off(Foo::EVENT_HELLO, $anonymousFunction);
下边看一下执行事件也就是trigger方法的内容
/**
* Triggers an event.
* This method represents the happening of an event. It invokes
* all attached handlers for the event including class-level handlers.
* @param string $name the event name
* @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
*/
public function trigger($name, Event $event = null)
{
$this->ensureBehaviors();
$eventHandlers = [];
foreach ($this->_eventWildcards as $wildcard => $handlers) {
if (StringHelper::matchWildcard($wildcard, $name)) {
$eventHandlers = array_merge($eventHandlers, $handlers);
}
}
if (!empty($this->_events[$name])) {
$eventHandlers = array_merge($eventHandlers, $this->_events[$name]);
}
if (!empty($eventHandlers)) {
if ($event === null) {
$event = new Event();
}
if ($event->sender === null) {
$event->sender = $this;
}
$event->handled = false;
$event->name = $name;
foreach ($eventHandlers as $handler) {
$event->data = $handler[1];
call_user_func($handler[0], $event);
// stop further handling if the event is handled
if ($event->handled) {
return;
}
}
}
// invoke class-level attached handlers
Event::trigger($this, $name, $event);
}
通过最后一行发现这个方法最后还是调用了Event::trigger()这个方法,再看下Event::trigger()这个方法的内容
/**
* Triggers a class-level event.
* This method will cause invocation of event handlers that are attached to the named event
* for the specified class and all its parent classes.
* @param string|object $class the object or the fully qualified class name specifying the class-level event.
* @param string $name the event name.
* @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
*/
public static function trigger($class, $name, $event = null)
{
$wildcardEventHandlers = [];
foreach (self::$_eventWildcards as $nameWildcard => $classHandlers) {
if (!StringHelper::matchWildcard($nameWildcard, $name)) {
continue;
}
$wildcardEventHandlers = array_merge($wildcardEventHandlers, $classHandlers);
}
if (empty(self::$_events[$name]) && empty($wildcardEventHandlers)) {
return;
}
if ($event === null) {
$event = new static();
}
$event->handled = false;
$event->name = $name;
if (is_object($class)) {
if ($event->sender === null) {
$event->sender = $class;
}
$class = get_class($class);
} else {
$class = ltrim($class, '\\');
}
$classes = array_merge(
[$class],
class_parents($class, true),
class_implements($class, true)
);
foreach ($classes as $class) {
$eventHandlers = [];
foreach ($wildcardEventHandlers as $classWildcard => $handlers) {
if (StringHelper::matchWildcard($classWildcard, $class)) {
$eventHandlers = array_merge($eventHandlers, $handlers);
unset($wildcardEventHandlers[$classWildcard]);
}
}
if (!empty(self::$_events[$name][$class])) {
$eventHandlers = array_merge($eventHandlers, self::$_events[$name][$class]);
}
foreach ($eventHandlers as $handler) {
$event->data = $handler[1];
call_user_func($handler[0], $event);
if ($event->handled) {
return;
}
}
}
}
方法前几行的内容主要就是对事件中注册的处理器进行一些处理,最后三个foreach中的内容才是执行的过程,可以看见是利用了php的call_user_func()函数来循环执行事件中绑定的内容。