国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發(fā) > PHP > 正文

深入解析PHP的Yii框架中的event事件機制

2024-05-04 21:50:54
字體:
供稿:網(wǎng)友

事件

事件可以將自定義代碼“注入”到現(xiàn)有代碼中的特定執(zhí)行點。附加自定義代碼到某個事件,當(dāng)這個事件被觸發(fā)時,這些代碼就會自動執(zhí)行。例如,郵件程序?qū)ο蟪晒Πl(fā)出消息時可觸發(fā) messageSent 事件。如想追蹤成功發(fā)送的消息,可以附加相應(yīng)追蹤代碼到messageSent 事件。

Yii 引入了名為 yii/base/Component 的基類以支持事件。如果一個類需要觸發(fā)事件就應(yīng)該繼承 yii/base/Component 或其子類。

Yii的event機制

YII的事件機制,是其比較獨特之處,合理使用好事件機制,會使各個組件之間的耦合更為松散,利于團體協(xié)作開發(fā)。

何時需要使用事件,如何給事件綁定事件處理函數(shù),以及如何觸發(fā)事件,與其它語言是有較大的差別的。例如Javascript中,可以使用

$(‘#id').on("click",function() {});

方式給DOM元素綁定處理函數(shù),當(dāng)DOM元素上發(fā)生指定的事件(如click)時,將自動執(zhí)行設(shè)定的函數(shù)。

但是PHP是服務(wù)器端的腳本語言,就不存在自動觸發(fā)事件之說,所以和Javascript對比,YII中的事件是需要手動觸發(fā)的。一般來說,要實現(xiàn)YII組件的事件機制,需要以下幾步:

定義事件名稱,其實就是級組件定義一個on開頭的方法,其中的代碼是固定的,如:

  1. public function onBeginRequest($event){ 
  2.  
  3.  $this->raiseEvent('onBeginRequest',$event); 
  4.  

即函數(shù)名與事件名是一致的。此步的作用就是將綁定在此事件上的處理函數(shù)逐個執(zhí)行。寫這一系列的播客,算是一個整理,所以我寫細一點,現(xiàn)在把raiseEvent方法的代碼貼出來。

  1. /** * Raises an event.  
  2.  
  3.   * This method represents the happening of an event. It invokes  
  4.  
  5.   * all attached handlers for the event.  
  6.  
  7.   * @param string $name the event name  
  8.  
  9.   * @param CEvent $event the event parameter  
  10.  
  11.   * @throws CException if the event is undefined or an event handler is invalid.  
  12.  
  13. */ 
  14.  
  15.   
  16.  
  17.   public function raiseEvent($name,$event){   
  18.  
  19.        $name=strtolower($name);   
  20.  
  21.        //_e這個數(shù)組用來存所有事件信息 
  22.  
  23.        if(isset($this->_e[$name]))  {    
  24.  
  25.           foreach($this->_e[$nameas $handler) {      
  26.  
  27.             if(is_string($handler))  
  28.  
  29.               call_user_func($handler,$event);        
  30.  
  31.             elseif(is_callable($handler,true)){  
  32.  
  33.                  if(is_array($handler)){         
  34.  
  35.                    // an array: 0 - object, 1 - method name   
  36.  
  37.                    list($object,$method)=$handler;    
  38.  
  39.                    if(is_string($object)) // static method call  
  40.  
  41.                     call_user_func($handler,$event);  
  42.  
  43.                    elseif(method_exists($object,$method))      
  44.  
  45.                      $object->$method($event);         
  46.  
  47.                    else         
  48.  
  49.                      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])));       
  50.  
  51.                   }       
  52.  
  53.                   else // PHP 5.3: anonymous function  
  54.  
  55.                      call_user_func($handler,$event);      
  56.  
  57.             }      
  58.  
  59.             else      
  60.  
  61.               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))));      
  62.  
  63.            // stop further handling if param.handled is set true  
  64.  
  65.             if(($event instanceof CEvent) && $event->handled)  
  66.  
  67.               return;    
  68. //Vevb.com 
  69.           }  
  70.  
  71.         }  elseif(YII_DEBUG && !$this->hasEvent($name))    
  72.  
  73.           throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.',     array('{class}'=>get_class($this), '{event}'=>$name))); 
  74.  
  75.  } 

事件處理器(Event Handlers)

事件處理器是一個PHP 回調(diào)函數(shù),當(dāng)它所附加到的事件被觸發(fā)時它就會執(zhí)行??梢允褂靡韵禄卣{(diào)函數(shù)之一:

字符串形式指定的 PHP 全局函數(shù),如 'trim' ;

對象名和方法名數(shù)組形式指定的對象方法,如 [$object, $method] ;

類名和方法名數(shù)組形式指定的靜態(tài)類方法,如 [$class, $method] ;

匿名函數(shù),如 function ($event) { ... } 。

事件處理器的格式是:

  1. function ($event) { 
  2.  
  3.   // $event 是 yii/base/Event 或其子類的對象 
  4.  

通過 $event 參數(shù),事件處理器就獲得了以下有關(guān)事件的信息:

yii/base/Event::name:事件名

yii/base/Event::sender:調(diào)用 trigger() 方法的對象

yii/base/Event::data:附加事件處理器時傳入的數(shù)據(jù),默認為空,后文詳述

附加事件處理器

調(diào)用 yii/base/Component::on() 方法來附加處理器到事件上,如:

  1. $foo = new Foo; 
  2.  
  3.   
  4.  
  5. // 處理器是全局函數(shù) 
  6.  
  7. $foo->on(Foo::EVENT_HELLO, 'function_name'); 
  8.  
  9.   
  10.  
  11. // 處理器是對象方法 
  12.  
  13. $foo->on(Foo::EVENT_HELLO, [$object'methodName']); 
  14.  
  15.   
  16.  
  17. // 處理器是靜態(tài)類方法 
  18.  
  19. $foo->on(Foo::EVENT_HELLO, ['app/components/Bar''methodName']); 
  20.  
  21.   
  22.  
  23. // 處理器是匿名函數(shù) 
  24.  
  25. $foo->on(Foo::EVENT_HELLO, function ($event) { 
  26.  
  27.   //事件處理邏輯 
  28.  
  29. }); 
  30.  
  31. 附加事件處理器時可以提供額外數(shù)據(jù)作為 yii/base/Component::on() 方法的第三個參數(shù)。數(shù)據(jù)在事件被觸發(fā)和處理器被調(diào)用時能被處理器使用。如: 
  32.  
  33. //Vevb.com 
  34. // 當(dāng)事件被觸發(fā)時以下代碼顯示 "abc" 
  35.  
  36. // 因為 $event->data 包括被傳遞到 "on" 方法的數(shù)據(jù) 
  37.  
  38. $foo->on(Foo::EVENT_HELLO, function ($event) { 
  39.  
  40.   echo $event->data; 
  41.  
  42. }, 'abc'); 

事件處理器順序

可以附加一個或多個處理器到一個事件。當(dāng)事件被觸發(fā),已附加的處理器將按附加次序依次調(diào)用。如果某個處理器需要停止其后的處理器調(diào)用,可以設(shè)置 $event 參數(shù)的 [yii/base/Event::handled]] 屬性為真,如下:

  1. $foo->on(Foo::EVENT_HELLO, function ($event) { 
  2.  
  3.   $event->handled = true; 
  4.  
  5. }); 

默認新附加的事件處理器排在已存在處理器隊列的最后。因此,這個處理器將在事件被觸發(fā)時最后一個調(diào)用。在處理器隊列最前面插入新處理器將使該處理器最先調(diào)用,可以傳遞第四個參數(shù) $append 為假并調(diào)用 yii/base/Component::on() 方法實現(xiàn):

  1. $foo->on(Foo::EVENT_HELLO, function ($event) { 
  2.  
  3.   // 這個處理器將被插入到處理器隊列的第一位... 
  4.  
  5. }, $data, false); 

觸發(fā)事件

事件通過調(diào)用 yii/base/Component::trigger() 方法觸發(fā),此方法須傳遞事件名,還可以傳遞一個事件對象,用來傳遞參數(shù)到事件處理器。如:

  1. namespace app/components; 
  2.  
  3.   
  4.  
  5. use yii/base/Component; 
  6.  
  7. use yii/base/Event; 
  8.  
  9.   
  10.  
  11. class Foo extends Component 
  12.  
  13.  
  14.   const EVENT_HELLO = 'hello'
  15.  
  16.   
  17.  
  18.   public function bar() 
  19.  
  20.   { 
  21.  
  22.     $this->trigger(self::EVENT_HELLO); 
  23.  
  24.   } 
  25.  

以上代碼當(dāng)調(diào)用 bar() ,它將觸發(fā)名為 hello 的事件。

提示:推薦使用類常量來表示事件名。上例中,常量 EVENT_HELLO 用來表示 hello 。這有兩個好處。第一,它可以防止拼寫錯誤并支持 IDE 的自動完成。第二,只要簡單檢查常量聲明就能了解一個類支持哪些事件。

有時想要在觸發(fā)事件時同時傳遞一些額外信息到事件處理器。例如,郵件程序要傳遞消息信息到 messageSent 事件的處理器以便處理器了解哪些消息被發(fā)送了。為此,可以提供一個事件對象作為 yii/base/Component::trigger() 方法的第二個參數(shù)。這個事件對象必須是 yii/base/Event 類或其子類的實例。如:

  1. namespace app/components; 
  2.  
  3.   
  4.  
  5. use yii/base/Component; 
  6.  
  7. use yii/base/Event; 
  8.  
  9.   
  10.  
  11. class MessageEvent extends Event 
  12.  
  13.  
  14.   public $message
  15.  
  16.  
  17.   
  18.  
  19. class Mailer extends Component 
  20.  
  21.  
  22.   const EVENT_MESSAGE_SENT = 'messageSent'
  23.  
  24.   
  25.  
  26.   public function send($message
  27.  
  28.   { 
  29.  
  30.     // ...發(fā)送 $message 的邏輯... 
  31.  
  32.  //Vevb.com 
  33.  
  34.     $event = new MessageEvent; 
  35.  
  36.     $event->message = $message
  37.  
  38.     $this->trigger(self::EVENT_MESSAGE_SENT, $event); 
  39.  
  40.   } 
  41.  

當(dāng) yii/base/Component::trigger() 方法被調(diào)用時,它將調(diào)用所有附加到命名事件(trigger 方法第一個參數(shù))的事件處理器。

移除事件處理器

從事件移除處理器,調(diào)用 yii/base/Component::off() 方法。如:

  1. // 處理器是全局函數(shù) 
  2.  
  3. $foo->off(Foo::EVENT_HELLO, 'function_name'); 
  4.  
  5. // 處理器是對象方法 
  6.  
  7. $foo->off(Foo::EVENT_HELLO, [$object'methodName']); 
  8.  
  9. // 處理器是靜態(tài)類方法 
  10.  
  11. $foo->off(Foo::EVENT_HELLO, ['app/components/Bar''methodName']); 
  12.  
  13. // 處理器是匿名函數(shù) 
  14.  
  15. $foo->off(Foo::EVENT_HELLO, $anonymousFunction); 

注意當(dāng)匿名函數(shù)附加到事件后一般不要嘗試移除匿名函數(shù),除非你在某處存儲了它。以上示例中,假設(shè)匿名函數(shù)存儲為變量$anonymousFunction 。

移除事件的全部處理器,簡單調(diào)用 yii/base/Component::off() 即可,不需要第二個參數(shù):

$foo->off(Foo::EVENT_HELLO);

類級別的事件處理器

以上部分,我們敘述了在實例級別如何附加處理器到事件。有時想要一個類的所有實例而不是一個指定的實例都響應(yīng)一個被觸發(fā)的事件,并不是一個個附加事件處理器到每個實例,而是通過調(diào)用靜態(tài)方法 yii/base/Event::on() 在類級別附加處理器。

例如,活動記錄對象要在每次往數(shù)據(jù)庫新增一條新記錄時觸發(fā)一個 yii/db/BaseActiveRecord::EVENT_AFTER_INSERT 事件。要追蹤每個活動記錄對象的新增記錄完成情況,應(yīng)如下寫代碼:

  1. use Yii; 
  2.  
  3. use yii/base/Event; 
  4.  
  5. use yii/db/ActiveRecord; 
  6.  
  7. Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { 
  8.  
  9.   Yii::trace(get_class($event->sender) . ' is inserted'); 
  10.  
  11. }); 

每當(dāng) yii/db/BaseActiveRecord 或其子類的實例觸發(fā) yii/db/BaseActiveRecord::EVENT_AFTER_INSERT 事件時,這個事件處理器都會執(zhí)行。在這個處理器中,可以通過 $event->sender 獲取觸發(fā)事件的對象。

當(dāng)對象觸發(fā)事件時,它首先調(diào)用實例級別的處理器,然后才會調(diào)用類級別處理器。

可調(diào)用靜態(tài)方法yii/base/Event::trigger()來觸發(fā)一個類級別事件。類級別事件不與特定對象相關(guān)聯(lián)。因此,它只會引起類級別事件處理器的調(diào)用。如:

  1. use yii/base/Event; 
  2.  
  3. Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) { 
  4.  
  5.   echo $event->sender; // 顯示 "app/models/Foo" 
  6.  
  7. }); 
  8.  
  9. Event::trigger(Foo::className(), Foo::EVENT_HELLO); 

注意這種情況下 $event->sender 指向觸發(fā)事件的類名而不是對象實例。

注意:因為類級別的處理器響應(yīng)類和其子類的所有實例觸發(fā)的事件,必須謹慎使用,尤其是底層的基類,如 yii/base/Object。

移除類級別的事件處理器只需調(diào)用yii/base/Event::off(),如:

  1. // 移除 $handler 
  2.  
  3. Event::off(Foo::className(), Foo::EVENT_HELLO, $handler); 
  4.  
  5. // 移除 Foo::EVENT_HELLO 事件的全部處理器 
  6.  
  7. Event::off(Foo::className(), Foo::EVENT_HELLO); 

全局事件

所謂全局事件實際上是一個基于以上敘述的事件機制的戲法。它需要一個全局可訪問的單例,如應(yīng)用實例。

事件觸發(fā)者不調(diào)用其自身的 trigger() 方法,而是調(diào)用單例的 trigger() 方法來觸發(fā)全局事件。類似地,事件處理器被附加到單例的事件。如:

  1. use Yii; 
  2.  
  3. use yii/base/Event; 
  4.  
  5. use app/components/Foo; 
  6.  
  7. Yii::$app->on('bar'function ($event) { 
  8.  
  9.   echo get_class($event->sender); // 顯示 "app/components/Foo" 
  10.  
  11. }); 
  12.  
  13. Yii::$app->trigger('bar'new Event(['sender' => new Foo])); 

全局事件的一個好處是當(dāng)附加處理器到一個對象要觸發(fā)的事件時,不需要產(chǎn)生該對象。相反,處理器附加和事件觸發(fā)都通過單例(如應(yīng)用實例)完成。

然而,因為全局事件的命名空間由各方共享,應(yīng)合理命名全局事件,如引入一些命名空間(例:"frontend.mail.sent", "backend.mail.sent")。

給組件對象綁定事件處理函數(shù)

$component->attachEventHandler($name, $handler);

$component->onBeginRequest = $handler ;

yii支持一個事件綁定多個回調(diào)函數(shù),上述的兩個方法都會在已有的事件上增加新的回調(diào)函數(shù),而不會覆蓋已有回調(diào)函數(shù)。

$handler即是一個PHP回調(diào)函數(shù),關(guān)于回調(diào)函數(shù)的形式,本文的最后會附帶說明。如CLogRouter組件的init事件中,有以下代碼:

Yii::app()->attachEventHandler('onEndRequest',array($this,'processLogs'));

這就是給CApplication對象的onEndRequest綁定了CLogRouter::processLogs()回調(diào)函數(shù)。而CApplication組件確實存在名為onEndRequest的方法(即onEndRequest事件),它之中的代碼就是激活了相應(yīng)的回調(diào)函數(shù),即CLogRouter::processLogs()方法。所以從這里可以得出,日志的記錄其實是發(fā)生在CApplication組件的正常退出時。

在需要觸發(fā)事件的時候,直接激活組件的事件,即調(diào)用事件即可,如:比如CApplication組件的run方法中:

if($this->hasEventHandler('onBeginRequest'))

$this->onBeginRequest(new CEvent($this));

這樣即觸發(fā)了事件處理函數(shù)。如果沒有第一行的判斷,那么在調(diào)試模式下(YII_DEBUG常量被定義為true),會拋出異常,而在非調(diào)試模式下(YII_DEBUG常量定義為false或沒有定義YII_DEBUG常量),則不會產(chǎn)生任何異常。

回調(diào)函數(shù)的形式:

普通全局函數(shù)(內(nèi)置的或用戶自定義的)

call_user_func(‘print', $str);

類的靜態(tài)方法,使用數(shù)組形式傳遞

call_user_func(array(‘className', ‘print'), $str );

對象方法,使用數(shù)組形式傳遞

$obj = new className();

call_user_func(array($obj, ‘print'), $str );

匿名方法,類似javascript的匿名函數(shù)

call_user_func(function($i){echo $i++;},4);

或使用以下形式:

  1. $s = function($i) { 
  2.  
  3.   echo $i++; 
  4.  
  5. }; 
  6.  
  7. call_user_func($s,4); 

總結(jié):關(guān)于Yii的事件機制其實就是提供了一種用于解耦的方式,在需要調(diào)用event的地方之前,只要你提供了事件的實現(xiàn)并注冊在之后的地方需要的時候即可調(diào)用。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 柳江县| 新余市| 拉孜县| 娄烦县| 嘉兴市| 新闻| 沅陵县| 库尔勒市| 井冈山市| 兴业县| 高唐县| 武宁县| 光泽县| 德州市| 富裕县| 临高县| 开鲁县| 绿春县| 清苑县| 仙桃市| 康保县| 五寨县| 冷水江市| 罗江县| 湟中县| 吴堡县| 黔江区| 洞口县| 常宁市| 左云县| 营山县| 清新县| 山阴县| 安岳县| 滦平县| 宜丰县| 湖州市| 汉沽区| 洱源县| 图木舒克市| 仁化县|