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