<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                使用事件,可以在特定的時點,觸發執行預先設定的一段代碼,事件既是代碼解耦的一種方式,也是設計業務流程的一種模式。現代軟件中,事件無處不在,比如,你發了個微博,觸發了一個事件,導致關注你的人,看到了你新發出來的內容。對于事件而言,有這么幾個要素: * 這是一個什么事件?一個軟件系統里,有諸多事件,發布新微博是事件,刪除微博也是一種事件。 * 誰觸發了事件?你發的微博,就是你觸發的事件。 * 誰負責監聽這個事件?或者誰能知道這個事件發生了?服務器上處理用戶注冊的模塊,肯定不會收到你發出新微博的事件。 * 事件怎么處理?對于發布新微博的事件,就是通知關注了你的其他用戶。 * 事件相關數據是什么?對于發布新微博事件,包含的數據至少要有新微博的內容,時間等。 ## Yii中與事件相關的類[](http://www.digpage.com/event.html#yii "Permalink to this headline") Yii中,事件是在?yii\base\Component?中引入的,注意,?yii\base\Object?不支持事件。所以,當你需要使用事件時,請從?yii\base\Component?進行繼承。同時,Yii中還有一個與事件緊密相關的yii\base\Event?,他封裝了與事件相關的有關數據,并提供一些功能函數作為輔助: ~~~ class Event extends Object { public $name; // 事件名 public $sender; // 事件發布者,通常是調用了 trigger() 的對象或類。 public $handled = false; // 是否終止事件的后續處理 public $data; // 事件相關數據 private static $_events = []; public static function on($class, $name, $handler, $data = null, $append = true) { // ... ... // 用于綁定事件handler } public static function off($class, $name, $handler = null) { // ... ... // 用于取消事件handler綁定 } public static function hasHandlers($class, $name) { // ... ... // 用于判斷是否有相應的handler與事件對應 } public static function trigger($class, $name, $event = null) { // ... ... // 用于觸發事件 } } ~~~ ## 事件handler[](http://www.digpage.com/event.html#handler "Permalink to this headline") 所謂事件handler就是事件處理程序,負責事件觸發后怎么辦的問題。從本質上來講,一個事件handler就是一段PHP代碼,即一個PHP函數。對于一個事件handler,可以是以下的形式提供: * 一個PHP全局函數的函數名,不帶參數和括號,光禿禿的就一個函數名。如?trim?,注意,不是?trim($str)?也不是?trim()?。 * 一個對象的方法,或一個類的靜態方法。如?$person->sayHello()?可以用為事件handler,但要改寫成以數組的形式,?[$person,?'sayHello']?,而如果是類的靜態方法,那應該是['namespace\to\Person',?'sayHello']?。 * 匿名函數。如?function?($event)?{?...?} 但無論是何種方式提供,一個事件handler必須具有以下形式: ~~~ function ($event) { // $event 就是前面提到的 yii\base\Event } ~~~ 也就是只有長得像上面這樣的,才可以作為事件handler。 還有一點容易犯錯的地方,就是對于類自己的成員函數,盡管在調用?on()?進行綁定時,看著這個handler是有效的,因此,有的小伙伴就寫成這樣了?$this->on(EVENT_A,?'publicMethod')?,但事實上,這是一個錯誤的寫法。以字符串的形式提供handler,只能是PHP的全局函數。這是由于handler的調用是通過?call_user_func()?來實現的。因此,handler的形式,與?call_user_func()?的要求是一致的。這將在?[事件的觸發](http://www.digpage.com/event.html#id6)?中介紹。 ## 事件的綁定與解除[](http://www.digpage.com/event.html#id2 "Permalink to this headline") ### 事件的綁定[](http://www.digpage.com/event.html#id3 "Permalink to this headline") 有了事件handler,還要告訴Yii,這個handler是負責處理哪種事件的。這個過程,就是事件的綁定, 把事件和事件handler這兩個螞蚱綁在一根繩上,當事件跳起來的時候,就會扯動事件handler啦。 yii\base\Component::on()?就是用來綁定的,很容易就猜到,?yii\base\Component::off()?就是用來解除的。對于綁定,有以下形式: ~~~ $person = new Person; // 使用PHP全局函數作為handler來進行綁定 $person->on(Person::EVENT_GREET, 'person_say_hello'); // 使用對象$obj的成員函數say_hello來進行綁定 $person->on(Person::EVENT_GREET, [$obj, 'say_hello']); // 使用類Greet的靜態成員函數say_hello進行綁定 $person->on(Person::EVENT_GREET, ['app\helper\Greet', 'say_hello']); // 使用匿名函數 $person->on(Person::EVENT_GREET, function ($event) { echo 'Hello'; }); ~~~ 事件的綁定可以像上面這樣在運行時以代碼的形式進行綁定,也可以在配置中進行綁定。 當然,這個配置生效的過程其實也是在運行時的。原理可以參見?[_配置項(Configuration)_](http://www.digpage.com/configuration.html#configuration)?部分的內容。 上面的例子只是簡單的綁定了事件與事件handler,如果有額外的數據傳遞給handler,可以使用yii\base\Component::on()?的第三個參數。這個參數將會寫進?Event?的相關數據字段,即屬性?data。如: ~~~ $person->on(Person::EVENT_GREET, 'person_say_hello', 'Hello World!'); // 'Hello World!' 可以通過 $event訪問。 function person_say_hello($event) { echo $event->data; // 將顯示 Hello World! } ~~~ yii\base\Component?維護了一個handler數組,用來保存綁定的handler: ~~~ // 這個就是handler數組 private _events = []; // 綁定過程就是將handler寫入_event[] 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]); } } ~~~ ### 保存handler的數據結構[](http://www.digpage.com/event.html#id4 "Permalink to this headline") 從上面代碼我們可以了解兩個方向的內容,一是?$_event[]?的數據結構,二是綁定handler的邏輯。 從handler數組?$_evnet[]?的結構看,首先他是一個數組,保存了該Component的所有事件handler。 該數組的下標為事件名,數組元素是形為一系列?[$handler,?$data]?的數組,如?[_$_event[]數組的數據結構示意圖_](http://www.digpage.com/event.html#img-event)?所示。 ![$_event[]數組的數據結構示意圖](http://www.digpage.com/_images/event.png) $_event[]數組的數據結構示意圖 在事件的綁定邏輯上,按照以下順序: * 參數?$append?是否為?true?。為?true?表示所要綁定的事件handler要放在?$_event[]?數組的最后面。這也是默認的綁定方式。 * 參數?$append?是否為?false?。表示handler要放在數組的最前面。這個時候,要多進行一次判定。 * 如果所有綁定的事件還沒有已經綁定好的handler,也就是說,將要綁定的handler是第一個,那么無論?$append?是否是?true?,該handler必然是第一個元素,也是最后一個元素。 * 如果?$append?為?false?,且要綁定的事件已經有了handler,那么,就將新綁定的事件插入到數組的最前面。 handler在?$event[]?數組中的位置很重要,代表的是執行的先后順序。這個在?[多個事件handler的順序](http://www.digpage.com/event.html#id7)中會講到。 ### 事件的解除[](http://www.digpage.com/event.html#id5 "Permalink to this headline") 在解除時,就是使用?unset()?函數,處理?$_event[]?數組的相應元素。?yii\base\Component::off()如下所示: ~~~ public function off($name, $handler = null) { $this->ensureBehaviors(); if (empty($this->_events[$name])) { return false; } // $handler === null 時解除所有的handler if ($handler === null) { unset($this->_events[$name]); return true; } else { $removed = false; // 遍歷所有的 $handler 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; } } ~~~ 要留意以下幾點: * 當?$handler?為?null?時,表示解除?$name?事件的所有handler。 * 在解除?$handler?時,將會解除所有的這個事件下的?$handler?。雖然一個handler多次綁定在同一事件上的情況不多見,但這并不是沒有,也不是沒有意義的事情。在特定的情況下,確實有一個handler多次綁定在同一事件上。因此在解除時,所有的?$handler?都會被解除。而且沒有辦法只解除其中的一兩個。 ## 事件的觸發[](http://www.digpage.com/event.html#id6 "Permalink to this headline") 事件的處理程序handler有了,事件與事件handler關聯好了,那么只要事件觸發了,handler就會按照設計的路子走。事件的觸發,需要調用?yii\base\Component::trigger() ~~~ public function trigger($name, Event $event = null) { $this->ensureBehaviors(); if (!empty($this->_events[$name])) { if ($event === null) { $event = new Event; } if ($event->sender === null) { $event->sender = $this; } $event->handled = false; $event->name = $name; // 遍歷handler數組,并依次調用 foreach ($this->_events[$name] as $handler) { $event->data = $handler[1]; // 使用PHP的call_user_func調用handler call_user_func($handler[0], $event); // 如果在某一handler中,將$evnet->handled 設為true, // 就不再調用后續的handler if ($event->handled) { return; } } } Event::trigger($this, $name, $event); // 觸發類一級的事件 } ~~~ 以?yii\base\Application?為例,他定義了兩個事件,?EVENT_BEFORE_REQUEST?EVENT_AFTER_REQUEST?分別在處理請求的前后觸發: ~~~ abstract class Application extends Module { // 定義了兩個事件 const EVENT_BEFORE_REQUEST = 'beforeRequest'; const EVENT_AFTER_REQUEST = 'afterRequest'; public function run() { try { $this->state = self::STATE_BEFORE_REQUEST; // 先觸發EVENT_BEFORE_REQUEST $this->trigger(self::EVENT_BEFORE_REQUEST); $this->state = self::STATE_HANDLING_REQUEST; // 處理Request $response = $this->handleRequest($this->getRequest()); $this->state = self::STATE_AFTER_REQUEST; // 處理完畢后觸發EVENT_AFTER_REQUEST $this->trigger(self::EVENT_AFTER_REQUEST); $this->state = self::STATE_SENDING_RESPONSE; $response->send(); $this->state = self::STATE_END; return $response->exitStatus; } catch (ExitException $e) { $this->end($e->statusCode, isset($response) ? $response : null); return $e->statusCode; } } } ~~~ 上面的代碼,不用全部去讀懂。只要注意是怎么定義事件,怎么觸發事件的就可以了。 對于事件的定義,提倡使用const 常量的形式,可以避免寫錯。?trigger('Hello')?和trigger('hello')?可是不同的事件哦。原因在于handler數組下標,就是事件名。 而PHP里數組下標是區分大小寫的。所以,用類常量的方式,可以避免這種頭疼的問題。 在觸發事件時,可以把與事件相關的數據傳遞給所有的handler。比如,發布新微博事件: ~~~ // 定義事件的關聯數據 class MsgEvent extend yii\base\Event { public $dateTime; // 微博發出的時間 public $author; // 微博的作者 public $content; // 微博的內容 } // 在發布新的微博時,準備好要傳遞給handler的數據 $event = new MsgEvent; $event->title = $title; $event->author = $auhtor; // 觸發事件 $msg->trigger(Msg::EVENT_NEW_MESSAGE, $event); ~~~ 注意這里數據的傳入,與使用?on()?綁定handler時傳入數據方法的不同。在?on()?中,使用一個簡單變量,傳入,并在handler中通過?$event->data?進行訪問。這個是在綁定時確定的數據。而有的數據是沒辦法在綁定時確定的,如發出微博的時間。這個時候,就需要在觸發事件時提供其他的數據了。也就是上面這段代碼使用的方法了。這兩種方法,一種用于提供綁定時的相關數據,一種用于提供事件觸發時的數據,各有所長,互相補充。你可要一碗水端平,不要厚此薄彼了。 ## 多個事件handler的順序[](http://www.digpage.com/event.html#id7 "Permalink to this headline") 使用?yii\base\Component::on()?可以為各種事件綁定handler,也可以為同一事件綁定多個handler。假如,你是微博系統的技術人員,剛開始的時候,你指定新發微博的事件handler就是通知關注者有新的內容發布了。現在,你不光要保留這個功能,你還要通知微博中@到的所有人。這個時候,一種做法是直接在原來的handler末尾加上新的代碼,以處理這個新的需要。另一個方法,就是再寫一個handler,并綁定到這個事件上。從易于維護的角度來講,第二種方法是比較合理的。前一種方法由于修改了原來正常使用的代碼,可能會影響原來的正常功能。同時,如果一直有新的需求,那么很快這個handler就會變得很雜,很大。所以,建議使用第二種方法。 Yii中是支持這種一對多的綁定的。那么,在一個事件觸發時,哪個handler會被先執行呢?各handler之間總有一個先后問題吧。這個可能不同的編程語言、不同的框架有不同的實現方式。有的語言是以堆棧的形式來保存handler,可能會以后綁定上去的事件先執行的方式運作。這種方式的好處是編碼的人權限大些,可以對事件進行更改、攔截、中止,移花接木、偷天換日、無中生有,各種欺騙后面的handler。而Yii是使用數組來保存handler的,并按順序執行這些handler。這意味著一般框架上預設的handler會先執行。但是不要以為Yii的事件handler就沒辦法偷天換日了,要使后加上的事件handler先運行,只需在調用?yii\base\Component::on()?進行綁定時,將第四個參數設為?$append?設為?false那么這個handler就會被放在數組的最前面了,它就會被最先執行,它也就有可能欺騙后面的handler了。 為了加強安全生產,國家安監局對某個煤礦進行監管,一旦發生礦難,他們會收到報警,這就是一個事件和一個handler: ~~~ $coal->on(Coal::EVENT_DISASTER, [$government, 'onDisaster']); class Government extend yii\base\Component { ... ... public function onDisaster($event) { echo 'DISASTER! from ' . $event->sender; } } ~~~ 由于煤礦自身也要進行管理,所以,政府允許煤礦可以編寫自己的handler對礦難進行處理。 但是,這個小煤窯的老板,你有張良計,我有過墻梯,對于發生礦難這一事件編寫了一個handler專門用于瞞報: ~~~ // 第四個參數設為false,使得該handler在整個handler數組中處于第一個 $coal->on(Coal::EVENT_DISASTER, [$baddy, 'onDisaster'], null, false); calss Baddy extend yii\base\Component { ... ... public function onDisaster($event) { // 將事件標記為已經處理完畢,阻止后續事件handler介入。 $event->handled = true; } } ~~~ 壞人不可怕,會編程的壞人才可怕。我們要阻止他,所以要把綁定好的handler解除。這個解除是綁定的逆向過程,在實質上,就是把對應的handler從handler數組中刪除。使用?yii\base\Component::off()就能刪除: ~~~ // 刪除所有EVENT_DISASTER事件的handler $coal->off(Coal::EVENT_DISASTER); // 刪除一個PHP全局函數的handler $coal->off(Coal::EVENT_DISASTER, 'global_onDisaster'); // 刪除一個對象的成員函數的handler $coal->off(Coal::EVENT_DISASTER, [$baddy, 'onDisaster']); // 刪除一個類的靜態成員函數的handler $coal->off(Coal::EVENT_DISASTER, ['path\to\Baddy', 'static_onDisaster']); // 刪除一個匿名函數的handler $coal->off(Coal::EVENT_DISASTER, $anonymousFunction); ~~~ 其中,第三種方法就可以把小煤窯老板的handler解除下來。 細心的讀者朋友可能留意到,在刪除匿名函數handler時,需要使用一個變量。請讀者朋友留意,就算你調用?yii\base\Component::on()?yii\base\Component::off()?時,寫了兩個一模一樣的匿名函數,你也沒辦法把你前面的匿名handler解除。從本質上來講,兩個匿名函數就是兩個不同的存在,為了能夠正確解除,需要先把匿名handler保存成一個變量,如上面的?$anonymousFunction?,然后再依次綁定、解除。但是,使用了變量后,就失去了匿名函數的一大心理上的優勢,你本不用去關心他的,我的建議是在這種情況下,就不要使用匿名函數了。因此,在作為handler時,要慎重使用匿名函數。只有在確定不需要解除時,才可以使用。 ## 事件的級別[](http://www.digpage.com/event.html#id8 "Permalink to this headline") 前面的事件,都是針對類的實例而言的,也就是事件的觸發、處理全部都在實例范圍內。這種級別的事件用情專一,不與類的其他實例發生關系,也不與其他類、其他實例發生關系。除了實例級別的事件外,還有類級別的事件。對于Yii,由于Application是一個單例,所有的代碼都可以訪問這個單例。因此,有一個特殊級別的事件,全局事件。但是,本質上,他只是一個實例級別的事件。 這就好比是公司里的不同階層。底層的碼農們只能自己發發牢騷,個人的喜怒哀樂只發生在自己身上,影響不了其他人。而團隊負責人如果心情不好,整個團隊的所有成員今天都要戰戰兢兢,慎言慎行。到了公司老總那里,他今天不爽,哪個不長眼的敢上去觸霉頭?事件也是這樣的,不同層次的事件,決定了他影響到的范圍。 ### 類級別事件[](http://www.digpage.com/event.html#id9 "Permalink to this headline") 先講講類級別的事件。類級別事件用于響應所有類實例的事件。比如,工頭需要了解所有工人的下班時間, 那么,對于數百個工人,即數百個Worker實例,工頭難道要一個一個去綁定自己的handler么? 這也太低級了吧?其實,他只需要綁定一個handler到Worker類,這樣每個工人下班時,他都能知道了。 與實例級別的事件不同,類級別事件的綁定需要使用?yii\base\Event::on() ~~~ Event::on( Worker::className(), // 第一個參數表示事件發生的類 Worker::EVENT_OFF_DUTY, // 第二個參數表示是什么事件 function ($event) { // 對事件的處理 echo $event->sender . ' 下班了'; } ); ~~~ 這樣,每個工人下班時,會觸發自己的事件處理函數,比如去打卡。之后,會觸發類級別事件。 類級別事件的觸發仍然是在?yii\base\Component::trigger()?中,還記得該函數的最后一個語句么: ~~~ Event::trigger($this, $name, $event); // 觸發類一級的事件 ~~~ 這個語句就觸發了類級別的事件。類級別事件,總是在實例事件后觸發。既然觸發時機靠后,那么如果有一天你要早退又不想老板知道,你就可以向小煤窯老板那樣,通過?$event->handled?=?true?,來終止事件處理。 從?yii\base\Event::trigger()?的參數列表來看,比?yii\base\Component::trigger()?多了一個參數$class?表示這是哪個類的事件。因此,在保存?$_event[]?數組上,?yii\base\Event?也比yii\base\Component?要多一個維度: ~~~ // Component中的$_event[] 數組 $_event[$eventName][] = [$handler, $data]; // Event中的$_event[] 數組 $_event[$eventName][$calssName][] = [$handler, $data]; ~~~ 那么,反過來的話,低級別的handler可以在高級別事件發生時發生作用么?這當然也是不行的。由于類級別事件不與任意的實例相關聯,所以,類級別事件觸發時,類的實例可能都還沒有呢,怎么可能進行處理呢? 類級別事件的觸發,應使用?yii\base\Event::trigger()?。這個函數不會觸發實例級別的事件。值得注意的是,?$event->sender?在實例級別事件中,?$event->sender?指向觸發事件的實例,而在類級別事件中, 指向的是類名。在?yii\base\Event::trigger()?代碼中,有: ~~~ if (is_object($class)) { // $class 是trigger()的第一個參數,表示類名 if ($event->sender === null) { $event->sender = $class; } $class = get_class($class); // 傳入的是一個實例,則以類名替換之 } else { $class = ltrim($class, '\\'); } ~~~ 這段代碼會對?$evnet->sender?進行設置,如果傳入的時候,已經指定了他的值,那么這個值會保留,否則,就會替換成類名。 對于類級別事件,有一個要格外注意的地方,就是他不光會觸發自身這個類的事件,這個類的所有祖先類的同一事件也會被觸發。但是,自身類事件與所有祖先類的事件,視為同一級別: ~~~ // 最外面的循環遍歷所有祖先類 do { if (!empty(self::$_events[$name][$class])) { foreach (self::$_events[$name][$class] as $handler) { $event->data = $handler[1]; call_user_func($handler[0], $event); // 所有的事件都是同一級別,可以隨時終止 if ($event->handled) { return; } } } } while (($class = get_parent_class($class)) !== false); ~~~ 上面的嵌套循環的深度,或者叫時間復雜度,受兩個方面影響,一是類繼承結構的深度,二是$_event[$name][$class][]?數組的元素個數,即已經綁定的handler的數量。從實踐經驗看,一般軟件工程繼承深度超過十層的就很少見,而事件綁定上,同一事件的綁定handler超過十幾個也比較少見。因此,上面的嵌套循環運算數量級大約在100~1000之間,這是可以接受的。 但是,從機制上來講,由于類級別事件會被類自身、類的實例、后代類、后代類實例所觸發,所以,對于越底層的類而言,其類事件的影響范圍就越大。因此,在使用類事件上要注意,盡可能往后代類安排,以控制好影響范圍,盡可能不在基礎類上安排類事件。 ### 全局事件[](http://www.digpage.com/event.html#id10 "Permalink to this headline") 接下來再講講全局級別事件。上面提到過,所謂的全局事件,本質上只是一個實例事件罷了。他只是利用了Application實例在整個應用的生命周期中全局可訪問的特性,來實現這個全局事件的。當然,你也可以將他綁定在任意全局可訪問的的Component上。 全局事件一個最大優勢在于:在任意需要的時候,都可以觸發全局事件,也可以在任意必要的時候綁定,或解除一個事件: ~~~ Yii::$app->on('bar', function ($event) { echo get_class($event->sender); // 顯示當前觸發事件的對象的類名稱 }); Yii::$app->trigger('bar', new Event(['sender' => $this])); ~~~ 上面的?Yii::$app->on()?可以在任何地方調用,就可以完成事件的綁定。而?Yii::$app->trigger()?只要在綁定之后的任何時候調用就OK了。 如果覺得《深入理解Yii2.0》對您有所幫助,也請[幫助《深入理解Yii2.0》](http://www.digpage.com/donate.html#donate)。 謝謝!
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看