0 关注者

事件

事件允许您在特定执行点将自定义代码注入到现有代码中。您可以将自定义代码附加到事件,以便在触发事件时,代码会自动执行。例如,邮件发送器对象在成功发送邮件时可能会触发一个messageSent事件。如果您想跟踪成功发送的邮件,则只需将跟踪代码附加到messageSent事件即可。

Yii 引入了一个名为 yii\base\Component 的基类来支持事件。如果某个类需要触发事件,则应扩展自 yii\base\Component 或其子类。

事件处理器

事件处理器是一个 PHP回调函数,当它所绑定的事件被触发时,它会被执行。您可以使用以下任何回调函数

  • 以字符串形式指定的全局PHP函数(无括号),例如:'trim';
  • 以对象和方法名字符串数组形式指定的对象方法(无括号),例如:[$object, 'methodName'];
  • 以类名和方法名字符串数组形式指定的静态类方法(无括号),例如:['ClassName', 'methodName'];
  • 匿名函数,例如:function ($event) { ... }

事件处理器的签名为

function ($event) {
    // $event is an object of yii\base\Event or a child class
}

通过$event参数,事件处理器可以获取以下关于发生的事件的信息

绑定事件处理器

您可以通过调用 yii\base\Component::on() 方法将处理器绑定到事件。例如

$foo = new Foo();

// this handler is a global function
$foo->on(Foo::EVENT_HELLO, 'function_name');

// this handler is an object method
$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);

// this handler is a static class method
$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);

// this handler is an anonymous function
$foo->on(Foo::EVENT_HELLO, function ($event) {
    // event handling logic
});

您也可以通过 配置 绑定事件处理器。有关更多详细信息,请参阅 配置 部分。

绑定事件处理器时,您可以将附加数据作为第三个参数传递给 yii\base\Component::on()。当事件被触发并调用处理器时,数据将可供处理器使用。例如

// The following code will display "abc" when the event is triggered
// because $event->data contains the data passed as the 3rd argument to "on"
$foo->on(Foo::EVENT_HELLO, 'function_name', 'abc');

function function_name($event) {
    echo $event->data;
}

事件处理器顺序

您可以将一个或多个处理器绑定到单个事件。当事件被触发时,绑定的处理器将按照它们绑定到事件的顺序被调用。如果某个处理器需要停止后续处理器的调用,则可以将$event参数的 yii\base\Event::$handled 属性设置为true

$foo->on(Foo::EVENT_HELLO, function ($event) {
    $event->handled = true;
});

默认情况下,新绑定的处理器会被追加到事件的现有处理器队列中。因此,当事件被触发时,处理器将在最后被调用。要将新处理器插入处理器队列的开头,以便处理器首先被调用,您可以调用 yii\base\Component::on(),并将false作为第四个参数$append传递。

$foo->on(Foo::EVENT_HELLO, function ($event) {
    // ...
}, $data, false);

触发事件

事件通过调用 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 事件。这种方法有三个好处。首先,它可以防止拼写错误。其次,它可以使事件更容易被 IDE 自动完成支持识别。第三,您可以通过简单地检查类的常量声明来了解该类支持哪些事件。

有时在触发事件时,您可能希望将其他信息传递给事件处理程序。例如,邮件发送器可能希望将邮件信息传递给 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() 方法被调用时,它将调用附加到命名事件的所有处理程序。

分离事件处理程序

要从事件中分离处理程序,请调用 yii\base\Component::off() 方法。例如

// the handler is a global function
$foo->off(Foo::EVENT_HELLO, 'function_name');

// the handler is an object method
$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']);

// the handler is a static class method
$foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);

// the handler is an anonymous function
$foo->off(Foo::EVENT_HELLO, $anonymousFunction);

请注意,通常您不应该尝试分离匿名函数,除非在将其附加到事件时将其存储在某个地方。在上面的示例中,假设匿名函数存储为变量 $anonymousFunction

要从事件中分离所有处理程序,只需调用 yii\base\Component::off() 而不带第二个参数

$foo->off(Foo::EVENT_HELLO);

类级别事件处理程序

以上小节描述了如何在实例级别将处理程序附加到事件。有时,您可能希望响应由类的每个实例触发的事件,而不是仅由特定实例触发。您可以通过调用静态方法 yii\base\Event::on()类级别附加处理程序,而不是将事件处理程序附加到每个实例。

例如,一个 活动记录 对象将在其将新记录插入数据库时触发 EVENT_AFTER_INSERT 事件。为了跟踪由每个 活动记录 对象完成的插入,您可以使用以下代码

use Yii;
use yii\base\Event;
use yii\db\ActiveRecord;

Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
    Yii::debug(get_class($event->sender) . ' is inserted');
});

每当 ActiveRecord 或其子类的实例触发 EVENT_AFTER_INSERT 事件时,都会调用事件处理程序。在处理程序中,您可以通过 $event->sender 获取触发事件的对象。

当对象触发事件时,它将首先调用实例级处理程序,然后调用类级处理程序。

您可以通过调用静态方法 yii\base\Event::trigger() 来触发类级别事件。类级别事件与特定对象无关。因此,它只会导致类级别事件处理程序的调用。例如

use yii\base\Event;

Event::on(Foo::class, Foo::EVENT_HELLO, function ($event) {
    var_dump($event->sender);  // displays "null"
});

Event::trigger(Foo::class, Foo::EVENT_HELLO);

请注意,在这种情况下,$event->sendernull 而不是对象实例。

注意:因为类级处理程序将响应由该类的任何实例或任何子类触发的事件,所以您应该谨慎使用它,尤其是在该类是低级基类时,例如 yii\base\BaseObject

要分离类级事件处理程序,请调用 yii\base\Event::off()。例如

// detach $handler
Event::off(Foo::class, Foo::EVENT_HELLO, $handler);

// detach all handlers of Foo::EVENT_HELLO
Event::off(Foo::class, Foo::EVENT_HELLO);

使用接口的事件

还有更抽象的方法来处理事件。您可以为特殊事件创建一个单独的接口,并在需要它的类中实现它。

例如,我们可以创建以下接口

namespace app\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 testsPassed()
    {
        echo "Yay!";
        $this->trigger(DanceEventInterface::EVENT_DANCE);
    }
}

要处理由这些类中的任何一个触发的 EVENT_DANCE,请调用 Event::on() 并将接口类名称作为第一个参数传递

Event::on('app\interfaces\DanceEventInterface', DanceEventInterface::EVENT_DANCE, function ($event) {
    Yii::debug(get_class($event->sender) . ' just danced'); // Will log that Dog or Developer danced
});

您可以触发这些类的事件

// trigger event for Dog class
Event::trigger(Dog::class, DanceEventInterface::EVENT_DANCE);

// trigger event for Developer class
Event::trigger(Developer::class, DanceEventInterface::EVENT_DANCE);

但请注意,您不能触发所有实现该接口的类。

// DOES NOT WORK. Classes that implement this interface will NOT be triggered.
Event::trigger('app\interfaces\DanceEventInterface', DanceEventInterface::EVENT_DANCE);

要分离事件处理程序,请调用 Event::off()。例如

// detaches $handler
Event::off('app\interfaces\DanceEventInterface', DanceEventInterface::EVENT_DANCE, $handler);

// detaches all handlers of DanceEventInterface::EVENT_DANCE
Event::off('app\interfaces\DanceEventInterface', DanceEventInterface::EVENT_DANCE);

全局事件

Yii 支持所谓的全局事件,这实际上是基于上面描述的事件机制的一种技巧。全局事件需要一个全局可访问的单例,例如 应用程序 实例本身。

要创建全局事件,事件发送者会调用单例的 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]));

使用全局事件的好处是,在将处理程序附加到将由对象触发的事件时,您不需要对象。相反,处理程序附加和事件触发都是通过单例(例如应用程序实例)完成的。

但是,因为全局事件的命名空间由所有各方共享,所以您应该明智地命名全局事件,例如引入某种命名空间(例如“frontend.mail.sent”、“backend.mail.sent”)。

通配符事件

从 2.0.14 开始,您可以为匹配通配符模式的多个事件设置事件处理程序。例如

use Yii;

$foo = new Foo();

$foo->on('foo.event.*', function ($event) {
    // triggered for any event, which name starts on 'foo.event.'
    Yii::debug('trigger event: ' . $event->name);
});

通配符模式也可用于类级事件。例如

use yii\base\Event;
use Yii;

Event::on('app\models\*', 'before*', function ($event) {
    // triggered for any class in namespace 'app\models' for any event, which name starts on 'before'
    Yii::debug('trigger event: ' . $event->name . ' for class: ' . get_class($event->sender));
});

这允许您使用以下代码通过单个处理程序捕获所有应用程序事件

use yii\base\Event;
use Yii;

Event::on('*', '*', function ($event) {
    // triggered for any event at any class
    Yii::debug('trigger event: ' . $event->name);
});

注意:使用事件处理程序设置的通配符可能会降低应用程序性能。如果可能,最好避免使用它。

为了分离由通配符模式指定的事件处理程序,您应该在 yii\base\Component::off()yii\base\Event::off() 调用中重复相同的模式。请记住,在分离事件处理程序时传递通配符只会分离为此通配符指定的处理程序,而即使与模式匹配,为常规事件名称附加的处理程序也将保留。例如

use Yii;

$foo = new Foo();

// attach regular handler
$foo->on('event.hello', function ($event) {
    echo 'direct-handler'
});

// attach wildcard handler
$foo->on('*', function ($event) {
    echo 'wildcard-handler'
});

// detach wildcard handler only!
$foo->off('*');

$foo->trigger('event.hello'); // outputs: 'direct-handler'

发现错别字或您认为此页面需要改进?
在 github 上编辑它 !