首页 > 编程 > PHP > 正文

yii2学习事件(event)

2020-03-22 17:54:45
字体:
来源:转载
供稿:网友
  • 事件介绍yii2的事件机制可以让我们将自定义的代码注入到特定的执行点。绑定事件后,一旦事件被触发,自定义代码便会自动执行。 例如,在发送邮件时,我们可能引发messageSent事件来发送消息。如果我们想跟踪已经成功发送的邮件,只需要简单地将跟踪代码附加到messageSent事件上。 由此,我们可以看出,事件执行的大致过程:绑定事件>>触发事件。 所谓绑定事件,其实是指绑定事件处理器——即event handler。 event handler是事件被触发执行后的回调方法,有四种回调方法: 一个全局的html' target='_blank'>PHP函数(不带括号),例如 trim 一个对象,形式为[$object, ['methodName']],注意methodName不带括号。 一个类的静态方法,形式为['ClassName', 'methodName'],注意methodName不带括号。 一个匿名函数,形式为function ($event) { ... }。

    事件的格式如下:

    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编程

    郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

  • 发表评论 共有条评论
    用户名: 密码:
    验证码: 匿名发表