Yii中的Event和Behaviour理解

來源:互聯網
上載者:User

Event                                                                                                                                                 

Event其實是個既複雜又簡單的問題,因為裡面總是有很多概念容易交織,在理解Event前,我們首先要明白Event的概念。

Event中往往有兩個角色,一個是Event,一個是Event Handler。為什麼需要兩個。

原因很容易理解,Event只是說明這件事情發生了,但是沒有說怎麼處理。這部分處理的工作當然是留給了Event Handler了。

我在這裡主要從和現實世界的相似性上說,不涉及設計模式上的講解。有意向的朋友可以自己去參看相關的模式,資料還是很多的。

言歸正傳,對於Event和Event Handler的概念一定要時刻爛熟於胸,否則,很容易混淆的。

在這裡我們主要針對Yii中的情況進行分析

對於Event在Yii中的理解,首先要清楚這麼幾件事情

1、  在Yii中Event一般是在CComponet的子類中擴充出來的,一般以on開頭。

public function onClick($event) {     $this->raiseEvent('onClick',$event); }

上面是一段例子,用來定義Event的。但是你要注意這裡的$event可不是我們前面說的Event,而應該說$event和這個onClick函數整體構成了一個Event,

其實這也很容易理解,因為一個Event的構成需要一個標誌說明Event發生,還需要一些資料來說明這個Event發生時的情況。

2、  如raiseEvent的機制要明白,就是要看源碼。

我們首先看CComponent的raiseEvent的源碼,當然避免不了的還有CComponet的源碼

在講解raiseEvent前,我們需要看CComponent中的一段代碼,是__get函數的

public function __get($name)         {                 $getter='get'.$name;                 if(method_exists($this,$getter))                         return $this->$getter();                 else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))                 {                         // duplicating getEventHandlers() here for performance                         $name=strtolower($name);                         if(!isset($this->_e[$name]))                                 $this->_e[$name]=new CList;                         return $this->_e[$name];                 }                 else if(isset($this->_m[$name]))                         return $this->_m[$name];                 else if(is_array($this->_m))                 {                         foreach($this->_m as $object)                         {                                 if($object->getEnabled() && (property_exists($object,$name) || $object->canGetProperty($name)))                                         return $object->$name;                         }                 }                 throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',                         array('{class}'=>get_class($this), '{property}'=>$name)));         }

這裡其實完成的功能很簡單,就是針對不同的情況來完成對$this->property的叫用作業,這個應該屬於php的了,就不多說了。

我們在這裡主要需要注意的是$this->_e[$name]這個變數,在程式的第6-13行,這段是針對事件做的操作。

這裡,只要清楚,這個變數是一個CLIST類型,並且是和所設定的具體的Event的相關的即可。

接下來,我們可以看具體的raiseEvent代碼了,如下:

public function raiseEvent($name,$event) {     $name=strtolower($name);     if(isset($this->_e[$name]))     {         foreach($this->_e[$name] as $handler)         {             if(is_string($handler))                 call_user_func($handler,$event);             else if(is_callable($handler,true))             {                 if(is_array($handler))                 {                     // an array: 0 - object, 1 - method name                     list($object,$method)=$handler;                     if(is_string($object))    // static method call                         call_user_func($handler,$event);                     else if(method_exists($object,$method))                         $object->$method($event);                     else                         throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".',                             array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>$handler[1])));                 }                 else // PHP 5.3: anonymous function                     call_user_func($handler,$event);             }             else                 throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".',                     array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>gettype($handler))));             // stop further handling if param.handled is set true             if(($event instanceof CEvent) && $event->handled)                 return;         }     }     else if(YII_DEBUG && !$this->hasEvent($name))         throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.',             array('{class}'=>get_class($this), '{event}'=>$name))); }

第四行,我們發現了$this->_e[$name]變數,從第四行往下看,我們不難發現,原來這個變數是一個存放Event Handler的地方,就是放具體要怎麼處理這個事件的函數。

這裡需要插播一段,你可能會奇怪,沒有見到具體的Event Handler傳入啊。我引用了一段CComponent說明的程式來說明,

$component->onClick=$callback;    // or $component->onClick->add($callback);

看到這裡,很自然的就要想到找CComponet中的__set函數

public function __set($name,$value)         {                 $setter='set'.$name;                 if(method_exists($this,$setter))                         return $this->$setter($value);                 else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))                 {                         // duplicating getEventHandlers() here for performance                         $name=strtolower($name);                         if(!isset($this->_e[$name]))                                 $this->_e[$name]=new CList;                         return $this->_e[$name]->add($value);                 }                 else if(is_array($this->_m))                 {                         foreach($this->_m as $object)                         {                                 if($object->getEnabled() && (property_exists($object,$name) || $object->canSetProperty($name)))                                         return $object->$name=$value;                         }                 }                 if(method_exists($this,'get'.$name))                         throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.',                                 array('{class}'=>get_class($this), '{property}'=>$name)));                 else                         throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',                                 array('{class}'=>get_class($this), '{property}'=>$name)));         }

從第六行看起,發現了嘛。又是Event的相關部分,看到第12行,發現了嗎。把$callback存入了$this->_e[$name]變數。好,說道這裡,我們又要再回到raiseEvent中。。。

其實接下來的代碼也很容易明白,就是運行這個Event Handler了。裡面一些判斷也只是針對了不同形式的$Callback而做的。

貌似。。。Event部分講完了。。。沒有錯其實就是這麼簡單的事情。。

總結一下,就是先在Componet的相關子類中1、定義Event,就是onXXX的函數。(以上只是定義的時候做的,接下來的可以在定義中定義,也可以根據情況動態處理。)2、需要再賦予相應的EventHandler就是callback3、調用相應的事件,就是在你覺得會出現這個事件地方加入這個事件函數,即可。比如在一個Click函數的開頭放入onClick,表示在執行Click動作之前。

Behaviour                                                                                                                                                     

Behaviour中文名為行為,那麼他到底是什麼呢。和Event又是什麼關係呢。

其實在Yii中這個Behaviour是Event Handler的另一種表現形式而已,和我們之前說的$callback其實是一回事,只是為了擴充和使用,做了進一步的封裝。

在具體解釋Behaviour之前,我們要理解一個概念Mixin。。。這個可不是"迷信“的意思。。我覺得應該是Mix in的意思,但是不確定,這是個什麼東西呢,他其實什麼都不是,就是一種概念。

這個概念具體指什麼。就是指一個類提供了某種功能可以被其子類繼承或複用,而不需要執行個體化。對於一般的語言都只可以單繼承,而mixin的表現更類似於種多繼承。他強調,從mixin繼承的不是一種特性,而是收集功能的手段。很神奇吧。也很晦澀吧。其實也就是演算法的事情。我們到具體的執行個體中就容易理解了。

接下裡我們看看Yii中的實現,首先先看behaviour的使用方法。

// $name uniquely identifies the behavior in the component$component->attachBehavior($name,$behavior);// test() is a method of $behavior$component->test();

上面通過調用CComponet中的attachBehavior方法實現的關聯,我們深入起源碼看看究竟做了什麼。

public function attachBehavior($name,$behavior) {     if(!($behavior instanceof IBehavior))         $behavior=Yii::createComponent($behavior);     $behavior->setEnabled(true);     $behavior->attach($this);     return $this->_m[$name]=$behavior; }

程式很短,就是判斷傳入的behaviour是不是IBehavior的執行個體,根據情況做一下處理,反正最後是通過調用IBehavior的attach方法去掛到當前的CComponent方法。

好了,關鍵的地方來了。注意看第七行,沒有錯,起把傳入的behavior傳入了 $this->_m[$name]變數,這個也就是為什麼可以多綁定的原因,實現了收集的機制。

你應該注意到,我這裡遺漏了一個關鍵的地方,就是第六行。這個部分才是Mixin的核心所在。讓我們轉到IBehavior先看方法定義。

/**  * IBehavior interfaces is implemented by all behavior classes.  *  * A behavior is a way to enhance a component with additional methods that  * are defined in the behavior class and not available in the component class.  *  * @author Qiang Xue <qiang.xue@gmail.com>  * @version $Id$  * @package system.base  * @since 1.0.2  */ interface IBehavior {         /**          * Attaches the behavior object to the component.          * @param CComponent $component the component that this behavior is to be attached to. */         public function attach($component);         /**          * Detaches the behavior object from the component.          * @param CComponent $component the component that this behavior is to be detached from. */         public function detach($component);         /**          * @return boolean whether this behavior is enabled */         public function getEnabled();         /**          * @param boolean $value whether this behavior is enabled */         public function setEnabled($value); }

主要看attach方法,他是一個behavior綁定到component的一個方法,具體的實現在其子類,我們繼續看下去,看CBehavior

其中對於attach的實現,如下

public function attach($owner){    $this->_owner=$owner;    foreach($this->events() as $event=>$handler)        $owner->attachEventHandler($event,array($this,$handler));}

詳細分析這段代碼,這裡完成了,設定behavior的綁定對象,這也就是說,一個behavior對象只可以為一個component服務,所謂的收集功能主要是前面所說的 $this->_m[$name]起的作用。

那麼Event和Behavior又怎麼會有關係了。看到 $owner->attachEventHandler($event,array($this,$handler));這句了嗎。$owner就是我們綁定behavior的component,用該方法的名稱就可以很容易的發現這裡實現的Event Handler的綁定功能。

這裡的$this->events()是什麼。

public function events()        {                return array();        }

怎麼突然和上面匹配不了了。返回了一個空的array()?千萬別搞錯,這個函數你可以認為是一個virtual的函數是需要重寫的。你其實這裡就可以理解為events()方法返回的是一個含有事件名稱和事件處理方法的關聯陣列。

如果還不能理解,我們看下CModelBehavior的這段的具體實現,他可是繼承自CBehavior的。

public function events(){    return array(        'onAfterConstruct'=>'afterConstruct',        'onBeforeValidate'=>'beforeValidate',        'onAfterValidate'=>'afterValidate',    );}


看到了嗎。定義了三個事件,以及相應的事件處理函數,只是這些事件處理函數,在你擴充的時候,需要重寫。

言歸正傳,繼續回到Behavior的attach方法中的這句$owner->attachEventHandler($event,array($this,$handler));語句。

我們跟進去看看這個Componet中的attachEventHandler方法到底搞了寫什麼。

public function attachEventHandler($name,$handler)         {                 $this->getEventHandlers($name)->add($handler);         }          public function getEventHandlers($name)         {                 if($this->hasEvent($name))                 {                         $name=strtolower($name);                         if(!isset($this->_e[$name]))                                 $this->_e[$name]=new CList;                         return $this->_e[$name];                 }                 else                         throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.',                                 array('{class}'=>get_class($this), '{event}'=>$name)));         }

attachEventHandler方法中調用了getEventHandlers方法,該方法就是返回$this->_e[$name]變數,然後$handler存入,注意這裡的$handler其實對應調用處的array($this,$handler)。

結合前面Event中說的,一切都很顯然了。

總結一下,behavior其實可以認為是一種將事件分類整理在一起,歸成一類,然後,一起註冊進去,這樣更方便維護和管理,同時也更物件導向。所以behavior也可以認為是一種事件和其處理方法的集合。


Ok,繞來繞去終於繞出來了。其實整個還是很簡單的。但是這種idea還是很好的,讓Behavior讓事件也可以複用,或者可以說讓事件的類型可以方便的歸類。
作者: AnyKoro      出處: http://JosephLiu.cnblogs.com/




聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.