Yii框架 Event 和 Behavior理解

Event php

    Yii中的Event由兩部分組成,分別是$events和EventHandler,其中$event表明事件發生時的具體數據,而EventHandler表明事件發生時的具體的處理函數。web

    所以,當錯誤發生時,直接調用onError函數就能夠,這時,以前被註冊過的onError這個事件對應的錯誤處理函數都會被執行。具體的ErrorHandler發生過程能夠經過raiseEvent()的源代碼來了解。數組

    在Yii中,Event一般是在CComponent的子類中擴展出來的,通常以on開頭,如:app

    public function onError($event)yii

    {函數

            $this->raiseEvent('onError', $event);this

    }spa

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)));  
 }

    此函數的主要內容就是在CComponent的變量$_e尋找與事件名稱(如本例中的'onError')對應的EventHandler(函數),而後調用該EventHandler,同時將事件發生時的數據做爲參數傳遞給該函數,完成事件的處理。code

    那麼既然有EventHandler的調用,那麼確定就會有Event和EventHandler的註冊了,不然就沒有EventHandler可調用。component

說到註冊,天然會想到CComponent類中的__set()函數了,來看一下__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事件的註冊是從第6行開始,從代碼中能夠看出Event名稱必須以'on'開頭,如onError,onClick等。對應的$value是一個List,便可以有多個EventHandler。

    具體的註冊代碼,Yii註釋中也已經給出:

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

    總結一下,Event的原理就是首先要定義一個事件處理函數,其次將其註冊到該類的對應的事件中去,最後在發生事件時調用該事件的EventHandler。實例以下,當改變類中的變量name時,打印字符串:

  class example extends CComponent
  {
        private $name;
        public function getName()
        {
            return isset($name) ? $name : '';
        }
        public function setName($newName)
        {
            $this->name = $newName;
            $this->raiseEvent('onChange', new CEvent());
        }
  }   
  //在某一controller中實現:
  $exa = new example();
  $exa->onChange = array($this, "showChange");
  $exa->setName("zhangsan");
  
  public function showChange()
  {
        echo "changed";
  }

Behavior

    Behavior是一種跟EventHandler功能比較相似的另外一種表現形式,有點相似於多重繼承,經過類綁定的方法,component將一個或者多個CBehavior類的成員方法和變量綁定到本身身上。

    經過調用CComponet中的attachBehavior方法實現關聯,咱們深刻起源碼看看究竟作了什麼?

public function attachBehavior($name,$behavior)  //$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]變量,這個也就是爲何能夠多綁定的緣由,實現了收集的機制。轉入IBehavior看attach方法定義

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

     分析這段代碼,首先能夠看出對於每個Behavior,都只能屬於一個Component.

       那麼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其實與event很相似,只不過behavior是將事件分類整理到一塊兒,歸到一個類中,而後一塊兒註冊給component,所以behavior能夠看作是一個事件與事件處理函數的集合。須要注意的是,當一個behavior被attach到一個component時,該behavior的全部函數都能被component調用。具體事例以下:

    首先重寫Behavior類:

    class exampleBehavior extends CBehavior
    {
            //重寫關聯數組
            public function events()
            {
                    return array(
                            'onBegin' => 'begin';
                            'onEnd' => 'end';
                            'onClick' => 'click';
                    );
            }
            //事件處理函數
            public function begin()
            {
            }
            public function end()
            {
            }
            public function click()
            {
            }
    }
   //在某一個controller中實現:
    $exBehavior = new exampleBehavior();
    $component->attachBehavior('example', 'application.behavior.exampleBehavior');
    $component->begin();
相關文章
相關標籤/搜索