阿江 2017-10-12 11:11:32 2277次浏览 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 //------------------------

(全文完)

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