[译]Laravel 5.0 之事件及处理程序

本文译自 Matt Stauffer系列文章.


提示:如果你还没有看过 Laravel 5.0 之命令及处理程序 这篇文章,建议先看一下。它包括了本文所需的背景知识。

借助 Laravel 5 的命令(及命令处理程序),你可以通过封装的方式非常简单、直接地向系统发出命令。DoThis.HandleACommandThatIsTellingMeToDoThis, 它是命令式的,告诉系统要做什么事。

但有时候,不管是在命令结果中,还是在其它的上下文中,我们需要发出更抽象的通知。比如在 Laravel 4 中,可以直接以事件名称的字符串来触发事件(而不是像上面那样通过对象和方法):

$response = Event::fire('auth.login', array($user));

这行代码向整个应用发出一条通知:“有人登陆了,这是用户信息”。它是知会式的,只是知会事件发生比提供相关信息。如果你熟悉“发布/订阅”概念的话,这就是“事件”机制要处理的。

在 Laravel 5 中,事件系统已经得到了升级,看上去与上一篇文章中介绍过的命令系统有几分相似。在升级后的事件系统中,不是基于字符串来标识事件(比如 “auth.login”),而是创建一个 PHP 对象,并发布它。

示例

接下来,直接以例子来做说明:

生成事件

$ php artisan make:event ThingWasDone

… 这行命令会生成下面的代码:

namespace SaveMyProposals\Events;

use SaveMyProposals\Events\Event;

use Illuminate\Queue\SerializesModels;

class ThingWasDone extends Event {

    use SerializesModels;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

}

你可以给构造函数添加参数并把参数值绑定到类的属性值,通过这样的方式为这个类提供额外的数据。

生成事件处理程序

执行命令:

$ php artisan handler:event SendMailInSomeParticularContext --event="ThingWasDone"

这行代码会生成下面的代码:

namespace SaveMyProposals\Handlers\Events;

use SaveMyProposals\Events\ThingWasDone;

use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldBeQueued;

class SendMailInSomeParticularContext {

    /**
     * Create the event handler.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  ThingWasDone  $event
     * @return void
     */
    public function handle(ThingWasDone $event)
    {
        //
    }

}

需要注意的一点是,生成的代码中,已经为 handle 方法指定了一个带有类型约束的 ThingWasDone $event 参数。不管是构造函数还是 event 方法,你都可以借助依赖注入来提供任何你需要的工具或对象。

绑定事件

在上一个步骤中我们创建了一个事件及其处理程序,但仅仅是创建,并没有通知事件总线(bus)我们刚才创建的事件和处理程序是配对的。所以接下来还需要在 app\Providers\EventServiceProvider 中绑定它们的监听关系,可以在 $listen 属性中做这件事:

// app\Providers\EventServiceProvider
    $listen = [
        ThingWasDone::class => [
            SendMailInSomeParticularContext::class,
            SaveSomeRecordInSomeOtherContext::class,
            DoSomethingElseInResponseToThingBeingDone::class
        ]
    ];

如你所见,通过 ::class 得到一个代表事件类名的字符串作为 key,然后在值数组中添加监听器(也是通过 ::class)。

“预备…瞄准…开火(::fire)”

好了,一切准备就绪,接下来就是触发该事件了。要注意的是这里只有简单的 PHP 类,所以你可以手动实例化事件,实例化事件对应的处理程序,然后把事件传递给处理程序。但那当然不是 Laravel 的思路, Laravel 提供了事件总线让以上这一系列的工作更简单,更具有一致性和全局性:

\Event::fire(new ThingWasDone($param1, $param2));

就这么简单!

ShouldBeQueued

与命令系统的机制一样,你可以让你的事件实现 Illuminate\Contracts\Queue\ShouldBeQueued 接口,从而使事件处理程序被加入到队列中异步执行;也可以给你的事件处理程序加上 Illuminate\Queue\InteractsWithQueue 的 trait,使事件处理程序的 handle 方法变得容易从外部访问,从而使事件处理程序可以和事件队列进行交互。比如在队列中删除当前的任务。

SerializesModels trait

还是与命令一样的,如果你需要在事件中用到某个 Eloquent 模型,你可以在事件类的代码顶部包含 SerializesModels 这个 trait。在本文写作时,生成的时间代码实际上已经默认包含了这部分。

写在最后

就这么多了。只要你理解了 Laravel 5 的命令和处理程序,掌握事件处理机制就是一件非常容易的事了。触发系统向整个应用发出通知说某个事件发生了,而不是要求系统执行某些操作。但本质上它们都是封装的信息和目的。它们可以互相配合使用,结果会更棒!