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/