事件可以将自定義代碼“注入”到現有代碼中的特定執行點。 附加自定義代碼到某個事件,當這個事件被觸發時,這些代碼就會自動執行。 例如,郵件程式對象成功發出消息時可觸發事件。 如想追蹤成功發送的消息,可以附加相應追蹤代碼到
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()函數來循環執行事件中綁定的内容。