阿江 2017-10-12 11:11:32 95次浏览 0条回复 0 0 0

说明

学习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
//------------------------

(全文完)

    没有找到数据。
您需要登录后才可以回复。登录 | 立即注册