事件系统
介绍
Laravel 的事件系统提供了一个简单的观察者模式的实现,允许你能够订阅和监听在你的应用中的发生的各种事件。事件类一般来说存储在 app/Events
目录,监听者的类存储在 app/Listeners
目录。不要担心在你的应用中没有看到这两个目录,因为通过 Artisan 命令行来创建事件和监听者的时候目录会同时被创建。
事件系统可以作为一个非常棒的方式来解耦你的系统的方方面面,因为一个事件可以有多个完全不相关的监听者。例如,你希望每当有订单发出的时候都给你发送一个 Slack 通知。你大可不必将你的处理订单的代码和发送 slack 消息的代码放在一起,你只需要触发一个 App\Events\OrderShipped
事件,然后事件监 听者可以收到这个事件然后发送 slack 通知
注册事件和监听器
在系统的服务提供者 App\Providers\EventServiceProvider
中提供了一个简单的方式来注册你所有的事件监听者。属性 listen
包含所有的事件 (作为键) 和对应的监听器 (值)。你可以添加任意多系统需要的监听器在这个数组中,让我们添加一个 OrderShipped
事件:
use App\Events\OrderShipped;
use App\Listeners\SendShipmentNotification;
/**
* 系统中的事件和监听器的对应关系。
*
* @var array
*/
protected $listen = [
OrderShipped::class => [
SendShipmentNotification::class,
],
];
技巧:可以用 Artisan 命令行 event:list
来显示系统注册的事件和监听器的列表。
生成事件和监听器
当然,为每个事件和监听器手动创建文件是很麻烦的。相反,将监听器和事件添加到 EventServiceProvider
并使用 event:generate
Artisan 命令。此命令将生成 EventServiceProvider
中列出的、尚不存在的任何事件或侦听器:
php artisan event:generate
或者,你可以使用 make:event
以及 make:listener
用于生成单个事件和监听器的 Artisan 命令:
php artisan make:event PodcastProcessed
php artisan make:listener SendPodcastNotification --event=PodcastProcessed
手动注册事件
通常,事件应该通过 EventServiceProvider
$listen
数组注册;但是,你也可以在 EventServiceProvider
的 boot
方法中手动注册基于类或闭包的事件监听器:
use App\Events\PodcastProcessed;
use App\Listeners\SendPodcastNotification;
use Illuminate\Support\Facades\Event;
/**
* 注册任意的其他事件和监听器。
*
* @return void
*/
public function boot()
{
Event::listen(
PodcastProcessed::class,
[SendPodcastNotification::class, 'handle']
);
Event::listen(function (PodcastProcessed $event) {
//
});
}
可排队匿名事件监听器
手动注册基于闭包的事件监听器时,可以将监听器闭包包装在 Illuminate\Events\queueable
函数中,以指示 Laravel 使用 队列 执行侦听器:
use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;
/**
* 注册任意的其他事件和监听器。
*
* @return void
*/
public function boot()
{
Event::listen(queueable(function (PodcastProcessed $event) {
//
}));
}
与队列任务一样,可以使用 onConnection
、onQueue
和 delay
方法自定义队列监听器的执行:
Event::listen(queueable(function (PodcastProcessed $event) {
//
})->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10)));
如果你想处理匿名队列监听器失败,你可以在定义 queueable
监听器时为 catch
方法提供一个闭包。这个闭包将接收导致监听器失败的事件实例和 Throwable
实例:
use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;
use Throwable;
Event::listen(queueable(function (PodcastProcessed $event) {
//
})->catch(function (PodcastProcessed $event, Throwable $e) {
// 队列监听器
}));
通配符事件监听器
您甚至可以使用 *
作为通配符参数注册监听器,允许您在同一个监听器上捕获多个事件。通配符监听器接收事件名作为其第一个参数,整个事件数据数组作为其第二个参数:
Event::listen('event.*', function ($eventName, array $data) {
//
});
事件的发现
您可以启用自动事件发现,而不是在 EventServiceProvider
的 $listen
数组中手动注册事件和侦听器。当事件发现启用,Laravel 将自动发现和注册你的事件和监听器扫描你的应用程序的 Listeners
目录。此外,在 EventServiceProvider
中列出的任何显式定义的事件仍将被注册。
Laravel 通过使用 PHP 的反射服务扫描监听器类来查找事件监听器。当 Laravel 发现任何以 handle
或 __invoke
开头的监听器类方法时,Laravel 会将这些方法注册为该方法签名中类型暗示的事件的事件监听器:
use App\Events\PodcastProcessed;
class SendPodcastNotification
{
/**
* 处理给定的事件 * * @param \App\Events\PodcastProcessed $event * @return void */ public function handle(PodcastProcessed $event) { // } }
事件发现在默认情况下是禁用的,但您可以通过重写应用程序的 EventServiceProvider
的 shouldDiscoverEvents
方法来启用它:
/**
* 确定是否应用自动发现事件和监听器。
*
* @return bool
*/
public function shouldDiscoverEvents()
{
return true;
}
默认情况下,应用程序 app/listeners
目录中的所有监听器都将被扫描。如果你想要定义更多的目录来扫描,你可以重写 EventServiceProvider
中的 discoverEventsWithin
方法:
/**
* 获取应用于发现事件的监听器目录。
*
* @return array
*/
protected function discoverEventsWithin()
{
return [
$this->app->path('Listeners'),
];
}
生产中的事件发现
在生产环境中,框架在每个请求上扫描所有监听器的效率并不高。因此,在你的部署过程中,你应该运行 event:cache
Artisan 命令来缓存你的应用程序的所有事件和监听器清单。框架将使用该清单来加速事件注册过程。event:clear
命令可以用来销毁缓存。
定义事件
事件类本质上是一个数据容器,它保存与事件相关的信息。例如,让我们假设一个 App\Events\OrderShipped
事件接收到一个 Eloquent ORM 对象:
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderShipped
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* 订单实例。
*
* @var \App\Models\Order
*/
public $order;
/**
* 创建一个新的事件实例。
*
* @param \App\Models\Order $order
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
}
如您所见,这个事件类不包含逻辑。它是一个被购买的 App\Models\Order
实例容器。 如果事件对象是使用 PHP 的 SerializesModels
函数序列化的,事件使用的 SerializesModels
trait 将会优雅地序列化任何 Eloquent 模型,比如在使用 队列侦听器。
定义监听器
接下来,让我们看一下示例事件的侦听器。事件监听器在其 handle
方法中接收事件实例。 artisan
命令 event:generate
和 make:listener
会自动导入正确的事件类,并在 handle 方法中注入提示事件。 在 handle
方法中,你可以执行任何必要的操作来响应事件:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
class SendShipmentNotification
{
/**
* 创建事件监听器
*
* @return void
*/
public function __construct()
{
//
}
/**
* 处理事件
*
* @param \App\Events\OrderShipped $event
* @return void
*/
public function handle(OrderShipped $event)
{
// 使用 $event->order 来访问订单 ...
}
}
技巧:事件监听器还可以在构造函数中加入任何依赖关系的类型提示。所有的事件监听器都是通过 Laravel 的 服务器容器 解析的,因此所有的依赖都将会被自动注入。
停止事件传播
有时,您可能希望停止将事件传播到其他侦听器。你可以通过从监听器的 handle
方法返回 false
来做到这一点。
事件监听器队列
如果侦听器执行缓慢的任务如发送电子邮件或发出 HTTP 请求,你可以将任务丢给队列处理。在开始使用队列监听器之前,请确保在你的服务器或者本地开发环境中能够 配置队列 并启动一个队列监听器。
要指定监听器启动队列,请将 ShouldQueue
接口添加到监听器类。 由 Artisan 命令 event:generate
和 make:listener
生成的监听器已经将此接口导入当前命名空间,因此您可以直接使用:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
//
}
就是这样!现在 ,当这个监听器被事件调用时,事件调度器会自动使用 Laravel 的 队列系统 自动排队。如果在队列中执行监听器时没有抛出异常,任务会在执行完成后自动从队列中删除。
自定义队列连接 & 队列名称
如果你想自定义事件监听器的队列连接、队列名称或延迟队列时间,你可以在监听器类上定义 $connection
、$queue
或 $delay
属性:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
/**
* 任务将被发送到的连接的名称。
*
* @var string|null
*/
public $connection = 'sqs';
/**
* 任务将被发送到的队列的名称。
*
* @var string|null
*/
public $queue = 'listeners';
/**
* 任务被处理的延迟时间(秒)。
*
* @var int
*/
public $delay = 60;
}
如果您想在运行时定义监听器的队列连接或队列名称,您可以在监听器上定义 viaConnection
或 viaQueue
方法:
/**
* 获取监听器的队列连接名称。
*
* @return string
*/
public function viaConnection()
{
return 'sqs';
}
/**
* 获取监听器队列的名称。
*
* @return string
*/
public function viaQueue()
{
return 'listeners';
}
条件监听队列
有时,您可能需要根据一些仅在运行时可用的数据来确定监听器是否应该排队。为此,可以将 shouldQueue
方法添加到监听器中,以确定监听器是否应该排队。如果 shouldQueue
方法返回 false
,则监听器将不会执行:
<?php
namespace App\Listeners;
use App\Events\OrderCreated;
use Illuminate\Contracts\Queue\ShouldQueue;
class RewardGiftCard implements ShouldQueue
{
/**
* 给客户奖励礼品卡。
*
* @param \App\Events\OrderCreated $event
* @return void
*/
public function handle(OrderCreated $event)
{
//
}
/**
* 确定监听器是否应加入队列。
*
* @param \App\Events\OrderCreated $event
* @return bool
*/
public function shouldQueue(OrderCreated $event)
{
return $event->order->subtotal >= 5000;
}
}
手动访问队列
如果你需要手动访问监听器下面队列任务的 delete
和 release
方法,可以使用 Illuminate\Queue\InteractsWithQueue
trait 进行访问。默认情况下,此 trait 在生成的监听器上导入,并提供对以下方法的访问:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;
/**
* 事件处理。
*
* @param \App\Events\OrderShipped $event
* @return void
*/
public function handle(OrderShipped $event)
{
if (true) {
$this->release(30);
}
}
}