Yii2之事件
眾所周知,yii的三大特性是:屬性、事件、行為,上一篇博文簡單講解了yii中的屬性,本文接著講講yii的事件。
事件是代碼解耦的一種方式,設計業務流程的一種模式。在yii2.0中,通過Yii\base\Component繼承yii\base\Object,
重載__get()、__set()方法,引入了事件和行為,使得開發變得十分方便。然而,在方便開發的同時也犧牲了一定的效率,
所以若不需要使用事件和行為,可不必繼承Component而選擇繼承Object,Object的效率更接近原生的PHP類。
首先說說yii事件的使用,由於Yii\base\Component類已經實現了事件,所以只要某個類繼承了
就可以調用on()方法來綁定事件,然後在需要的地方調用trigger()方法觸發指定事件。下面舉一個簡單的例子來說明。
例如,在博客系統中,博文數據表中有created_at(發表時間)和updated_at(更新時間)兩個字段,現在我想通過事
件來在更新數據表之前給這兩個字段賦值。我們知道,yii的AR模型在數據表插入或更新數據之前會調用beforeSave()方法,
現在就在博文AR模型中重寫這個方法去綁定並觸發事件給這兩個字段賦值,代碼如下:
public function beforeSave($insert) { if(parent::beforeSave($insert)) { $this->on(‘haha‘, [$this, ‘setTime‘], $insert); $this->trigger(‘haha‘); return true; } return false; }
(這個例子中,事件的綁定和觸發在同一個地方進行)
以上代碼將[$this, ‘setTime’]這個處理器綁定在名為’haha’的事件上,這個處理器其實就是博文AR模型的setTime()方法,
第三個參數表示是否插入數據,將會在事件觸發的時候傳遞給setTime()方法。setTime()方法代碼如下:
public function setTime($event) { if($event->data) { $this->uid = User::getUid(); $this->created_at = time(); } $this->updated_at = time(); }
其中$event->data就是on()函數的第三個參數了。setTime方法的參數$event在yii中是yii\base\Event類的對象,若在事件
觸發之時需要傳遞一些參數給處理器函數,可以寫一個子類繼承yii\base\Event類,設置一些成員變量,然後創建該類的
一個對象,把需要傳遞的參數賦值給這個對象的成員變量,接著把這個對象賦值給事件觸發方法trigger()的第二個參數,
在處理器函數中就可以接收到這些參數值了。
沒錯,在yii中使用事件就是這麽簡單!可以看到,其實事件的本質就是把一段代碼抽出來單獨寫成一個方法,然後把
它綁定在某個事件上,最後在需要調用這個方法的地方觸發這個事件就ok了。個人理解,設計事件主要是為了代碼解耦與
重用吧。
Yii的事件到底是怎麽實現的呢?說到底其實就是一個數組和三個方法就能搞定的事情,且聽我細細道來。首先,使用
一個數組(下文稱為事件隊列)來保存所有的事件,數組的鍵為各個事件名稱,數組的元素值則是各個事件觸發時需要調
用的函數以及需要傳遞給函數的參數(下文稱為事件處理器),可以有一個或多個,所以也是使用數組來存儲,下文稱為
處理器隊列,一個事件對應一個處理器隊列。為一個事件綁定一個事件處理器則是在事件隊列中找到這個事件對應的處理
器隊列並添加一個處理器,把一個處理器從某個事件解綁則是在事件隊列中找到這個事件指定的處理器並從它的處理器隊
列中刪除,觸發一個事件則是在事件隊列中找到這個事件然後按順序調用它對應的處理器隊列中的所有處理器。
下面讓我們從Yii\base\Component類的源碼來看看具體的實現細節,我根據自己的理解在代碼適當位置中加上了一些註釋:
/** * * @var 存儲事件列表的數組,形式:事件名稱 => 對應的事件處理器列表 */ private $_events = []; /** * 為某個事件綁定一個事件處理器 * @param $name:事件名稱,字符串形式 * @param $handler:事件處理器,指定事件觸發時調用的函數,有4種形式: * 1.全局php函數名,字符串形式 * 2.[類名, 方法名],數組形式 * 3.[對象, 方法名],數組形式 * 4.匿名函數,形式:function($event){ ... } * @param $data:事件觸發時傳遞給事件處理器函數的參數,在事件處理器函數調用形式:$event->data * @param $append:為true時表示將綁定的事件處理器添加在事件處理器列表的最後,為false則添加在最前面 */ public function on($name, $handler, $data = null, $append = true) { $this->ensureBehaviors(); if ($append || empty($this->_events[$name])) {//添加到列表後面 $this->_events[$name][] = [$handler, $data]; } else {//添加到列表前面 array_unshift($this->_events[$name], [$handler, $data]); } } /** * 解綁事件處理器 * @param $name:事件名稱 * @param $handler:要解綁的事件處理器,為null表示解綁這個事件的所有處理器,即刪除整個事件 * @return boolean */ public function off($name, $handler = null) { $this->ensureBehaviors(); if (empty($this->_events[$name])) {//事件不存在 return false; } if ($handler === null) {//刪除事件 unset($this->_events[$name]); return true; } $removed = false; foreach ($this->_events[$name] as $i => $event) { if ($event[0] === $handler) { unset($this->_events[$name][$i]);//刪除指定的事件處理器 $removed = true; } } if ($removed) { $this->_events[$name] = array_values($this->_events[$name]); } return $removed; } /** * 觸發一個事件 * @param $name:事件名稱 * @param $event:\yii\base\Event類對象,作為傳遞給事件處理器的參數 * @return type */ public function trigger($name, Event $event = null) { $this->ensureBehaviors(); if (!empty($this->_events[$name])) { if ($event === null) {//沒有$event參數則創建一個默認對象 $event = new Event; } if ($event->sender === null) {//指定觸發事件的對象 $event->sender = $this; } $event->handled = false;//事件是否處理完畢 $event->name = $name;//事件名稱 foreach ($this->_events[$name] as $handler) {//遍歷事件對應的處理器,逐個調用 $event->data = $handler[1];//這裏把on()方法綁定事件處理器時傳遞的$data參數傳遞給事件處理器 call_user_func($handler[0], $event);//調用事件處理器方法 if ($event->handled) {//若在某個事件處理器中將$event->handled置為true,表示事件處理完畢,後面的處理器不再被調用 return; } } } //觸發類級別事件 Event::trigger($this, $name, $event); }
理解了事件的實現原理之後,我們就會發現,其實我們自己也可以實現事件的!
額,說了這麽多也不知道說清楚了沒有,反正我自己覺得挺清楚的,哈哈。。。
Yii2之事件