Skip to content

事件

介绍

Laravel 的事件提供了一个简单的观察者实现,允许您在应用程序中订阅和监听事件。事件类通常存储在 app/Events 目录中,而它们的监听器存储在 app/Listeners 中。

注册事件/监听器

Laravel 应用程序中包含的 EventServiceProvider 提供了一个方便的地方来注册所有事件监听器。listen 属性包含一个所有事件(键)及其监听器(值)的数组。当然,您可以根据应用程序的需要向此数组添加任意数量的事件。例如,让我们添加我们的 PodcastWasPurchased 事件:

php
/**
 * 应用程序的事件监听器映射。
 *
 * @var array
 */
protected $listen = [
    'App\Events\PodcastWasPurchased' => [
        'App\Listeners\EmailPurchaseConfirmation',
    ],
];

生成事件/监听器类

当然,手动为每个事件和监听器创建文件是繁琐的。相反,只需将监听器和事件添加到您的 EventServiceProvider 并使用 event:generate 命令。此命令将生成在您的 EventServiceProvider 中列出的任何事件或监听器。当然,已经存在的事件和监听器将保持不变:

php
php artisan event:generate

手动注册事件

通常,事件应通过 EventServiceProvider$listen 数组注册;但是,您也可以使用事件调度器手动注册事件,使用 Event facade 或 Illuminate\Contracts\Events\Dispatcher 合约实现:

php
/**
 * 为您的应用程序注册任何其他事件。
 *
 * @param  \Illuminate\Contracts\Events\Dispatcher  $events
 * @return void
 */
public function boot(DispatcherContract $events)
{
    parent::boot($events);

    $events->listen('event.name', function ($foo, $bar) {
        //
    });
}

通配符事件监听器

您甚至可以使用 * 作为通配符注册监听器,允许您在同一监听器上捕获多个事件。通配符监听器接收整个事件数据数组作为单个参数:

php
$events->listen('event.*', function (array $data) {
    //
});

定义事件

事件类只是一个数据容器,用于保存与事件相关的信息。例如,假设我们生成的 PodcastWasPurchased 事件接收一个 Eloquent ORM 对象:

php
<?php

namespace App\Events;

use App\Podcast;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;

class PodcastWasPurchased extends Event
{
    use SerializesModels;

    public $podcast;

    /**
     * 创建一个新的事件实例。
     *
     * @param  Podcast  $podcast
     * @return void
     */
    public function __construct(Podcast $podcast)
    {
        $this->podcast = $podcast;
    }
}

如您所见,此事件类不包含任何逻辑。它只是一个 Podcast 对象的容器,该对象已被购买。事件使用的 SerializesModels trait 将在事件对象使用 PHP 的 serialize 函数序列化时优雅地序列化任何 Eloquent 模型。

定义监听器

接下来,让我们看看我们示例事件的监听器。事件监听器在其 handle 方法中接收事件实例。event:generate 命令将自动导入正确的事件类并在 handle 方法上进行类型提示。在 handle 方法中,您可以执行任何必要的逻辑来响应事件。

php
<?php

namespace App\Listeners;

use App\Events\PodcastWasPurchased;

class EmailPurchaseConfirmation
{
    /**
     * 创建事件监听器。
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * 处理事件。
     *
     * @param  PodcastWasPurchased  $event
     * @return void
     */
    public function handle(PodcastWasPurchased $event)
    {
        // 使用 $event->podcast 访问播客...
    }
}

您的事件监听器还可以在其构造函数上进行类型提示任何依赖项。所有事件监听器都是通过 Laravel 服务容器 解析的,因此依赖项将自动注入:

php
use Illuminate\Contracts\Mail\Mailer;

public function __construct(Mailer $mailer)
{
    $this->mailer = $mailer;
}

停止事件的传播

有时,您可能希望停止事件向其他监听器的传播。您可以通过从监听器的 handle 方法返回 false 来实现这一点。

队列事件监听器

需要 队列 一个事件监听器吗?这再简单不过了。只需将 ShouldQueue 接口添加到监听器类中。由 event:generate Artisan 命令生成的监听器已经在当前命名空间中导入了此接口,因此您可以立即使用它:

php
<?php

namespace App\Listeners;

use App\Events\PodcastWasPurchased;
use Illuminate\Contracts\Queue\ShouldQueue;

class EmailPurchaseConfirmation implements ShouldQueue
{
    //
}

就是这样!现在,当此监听器被事件调用时,它将由事件调度器使用 Laravel 的 队列系统 自动排队。如果在队列执行监听器时没有抛出异常,则队列作业将在处理后自动删除。

手动访问队列

如果您需要手动访问底层队列作业的 deleterelease 方法,可以这样做。Illuminate\Queue\InteractsWithQueue trait,默认情况下在生成的监听器上导入,提供了对这些方法的访问:

php
<?php

namespace App\Listeners;

use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class EmailPurchaseConfirmation implements ShouldQueue
{
    use InteractsWithQueue;

    public function handle(PodcastWasPurchased $event)
    {
        if (true) {
            $this->release(30);
        }
    }
}

触发事件

要触发事件,您可以使用 Event facade,将事件实例传递给 fire 方法。fire 方法将事件分派给其所有注册的监听器:

php
<?php

namespace App\Http\Controllers;

use Event;
use App\Podcast;
use App\Events\PodcastWasPurchased;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * 显示给定用户的个人资料。
     *
     * @param  int  $userId
     * @param  int  $podcastId
     * @return Response
     */
    public function purchasePodcast($userId, $podcastId)
    {
        $podcast = Podcast::findOrFail($podcastId);

        // 购买播客逻辑...

        Event::fire(new PodcastWasPurchased($podcast));
    }
}

或者,您可以使用全局 event 辅助函数来触发事件:

php
event(new PodcastWasPurchased($podcast));

广播事件

在许多现代 Web 应用程序中,Web 套接字用于实现实时、实时更新的用户界面。当服务器上的某些数据更新时,通常会通过 Web 套接字连接发送消息以由客户端处理。

为了帮助您构建这些类型的应用程序,Laravel 使您可以轻松地通过 Web 套接字连接“广播”您的事件。广播您的 Laravel 事件允许您在服务器端代码和客户端 JavaScript 框架之间共享相同的事件名称。

配置

所有事件广播配置选项都存储在 config/broadcasting.php 配置文件中。Laravel 开箱即用地支持几种广播驱动程序:PusherRedis 和一个用于本地开发和调试的 log 驱动程序。每个驱动程序都包含一个配置示例。

广播先决条件

事件广播需要以下依赖项:

  • Pusher: pusher/pusher-php-server ~2.0
  • Redis: predis/predis ~1.0

队列先决条件

在广播事件之前,您还需要配置并运行一个 队列监听器。所有事件广播都是通过队列作业完成的,以便不会严重影响应用程序的响应时间。

标记事件以进行广播

要通知 Laravel 某个事件应被广播,请在事件类上实现 Illuminate\Contracts\Broadcasting\ShouldBroadcast 接口。ShouldBroadcast 接口要求您实现一个方法:broadcastOnbroadcastOn 方法应返回事件应广播的“频道”名称数组:

php
<?php

namespace App\Events;

use App\User;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class ServerCreated extends Event implements ShouldBroadcast
{
    use SerializesModels;

    public $user;

    /**
     * 创建一个新的事件实例。
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * 获取事件应广播的频道。
     *
     * @return array
     */
    public function broadcastOn()
    {
        return ['user.'.$this->user->id];
    }
}

然后,您只需像往常一样 触发事件。一旦事件被触发,一个 队列作业 将自动通过您指定的广播驱动程序广播事件。

广播数据

当事件被广播时,其所有 public 属性将自动序列化并作为事件的有效负载广播,允许您从 JavaScript 应用程序访问其任何公共数据。例如,如果您的事件有一个包含 Eloquent 模型的公共 $user 属性,则广播有效负载将是:

json
{
    "user": {
        "id": 1,
        "name": "Jonathan Banks"
        ...
    }
}

但是,如果您希望对广播有效负载进行更细粒度的控制,可以向事件添加一个 broadcastWith 方法。此方法应返回您希望与事件一起广播的数据数组:

php
/**
 * 获取要广播的数据。
 *
 * @return array
 */
public function broadcastWith()
{
    return ['user' => $this->user->id];
}

事件广播自定义

自定义事件名称

默认情况下,广播事件名称将是事件的完全限定类名。因此,如果事件的类名是 App\Events\ServerCreated,则广播事件将是 App\Events\ServerCreated。您可以通过在事件类上定义 broadcastAs 方法来自定义此广播事件名称:

php
/**
 * 获取广播事件名称。
 *
 * @return string
 */
public function broadcastAs()
{
    return 'app.server-created';
}

自定义队列

默认情况下,每个要广播的事件都放在 queue.php 配置文件中默认队列连接的默认队列上。您可以通过向事件类添加 onQueue 方法来自定义事件广播器使用的队列。此方法应返回您希望使用的队列名称:

php
/**
 * 设置事件应放置的队列名称。
 *
 * @return string
 */
public function onQueue()
{
    return 'your-queue-name';
}

消费事件广播

Pusher

您可以使用 Pusher 的 JavaScript SDK 方便地消费使用 Pusher 驱动程序广播的事件。例如,让我们消费之前示例中的 App\Events\ServerCreated 事件:

javascript
this.pusher = new Pusher('pusher-key');

this.pusherChannel = this.pusher.subscribe('user.' + USER_ID);

this.pusherChannel.bind('App\\Events\\ServerCreated', function(message) {
    console.log(message.user);
});

Redis

如果您使用的是 Redis 广播器,则需要编写自己的 Redis 发布/订阅消费者来接收消息并使用您选择的 Web 套接字技术广播它们。例如,您可以选择使用流行的 Socket.io 库,该库是用 Node 编写的。

使用 socket.ioioredis Node 库,您可以快速编写一个事件广播器来发布由您的 Laravel 应用程序广播的所有事件:

javascript
var app = require('http').createServer(handler);
var io = require('socket.io')(app);

var Redis = require('ioredis');
var redis = new Redis();

app.listen(6001, function() {
    console.log('Server is running!');
});

function handler(req, res) {
    res.writeHead(200);
    res.end('');
}

io.on('connection', function(socket) {
    //
});

redis.psubscribe('*', function(err, count) {
    //
});

redis.on('pmessage', function(subscribed, channel, message) {
    message = JSON.parse(message);
    io.emit(channel + ':' + message.event, message.data);
});

事件订阅者

事件订阅者是可以在类本身中订阅多个事件的类,允许您在单个类中定义多个事件处理程序。订阅者应定义一个 subscribe 方法,该方法将传递一个事件调度器实例:

php
<?php

namespace App\Listeners;

class UserEventListener
{
    /**
     * 处理用户登录事件。
     */
    public function onUserLogin($event) {}

    /**
     * 处理用户注销事件。
     */
    public function onUserLogout($event) {}

    /**
     * 为订阅者注册监听器。
     *
     * @param  Illuminate\Events\Dispatcher  $events
     */
    public function subscribe($events)
    {
        $events->listen(
            'App\Events\UserLoggedIn',
            'App\Listeners\UserEventListener@onUserLogin'
        );

        $events->listen(
            'App\Events\UserLoggedOut',
            'App\Listeners\UserEventListener@onUserLogout'
        );
    }
}

注册事件订阅者

定义订阅者后,可以将其注册到事件调度器。您可以使用 EventServiceProvider 上的 $subscribe 属性注册订阅者。例如,让我们添加 UserEventListener

php
<?php

namespace App\Providers;

use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * 应用程序的事件监听器映射。
     *
     * @var array
     */
    protected $listen = [
        //
    ];

    /**
     * 要注册的订阅者类。
     *
     * @var array
     */
    protected $subscribe = [
        'App\Listeners\UserEventListener',
    ];
}