event
イベントは、既存のコードの特定の実行ポイントにカスタム コードを「挿入」できます。カスタム コードをイベントにアタッチすると、イベントがトリガーされたときにコードが自動的に実行されます。たとえば、メーラー オブジェクトがメッセージの送信に成功したときに、messageSent イベントを発生させることができます。正常に送信されたメッセージを追跡したい場合は、対応する追跡コードを messageSent イベントに添付できます。
Yii では、イベントをサポートするために yiibaseComponent と呼ばれる基本クラスが導入されています。クラスがイベントをトリガーする必要がある場合は、yiibaseComponent またはそのサブクラスを継承する必要があります。
Yiiのイベントメカニズム
YII のイベント メカニズムは、そのユニークな機能です。イベント メカニズムを適切に使用すると、さまざまなコンポーネント間の結合が緩和され、グループでの共同開発に役立ちます。
イベントを使用する必要がある場合、イベント処理関数をイベントにバインドする方法、およびイベントをトリガーする方法は他の言語とはまったく異なります。たとえば、JavaScript では
を使用できます。
メソッドは、DOM 要素に処理関数をバインドします。DOM 要素上で指定されたイベント (クリックなど) が発生すると、設定された関数が自動的に実行されます。
ただし、PHP はサーバー側のスクリプト言語であるため、JavaScript と比較してイベントを自動的にトリガーすることができません。したがって、YII のイベントは手動でトリガーする必要があります。一般に、YII コンポーネントのイベント メカニズムを実装するには、次の手順が必要です:
イベント名を定義します。実際、レベルコンポーネントは on で始まるメソッドを定義し、その中のコードは次のように固定されています。 リーリー
つまり、関数名とイベント名は一致しています。このステップの目的は、このイベントにバインドされた処理関数を 1 つずつ実行することです。この一連のポッドキャストを書くのは一種の仕上げなので、さらに詳しく書いて、raiseEvent メソッドのコードを投稿します。
リーリー
イベントハンドラー
イベント ハンドラーは、関連付けられたイベントがトリガーされたときに実行される PHP コールバック関数です。次のコールバック関数のいずれかを使用できます:
リーリー
$event パラメーターを通じて、イベント ハンドラーはイベントに関する次の情報を取得します。
追加のイベントハンドラー
yiibaseComponent::on() メソッドを呼び出して、イベントにハンドラーをアタッチします。例:リーリー
イベントハンドラーの順序
1 つ以上のハンドラーをイベントにアタッチできます。イベントがトリガーされると、アタッチされたハンドラーがアタッチされた順序で呼び出されます。プロセッサが後続のプロセッサ呼び出しを停止する必要がある場合は、次のように $event パラメータの [yiibaseEvent::handled]] 属性を true に設定できます。 リーリーデフォルトでは、新しくアタッチされたイベント ハンドラーは既存のハンドラー キューの最後に配置されます。したがって、このハンドラーは、イベントが発生したときに最後に呼び出されます。プロセッサ キューの先頭に新しいプロセッサを挿入すると、そのプロセッサが最初に呼び出されます。これを実現するには、4 番目のパラメータ $append を false として渡し、 yiibaseComponent::on() メソッドを呼び出します。 リーリー
トリガーイベント
イベントは yiibaseComponent::trigger() メソッドを呼び出すことによってトリガーされます。このメソッドはイベント名とイベント オブジェクトを渡してイベント ハンドラーにパラメーターを渡す必要があります。例:
リーリー
ヒント: イベント名を表すにはクラス定数を使用することをお勧めします。上記の例では、定数 EVENT_HELLO を使用して hello を表しています。これには 2 つの利点があります。まず、タイプミスを防止し、IDE のオートコンプリートをサポートします。次に、定数の宣言を調べるだけで、クラスがどのイベントをサポートしているかを知ることができます。
イベントがトリガーされたときに、追加情報をイベント ハンドラーに渡したい場合があります。たとえば、メール プログラムは、どのメッセージが送信されたかをハンドラーが認識できるように、メッセージ情報を messageSent イベントのハンドラーに渡す必要があります。これを行うには、yiibaseComponent::trigger() メソッドの 2 番目のパラメーターとしてイベント オブジェクトを指定できます。このイベント オブジェクトは、yiibaseEvent クラスまたはそのサブクラスのインスタンスである必要があります。例:
リーリー yiibaseComponent::trigger() メソッドが呼び出されると、名前付きイベント (トリガー メソッドの最初のパラメーター) にアタッチされたすべてのイベント ハンドラーが呼び出されます。
イベントハンドラーを削除
イベントからハンドラーを削除するには、yiibaseComponent::off() メソッドを呼び出します。例: リーリー
通常、イベントにアタッチされた匿名関数は、どこかに保存しない限り、削除しないでください。上記の例では、匿名関数が変数 $anonymousFunction として格納されていると仮定しています。
移除事件的全部处理器,简单调用 yii\base\Component::off() 即可,不需要第二个参数:
$foo->off(Foo::EVENT_HELLO);
类级别的事件处理器
以上部分,我们叙述了在实例级别如何附加处理器到事件。有时想要一个类的所有实例而不是一个指定的实例都响应一个被触发的事件,并不是一个个附加事件处理器到每个实例,而是通过调用静态方法 yii\base\Event::on() 在类级别附加处理器。
例如,活动记录对象要在每次往数据库新增一条新记录时触发一个 yii\db\BaseActiveRecord::EVENT_AFTER_INSERT 事件。要追踪每个活动记录对象的新增记录完成情况,应如下写代码:
use Yii; use yii\base\Event; use yii\db\ActiveRecord; Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { Yii::trace(get_class($event->sender) . ' is inserted'); });
每当 yii\db\BaseActiveRecord 或其子类的实例触发 yii\db\BaseActiveRecord::EVENT_AFTER_INSERT 事件时,这个事件处理器都会执行。在这个处理器中,可以通过 $event->sender 获取触发事件的对象。
当对象触发事件时,它首先调用实例级别的处理器,然后才会调用类级别处理器。
可调用静态方法yii\base\Event::trigger()来触发一个类级别事件。类级别事件不与特定对象相关联。因此,它只会引起类级别事件处理器的调用。如:
use yii\base\Event; Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) { echo $event->sender; // 显示 "app\models\Foo" }); Event::trigger(Foo::className(), Foo::EVENT_HELLO);
注意这种情况下 $event->sender 指向触发事件的类名而不是对象实例。
注意:因为类级别的处理器响应类和其子类的所有实例触发的事件,必须谨慎使用,尤其是底层的基类,如 yii\base\Object。
移除类级别的事件处理器只需调用yii\base\Event::off(),如:
// 移除 $handler Event::off(Foo::className(), Foo::EVENT_HELLO, $handler); // 移除 Foo::EVENT_HELLO 事件的全部处理器 Event::off(Foo::className(), Foo::EVENT_HELLO);
全局事件
所谓全局事件实际上是一个基于以上叙述的事件机制的戏法。它需要一个全局可访问的单例,如应用实例。
事件触发者不调用其自身的 trigger() 方法,而是调用单例的 trigger() 方法来触发全局事件。类似地,事件处理器被附加到单例的事件。如:
use Yii; use yii\base\Event; use app\components\Foo; Yii::$app->on('bar', function ($event) { echo get_class($event->sender); // 显示 "app\components\Foo" }); Yii::$app->trigger('bar', new Event(['sender' => new Foo]));
全局事件的一个好处是当附加处理器到一个对象要触发的事件时,不需要产生该对象。相反,处理器附加和事件触发都通过单例(如应用实例)完成。
然而,因为全局事件的命名空间由各方共享,应合理命名全局事件,如引入一些命名空间(例:"frontend.mail.sent", "backend.mail.sent")。
给组件对象绑定事件处理函数
$component->attachEventHandler($name, $handler); $component->onBeginRequest = $handler ;
yii支持一个事件绑定多个回调函数,上述的两个方法都会在已有的事件上增加新的回调函数,而不会覆盖已有回调函数。
$handler即是一个PHP回调函数,关于回调函数的形式,本文的最后会附带说明。如CLogRouter组件的init事件中,有以下代码:
Yii::app()->attachEventHandler('onEndRequest',array($this,'processLogs'));
这就是给CApplication对象的onEndRequest绑定了CLogRouter::processLogs()回调函数。而CApplication组件确实存在名为onEndRequest的方法(即onEndRequest事件),它之中的代码就是激活了相应的回调函数,即CLogRouter::processLogs()方法。所以从这里可以得出,日志的记录其实是发生在CApplication组件的正常退出时。
在需要触发事件的时候,直接激活组件的事件,即调用事件即可,如:比如CApplication组件的run方法中:
if($this->hasEventHandler('onBeginRequest')) $this->onBeginRequest(new CEvent($this));
这样即触发了事件处理函数。如果没有第一行的判断,那么在调试模式下(YII_DEBUG常量被定义为true),会抛出异常,而在非调试模式下(YII_DEBUG常量定义为false或没有定义YII_DEBUG常量),则不会产生任何异常。
回调函数的形式:
普通全局函数(内置的或用户自定义的)
call_user_func(‘print', $str);
类的静态方法,使用数组形式传递
call_user_func(array(‘className', ‘print'), $str );
对象方法,使用数组形式传递
$obj = new className(); call_user_func(array($obj, ‘print'), $str );
匿名方法,类似javascript的匿名函数
call_user_func(function($i){echo $i++;},4);
或使用以下形式:
$s = function($i) { echo $i++; }; call_user_func($s,4);
总结
关于Yii的事件机制其实就是提供了一种用于解耦的方式,在需要调用event的地方之前,只要你提供了事件的实现并注册在之后的地方需要的时候即可调用。