事件的格式如下:
function ($event) { // $event is an object of yiiaseEvent or a child class}
通过$event参数,时间处理器可以获得当前事件的如下信息:
event name event sender: 触发事件的对象 custom data: 触发事件提供的参数yiiaseComponent
yiiaseEvent
相关方法:on绑定,off解绑,trigger触发。
注意:yiiaseObject类没有事件处理功能!
在mvc应用开发中,我们在哪里绑定事件呢?
追踪下yiiwebControlle,我们发现:
yiiwebController继承自yiiaseController
yiiaseController继承自yiiaseComponent
而component是可以进行事件操作的。
由此可以知道,在任意控制器中可以直接使用$this->on($name, $handler, $data = null, $append = true)来绑定事件。
方式二:追踪下yiiaseModel,我们发现它也继承自component。
也就是说,只要我们的model继承了yiiaseModel类,我们就可以在model中使用$this->on($name, $handler, $data = null, $append = true)来绑定事件。
举个例子,现在我们在SiteController控制器定义一个新方法actionTest:
public function actionTest() { $person = new Person(); // backendmodelsPerson; $this->on('111','hello','111111111'); // 全局函数,定义在入口文件index.php中 $this->on('222',[$person,'say_bye'],'222222222'); // 对象 $this->on('333',['backendmodelsPerson','say_hello'],'333333333'); // 类 $this->on('444',function(){ echo '444444444'; }); //匿名函数 }
backendmodelPerson模型的代码如下:
<?phpnamespace backendmodels;use yiiaseModel;class Person extends Model{ public function say_bye($parm){ echo $parm->data.'<br>'; } static function say_hello($parm) { echo $parm->data.'<br>'; }}
入口文件index.php的全局函数hello
function hello($parm){ echo $parm->data.'<br>';}
这时候,我们通过浏览器访问site控制器的test方法,发现页面一片空白!为什么呢?事件没有触发呗。
yii2的事件是通过trigger方法触发的,此方法需要一个事件名称,事件的参数可以在第二个参数设置,如果没有设置,系统会默认创建一个事件对象。
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; foreach ($this->_events[$name] as $handler) { $event->data = $handler[1]; call_user_func($handler[0], $event); // stop further handling if the event is handled if ($event->handled) { return; } } } // invoke class-level attached handlers Event::trigger($this, $name, $event); }
现在来触发我们的事件,在SiteController的actionTest方法中添加触发事件的方法:
public function actionTest() { $person = new Person(); // backendmodelsPerson; $this->on('111','hello','111111111'); // 全局函数定义在入口文件backend/web/index.php中 $this->on('222',[$person,'say_bye'],'222222222'); // 对象 $this->on('333',['backendmodelsPerson','say_hello'],'333333333'); // 类 $this->on('444',function(){ echo '444444444'; }); //匿名函数 $this->trigger('111'); //触发事件 $this->trigger('222'); //触发事件 $this->trigger('333'); //触发事件 $this->trigger('444'); //触发匿名事件 }
继续浏览器访问test方法,结果如下图:
事件触发成功!非常好。
其实事件是可以“嵌套”使用的,举个例子吧:
比如在事件’222‘绑定的say_bye方法中,我们在绑定一个匿名事件
<?phpnamespace backendmodels;use yiiaseModel;class Person extends Model{ const EVENT_TEST='event_test'; public function say_bye($parm){ echo $parm->data.'<br>'; $this->on(Person::EVENT_TEST,function(){ echo 'I am from '.__CLASS__.'<br>'; }); $this->trigger(Person::EVENT_TEST); } static function say_hello($parm) { echo $parm->data.'<br>'; }}
浏览器访问test方法,如下图
说明事件是可以“链式”操作的。
细心的朋友会发现,上面的on和trigger操作,事件名没有使用简单的字符串,而是使用了const常量。
这样可以避免写错,也有利于代码整洁,推荐用这样的方法(最上面的代码只是便于演示和理解的目的)。
你可能对一个事件绑定多个处理程序。当事件被触发时,默认会按照它们被绑定的顺序执行。如果某个事件处理器需要停止后续事件处理器的调用,可以设置yiiaseEvent::$handled属性的$event参数为true:
$foo->on(Foo::EVENT_HELLO, function ($event) { $event->handled = true;});
比如我们要给test方法中绑定2个名字为’444‘的事件
public function actionTest() { $person = new Person(); // backendmodelsPerson; $this->on('111','hello','111111111'); // 对象 $this->on('222',[$person,'say_bye'],'222222222'); // 对象 $this->on('333',['backendmodelsPerson','say_hello'],'333333333'); // 类 $this->on('444','hello','------'); // 对象 $this->on('444',function(){ echo '444444444'; }); //匿名函数 $this->trigger('111'); //触发事件 $this->trigger('222'); //触发事件 $this->trigger('333'); //触发事件 $this->trigger('444'); //触发匿名事件 }
结果为:
假如我们不想让’444‘事件执行完hello后继续执行下面的匿名函数,可以修改hello方法
function hello($parm){ $parm->handled = true; // 停止执行此事件的后续处理程序 echo $parm->data.'<br>';}
结果为:
默认情况下,一个新的事件处理器被绑定在已经存在的事件队列后面。结果,该事件触发的时候被最后执行。为了让新插入的时间处理器在队列的最前面,以便最先被调用,你可以调用yiiaseConponent::on(),给第四个参数$append传递false即可。
$foo->on(Foo::EVENT_HELLO, function ($event) { // ...}, $data, false);
现在我们将hello方法中的$param->handled设置为false,修改test方法中的匿名函数,使它比hello先执行。修改actionTest
public function actionTest() { $person = new Person(); // backendmodelsPerson; $this->on('111','hello','111111111'); // 对象 $this->on('222',[$person,'say_bye'],'222222222'); // 对象 $this->on('333',['backendmodelsPerson','say_hello'],'333333333'); // 类 $this->on('444','hello','------'); // 对象 $this->on('444',function(){ echo '444444444'; },'',false); //匿名函数 $this->trigger('111'); //触发事件 $this->trigger('222'); //触发事件 $this->trigger('333'); //触发事件 $this->trigger('444'); //触发匿名事件 }
浏览器访问test方法
的确匿名函数优先执行了。
解绑事件处理器,可通过yiiaseComponent::off()方法。例如:
//事件处理器是一个全局函数
$foo->off(Foo::EVENT_HELLO, 'function_name');
//事件处理器是一个对象
$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']);
//事件处理器是一个静态方法
$foo->off(Foo::EVENT_HELLO, ['appcomponentsBar', 'methodName']);
//事件处理器是一个匿名函数
$foo->off(Foo::EVENT_HELLO, $anonymousFunction);
注意:一般情况不能解绑一个匿名函数,除非当他们被绑定时,你将它存放在某个变量。在上例中,假定将匿名函数存放在了$anonymousFunction变量里。
要解除所有的事件处理器,使用调用yiiaseComponent::off(),不传递第二个参数即可:
$foo->off(Foo::EVENT_HELLO);
以上部分描述了如何将处理程序附加到实例级上的事件。有些情况,我们可能想对某个类的所有实例绑定一些处理程序,而不是绑定到单个实例上。当然,对每一个实例都进行绑定是不可取的(那多麻烦啊~),这时候可以通过调用yiiaseEvent::on()的静态方法,绑定类级处理事件处理器。
例如:每当在数据库中插入新记录的时候,一个Actiove Record对象可能想触发一个EVENT_AFTER_INSERT事件。为了追踪Active Record进行的每一个插入操作,你可以这样做:
use Yii;use yiiaseEvent;use yiidbActiveRecord;Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { Yii::trace(get_class($event->sender) . ' is inserted');});一旦Active Record的实例或它的某个子类,触发EVENT_AFTER_INSERT事件,该事件处理程序将被调用。在处理程序中,你可以通过$event->sender来获得触发处理器的对象。 当对象触发事件时,会先调用实例级处理器,然后再调用类级处理器。 我们可以通过调用静态方法yiiaseEvent::trigger()来触发类级事件。一个类级事件与特定的对象无关。所以,只会触发类级事件处理器。例如:
use yiiaseEvent;Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) { echo $event->sender; // displays 'appmodelsFoo'});Event::trigger(Foo::className(), Foo::EVENT_HELLO);
注意,在这种情况,$event->sender指的是触发事件的类而不是一个实例对象。
另外,因为类级处理器能对该类或子类的所有触发器做出回应,所以我们要谨慎使用,尤其是基层类中,例如yiiaseObject。
解绑一个类级事件处理器,可以调用yiiaseEvent::off()来实现。例如:
// 解绑单个Event::off(Foo::className(), Foo::EVENT_HELLO, $handler);
// 解绑所有Event::off(Foo::className(), Foo::EVENT_HELLO);
yii2支持全局事件,实际上是以上事件处理机制的延伸。全局事件需要拥有全局访问权限的单例架构,比如application自身或者其它的component。
创建全局事件,一个事件调用者调用单例组件的trigger()方法而非调用自身的trigger()方法。
同样,事件处理器被绑定在单例组件上。例如:
use Yii;use yiiaseEvent;use appcomponentsFoo;Yii::$app->on('bar', function ($event) { echo get_class($event->sender); // displays 'appcomponentsFoo'});Yii::$app->trigger('bar', new Event(['sender' => new Foo]));
使用全局事件的好处是,当给对象的事件绑定处理程序时,不需要一个对象。相反,事件处理程序的绑定和触发都是通过单例组件来完成。
但是,由于全局变量的namespaces会被所有部分公享,所以要给全局事件合理地命名,例如说明下namespace的类型,比如('frontend.mail.sent', 'backend.mail.sent')。
PHP编程郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。
新闻热点
疑难解答