[Yii2笔记]051事件(Events) [ 技术分享 ]
说明
学习Yii Framework 2(易2框架)的过程是漫长的,也是充满乐趣的,以下是我学习Yii2框架时对官网英文资料(请参见原文网址)的翻译和代码实现,提供了较完整的代码,供你参考。不妥之处,请多多指正!
原文网址:
http://www.yiiframework.com/doc-2.0/guide-concept-events.html
本文主题:事件(Events)
事件允许你在某个执行点插入自定义代码到已有代码中,你可以添加自定义代码到事件中,这样当事件被触发时,代码会自动执行。例如,当一个邮件对象成功发送了一条消息时,它会触发一个messageSent事件,如果你想追踪那些被成功发送的信息,你只需在messageSent事件中添加追踪代码即可。
Yii引入了一个基类:yii\base\Component来支持事件,如果你的类需要触发事件,从yii\base\Component类或其子类继承即可。
1、Event Handlers(事件处理器)
事件处理器(Event Handler)是一个PHP回调函数,当它关联的事件被触发时,它将被调用,你可以使用以下形式的回调函数: 1、匿名函数(anonymous function):function($event){...} 2、类方法(object method):[$object,'handledAdd'] 3、静态类方法(staic class method):['Page','handleAdd'] 4、全局函数(global funcion):'handleAdd'
事件处理器的定义如下:
function foo($event){
//$event 是yii\base\Event对象或其子类,它包含了事件相关的所有参数信息。
}
通过$event 参数,事件处理可以获取事件相关的以下信息: 1、事件名称(event name) 2、事件发送者(event sender):trigger()方法被调用的对象 3、用户数据(custom data):绑定事件处理器时提供的数据(用于解释下一步的操作)
2、Attaching Event Handlers(绑定事件处理器)
可以调用yii\base\Component::on()方法来绑定事件处理器到事件上,例如:
$foo=new Foo;
//绑定一个全局函数到事件EVENT_HELLO
$foo->on(Foo::EVENT_HELLO,'function_name');
//绑定一个类方法到事件EVENT_HELLO
$foo->on(Foo::EVENT_HELLO,[$object,'methodName']);
//绑定一个静态类方法到事件EVENT_HELLO
$foo->on(Foo::EVENT_HELLO,['app\components\Bar','methodName']);
//绑定一个匿名函数到事件EVENT_HELLO
$foo->on(Foo::EVENT_HELLO,function($event){
//事件处理逻辑
});
也可以通过配置信息绑定事件处理器,格式如下:
[
'on add'=>function($evnet){...}
]
'on add'表示添加一个事件处理器到'add'事件。
[
'on search' => function ($event) {
Yii::info("Keyword searched: " . $event->keyword);
},
]
当绑定一个事件处理器时,你可以向yii\base\Component::on()提供额外的数据,当事件被触发时,这些数据将被事件调用的处理器所使用,例如:
$foo->on(Foo::EVENT_HELLO,'function_name','abc');
function function_name($event){
echo $event->data;
}
3、Event Handler Order(事件处理器的顺序)
我们可以为一个事件绑定单个或多个事件处理器,当一个事件触发时,绑定的事件处理器将按照它们的绑定顺序逐个调用。如果处理器需要停止调用其后的事件处理器,它可以设置事件$event 的yii\base\Event::$handled 属性为true。
$foo->on(Foo::EVENT_HELLO,function($event){
$event->handled=true;
});
通常,一个新绑定的事件处理器将追加到该事件的处理器队列中,在调用时,这个处理器也将最后一个被调用;如果想把新添加的事件处理器放在第一个被执行,可以在调用yii\base\Component::on()时,设置它的第四个参数为false,代码如下:
$foo->on(Foo::EVENT_HELLO,function($event){
//...
},$data,false);
4、Triggering Events(触发事件)
通过调用yii\base\Component::trigger()方法可以触发事件,此方法需要一个事件名称,随后一个描述这些参数的事件对象会传递给事件处理器,例如:
namespace app\components;
use yii\base\Component;
use yii\base\Event;
class Foo extends Component{
const EVENT_HELLO='hello';
public function bar(){
$this->trigger(self::EVENT_HELLO);
}
}
上述代码中,调用bar()方法就会触发'hello'事件。 小贴士:推荐使用类变量替代事件名称。在上例中,常量EVENT_HELLO代表hello事件。采用这种方法有三个好处: 1、防止输入错误(typos) 2、IDE自动完成技术让事件可识别 3、当前类支持多少个事件通过查看它的常量定义即可查出
触发事件时,我们可能需要传一些附加信息到事件处理器中,例如:一个邮件发送者想发送消息给messageSent事件处理器,这样处理器就可以知道发送消息的细节。要实现这个目标,需要给yii\base\Component::trigger()方法的第二个参数提供事件对象,这个事件对象必须是yii\base\Event类或其子类的实例,例如:
namespace app\components;
use yii\base\Component;
use yii\base\Event;
class MessageEvent extends Event{
public $message;
}
class Mailer extends Component{
const EVENT_MESSAGE_SENT = 'messageSent';
public function send($message){
// ...sending $message...
$event = new MessageEvent;
$event->message = $message;
$this->trigger(self::EVENT_MESSAGE_SENT, $event);
}
}
当调用yii\base\Component::trigger()方法时,它将调用绑定到此事件名称上的所有事件处理器。
详见://实例:事件注册和触发,对象级事件处理器
5、Detaching Event Handlers(解除事件绑定)
解除一个函数(事件处理器)到事件的绑定,可以调用yii\base\Component::off()方法,例如:
//解除全局函数到事件的绑定
$foo->off(Foo::EVENT_HELLO,'function_name');
//解除一个类方法到事件EVENT_HELLO的绑定
$foo->off(Foo::EVENT_HELLO,[$object,'methodName']);
//解除一个静态类方法到事件EVENT_HELLO的绑定
$foo->off(Foo::EVENT_HELLO,['app\components\Bar','methodName']);
//解除一个匿名函数到事件EVENT_HELLO的绑定,匿名函数事先被存储在一个变量$anonymousFunction中。
$foo->off(Foo::EVENT_HELLO,$anonymousFunction);
//解决事件EVENT_HELLO的所有绑定
$foo->off(Foo::EVENT_HELLO);
6、Class-Level Event Handlers(类级别的事件处理器)
前面描述了在实例级别(instance level)绑定事件处理器到事件上,有时,你可能需要绑定到类上,这样每实例化一个对象触发事件时都会调用事件处理器,也就是你可以绑定事件处理器到类级别(class level),可以调用静态方法yii\base\Event::on()来实现。 例如:一个AR对象在EVENT_AFTER_INSERT事件时会触发,此事件是向数据库中插入一条新记录时发生,为了跟踪AR插入动作已完成,你可能需要以下代码:
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'));
});
无论何时此AR或其子类的实例触发EVENT_AFTER_INSERT事件,事件处理器都会被执行。在处理器中,你可以通过$event->sender 获取触发事件的对象。 当一个对象触发了一个事件,它首先会调用实例级的事件处理器,然后再调用类级的事件处理器。 我们可以通过调用静态方法yii\base\Event::trigger()来触发一个类级的事件,一个类级事件和一个具体的对象并不相关,因此,它只调用类级别的事件处理器,例如:
use yii\base\Event;
Event::on(Foo::className(),Foo::EVENT_HELLO,function($event){
var_dump($event->sender);
});
Event::trigger(Foo::className(),Foo::EVENT_HELLO);
注意:此时$event->sender的值是null,而不是一个对象实例。
提醒:因为类级别的事件处理器对每个类或子类的实例化对象所触发,所以使用中应该多加小心,尤其是低级别的基础类,如yii\base\Object。
解绑类级别的事件处理器,调用yii\base\Event::off(),例如:
//解除$handler事件处理器到类事件Foo::EVENT_HELLO的绑定
Event::off(Foo::className,Foo::EVENT_HELLO,$handler);
//解除到类事件Foo::EVENT_HELLO的所有绑定
Event::off(Foo::className,Foo::EVENT_HELLO);
详见://实例:类级事件处理器
7、Events using interfaces(事件使用接口)
还有更多的方法去处理事件,可以为特定事件创建一个单独的接口,然后在需要的类中继承它。 例如,我们可以创建一个接口:
interface DanceEventInterface{
const EVENT_DANCE='dance';
}
再定义两个类实现这个接口:
class Dog extends Component implements DanceEventInterface{
public function meetBuddy(){
echo "Woof!";
$this->trigger(DanceEventInterface::EVENT_DANCE);
}
}
class Developer extends Component implements DanceEventInterface{
public function testPassed(){
echo "Yay!";
$this->trigger(DanceEventInterface::EVENT_DANCE);
}
}
EVENT_DANCE事件将由这些类触发,要绑定此事件调用Event::on()即可,并将接口名称当作第一个参数。
Event::on('DanceEventInterface',DanceEventInterface::EVETN_DANCE,function($event){
Yii::trace($event->sender->className.'just danced');
});
我们可以触发这些类的事件:
//DanceEventInterface::className,此处报错:Undefined class constant 'className'
Event::trigger(DanceEventInterface::className,DanceEventInterface::EVENT_DANCE);
注意:不能去触发实现接口的类:
//下句将不会运行:
Event::trigger('DanceEventInterface',DanceEventInterface::EVENT_DANCE);//错误的代码
解除事件绑定,调用Event::off(),例如:
//解除$handler与事件的绑定
Event::off('DanceEventInterface',DanceEventInterface::EVENT_DANCE,$handler);
//解除与事件绑定的所有事件处理器
Event::off('DanceEventInterface',DanceEventInterface::EVENT_DANCE);
8、Global Events(全局事件)
Yii支持所谓的全局事件(so-called global event),实际上就是应用上述事件机制的一个小把戏,全局事件需要一个全局存取的框架(Singleton),例如应用实例(application)。 要创建全局事件,一个事件发送者调用框架的trigger()方法触发事件,而不是调用发送者自己的trigger()方法,相似的,事件处理器要绑定到框架中的事件中去,例如:
use Yii;
use yii\base\Event;
use app\components\Foo;
Yii::$app->on('bar', function ($event) {
echo get_class($event->sender); // displays "app\components\Foo"
});
Yii::$app->trigger('bar', new Event(['sender' => new Foo]));
使用全局事件的好处是:当绑定一个处理器到对象触发的事件时,无需调用此对象。实际上,处理器绑定和事件触发都由框架(Singleton)来完成,框架是指的应用实例(application instance)。 因为全局事件的命名空间在所有组件之间都是共享的,所以应该给全局事件定义一个好名字,可以使用分段的命名空间(例如:"frontend.mail.sent","backend.mail.sent")
//----------------------------------------------- //教程本节结束,实例开始 D:\phpwork\advanced\frontend\controllers\PostController.php
public function actionEventBehavior(){
Yii::$app->on(DanceEventInterface::EVENT_DANCE, function ($event) {
echo get_class($event->sender); // displays "app\components\Foo"
});
Yii::$app->trigger(DanceEventInterface::EVENT_DANCE);
}
测试结果:
http://localhost:8082/post/event-behavior
/*
yii\web\Application
*/
//--------------- //实例:全局事件的绑定和调用 D:\phpwork\advanced\frontend\controllers\PostController.php
public function actionEventGlobal(){
Yii::$app->on('bar', function ($event) {
//获取对象的名称
echo get_class($event->sender);
echo "<br>";
//调用对象的方法
$event->sender->meetBuddy();
});
//触发时,将Dog对象实例作为参数传递到事件处理器中
Yii::$app->trigger('bar', new Event(['sender' => new Dog]));
}
测试结果:
http://localhost:8082/post/event-global
/*
frontend\models\Dog
Woof!
*/
//--------------- //实例:事件注册和触发,对象级事件处理器 D:\phpwork\advanced\frontend\controllers\PostController.php
public function actionEvent(){
echo "event";
$event=new TestEvent();
//注册事件,绑定[$event,'function_name']到事件EVENT_HELLO
$event->on(TestEvent::EVENT_HELLO,[$event,'function_name'], 'abc');
//触发事件
$event->bar();
}
D:\phpwork\advanced\frontend\models\TestEvent.php
<?php
namespace frontend\models;
use yii\base\Component;
use yii\base\Event;
class MessageEvent extends Event{
public $message;
}
class TestEvent extends Component{
const EVENT_HELLO='hello';
public function bar($message){
$event = new MessageEvent;
$event->message = $message;
//触发hello事件
$this->trigger(self::EVENT_HELLO,$event);
}
function function_name($event) {
//data是on()传递进来的参数
echo '<br>data provided by on:'.$event->data;
//message是trigger()传递进来的参数
echo '<br>Info from event:'.$event->message;
}
}
测试结果:
http://localhost:8082/post/event
/*
event
data provided by on:abc
Info from event:event info
*/
//--------------- //实例:类级事件处理器,在类初始化时绑定事件处理器 D:\phpwork\advanced\frontend\controllers\PostController.php
use frontend\models\TestEvent;
class PostController extends CommonController {
public function actionEvent(){
echo "event<br>";
$event=new TestEvent();
//对象级绑定事件处理器[$event,'function_name']到事件EVENT_HELLO上
$event->on(TestEvent::EVENT_HELLO,[$event,'function_name'], 'abc');
$event->bar('event info');
}
D:\phpwork\advanced\frontend\models\TestEvent.php
<?php
namespace frontend\models;
use yii\base\Component;
use yii\base\Event;
class MessageEvent extends Event{
public $message;
}
class TestEvent extends Component{
const EVENT_HELLO='hello';
public function bar($message){
$event = new MessageEvent;
$event->message = $message;
$this->trigger(self::EVENT_HELLO,$event);
}
function function_name($event) {
echo '<br>data provided by on:'.$event->data;
echo '<br>Info from event:'.$event->message;
}
public function init(){
parent::init();
//类级绑定事件处理器(一个匿名函数)到事件EVENT_HELLO上
Event::on(static::className(), static::EVENT_HELLO, function ($event) {
//类级事件处理器将在对象级事件处理器之后被调用
echo "<br>This is Class-level Handler:<br>";
var_dump($event->sender);
});
}
}
测试结果:
http://localhost:8082/post/event
/*
event
data provided by on:abc
Info from event:event info
This is Class-level Handler:
D:\phpwork\advanced\frontend\models\TestEvent.php:23:
object(frontend\models\TestEvent)[53]
private '_events' (yii\base\Component) =>
array (size=1)
'hello' =>
array (size=1)
0 =>
array (size=2)
...
private '_behaviors' (yii\base\Component) =>
array (size=0)
empty
*/
//------------------------ //类级事件处理器 s //------------------------ //完整实例:类级事件处理器,在类初始化时绑定事件处理器 D:\phpwork\advanced\frontend\controllers\PostController.php
use frontend\models\ClassEvent;
class PostController extends CommonController {
public function actionEvent3(){
echo "event<br>";
//绑定类级别的事件处理器,类的每一个实例都可以触发事件调用相关的事件处理器EVENT_HELLO
Event::on(ClassEvent::className(), ClassEvent::EVENT_HELLO, function ($event) {
//类级事件处理器使用的是一个匿名函数,$event->message是调用时传入进来的一个参数
echo "<br>This is Class-level Handler:".$event->message."<br>";
//var_dump($event->sender);
});
//类级事件处理器的另外一种调用方式
// Event::on(ClassEvent::className(), ClassEvent::EVENT_HELLO, [ClassEvent::className(),'function_name'],'[aa]');
//对象1触发事件,并调用事件处理器EVENT_HELLO
$event1=new ClassEvent();
$event1->bar('event info1');
//对象2触发事件,并调用事件处理器EVENT_HELLO
$event2=new ClassEvent();
$event2->bar('event info2');
}
D:\phpwork\advanced\frontend\models\ClassEvent.php
<?php
namespace frontend\models;
use yii\base\Component;
use yii\base\Event;
//使用Event的实例向事件处理器传递参数或信息
class MessageEvent extends Event{
//要传递的参数或信息定义为类的属性
public $message;
}
class ClassEvent extends Component{
const EVENT_HELLO='hello';
public function bar($msg){
//实例化Event对象,传递参数
$event = new MessageEvent;
$event->message = $msg;
//触发事件,并添加了Event对象$event
$this->trigger(self::EVENT_HELLO,$event);
}
public static function function_name($event) {
//类级事件处理器将在对象级事件处理器之后被调用
echo "<br>This".$event->data." is Class-level Handler:No param!<br>";
//var_dump($event->sender);
}
}
测试结果:
http://localhost:8082/post/event3
/*
event
This is Class-level Handler:event info1
This is Class-level Handler:event info2
*/
Event::on(ClassEvent::className(), ClassEvent::EVENT_HELLO, [ClassEvent::className(),'function_name'],'[aa]');
/*
event
This[aa] is Class-level Handler:No param!
This[aa] is Class-level Handler:No param!
*/
//------------------------ //类级事件处理器 e //------------------------
//------------------------ //使用接口定义事件名称 s //------------------------ //完整实例:使用接口定义事件名称
D:\phpwork\advanced\frontend\controllers\PostController.php
use frontend\models\Dog;
use frontend\models\Cat;
use yii\base\Event;
class PostController extends CommonController {
public function actionEvent(){
echo "<br>Event:<br>";
//绑定到接口事件
Event::on('frontend\models\DanceEventInterface', DanceEventInterface::EVENT_DANCE, function ($event) {
echo $event->className . ' just danced<br>'; // Will log that Dog or Developer danced
});
/*
* Yii2指南中的错误写法:
* 1、接口名称不是全称,无法找到
* 2、$event->sender->className,此名称不存在
Event::on('DanceEventInterface', DanceEventInterface::EVENT_DANCE, function ($event) {
Yii::trace($event->sender->className . ' just danced'); // Will log that Dog or Developer danced
});
*/
$dog=new Dog();
//触发事件
$dog->meetBuddy();
$cat=new Cat();
//触发事件
$cat->doEvent();
}
D:\phpwork\advanced\frontend\models\EventMsg.php
<?php
namespace frontend\models;
use yii\base\Event;
class EventMsg extends Event{
public $className;
}
D:\phpwork\advanced\frontend\models\Cat.php
<?php
namespace frontend\models;
use yii\base\Component;
class Cat extends Component implements DanceEventInterface{
public function doEvent(){
echo "Meel!<br>";
$event = new EventMsg;
$event->className =$this->className();
$this->trigger(DanceEventInterface::EVENT_DANCE,$event);
}
public function eventHandler(){
echo "Cat event<br>";
}
}
D:\phpwork\advanced\frontend\models\Dog.php
<?php
namespace frontend\models;
use yii\base\Component;
class Dog extends Component implements DanceEventInterface{
public function meetBuddy(){
echo "Woof!<br>";
$event = new EventMsg;
$event->className =$this->className();
$this->trigger(DanceEventInterface::EVENT_DANCE,$event);
}
public function eventHandler(){
echo "Dog event<br>";
}
}
测试结果:
http://localhost:8082/post/event
/*
Event:
Woof!
frontend\models\Dog just danced
Meel!
frontend\models\Cat just danced
*/
echo var_export($event,true)."<br>";
测试结果:
/*
frontend\models\EventMsg::__set_state(array( 'className' => 'frontend\\models\\Dog', 'name' => 'dance', 'sender' => frontend\models\Dog::__set_state(array( '_events' => array ( ), '_behaviors' => array ( ), )), 'handled' => false, 'data' => NULL, ))
*/
//------------------------ //使用接口定义事件名称 e //------------------------
//------------------------ //off实例 s //------------------------ //解除类绑定的事件处理器 D:\phpwork\advanced\frontend\controllers\PostController.php
use yii\base\Event;
use frontend\models\ClassEvent;
class PostController extends CommonController {
public function actionEvent3(){
Event::on(ClassEvent::className(), ClassEvent::EVENT_HELLO, [ClassEvent::className(),'function_name'],'[aa]');
$event1=new ClassEvent();
$event1->bar('event info1');
Event::off(ClassEvent::className(), ClassEvent::EVENT_HELLO, [ClassEvent::className(),'function_name']);
$event2=new ClassEvent();
$event2->bar('event info2');
}
D:\phpwork\advanced\frontend\models\ClassEvent.php
<?php
namespace frontend\models;
use yii\base\Component;
use yii\base\Event;
class MessageEvent extends Event{
public $message;
}
class ClassEvent extends Component{
const EVENT_HELLO='hello';
public function bar($msg){
$event = new MessageEvent;
$event->message = $msg;
echo $msg."<br>";
$this->trigger(self::EVENT_HELLO,$event);
}
public static function function_name($event) {
echo "event message:".$event->message."<br>";
}
}
测试结果:
http://localhost:8082/post/event3
/*
event info1
event message:event info1
event info2
*/
//------------------------ //off实例 e //------------------------
(全文完)
共 0 条回复
阿江
最后登录:2024-03-03
在线时长:186小时21分
- 粉丝94
- 金钱16816
- 威望160
- 积分20276