事件
介绍
Laravel 的事件提供了一个简单的观察者实现,允许您在应用程序中订阅和监听事件。事件类通常存储在 app/Events
目录中,而它们的监听器存储在 app/Listeners
中。
注册事件/监听器
Laravel 应用程序中包含的 EventServiceProvider
提供了一个方便的地方来注册所有事件监听器。listen
属性包含一个所有事件(键)及其监听器(值)的数组。当然,您可以根据应用程序的需要向此数组添加任意数量的事件。例如,让我们添加我们的 PodcastWasPurchased
事件:
/**
* 应用程序的事件监听器映射。
*
* @var array
*/
protected $listen = [
'App\Events\PodcastWasPurchased' => [
'App\Listeners\EmailPurchaseConfirmation',
],
];
生成事件/监听器类
当然,手动为每个事件和监听器创建文件是繁琐的。相反,只需将监听器和事件添加到您的 EventServiceProvider
并使用 event:generate
命令。此命令将生成在您的 EventServiceProvider
中列出的任何事件或监听器。当然,已经存在的事件和监听器将保持不变:
php artisan event:generate
手动注册事件
通常,事件应通过 EventServiceProvider
的 $listen
数组注册;然而,您也可以使用事件调度器手动注册事件,使用 Event
facade 或 Illuminate\Contracts\Events\Dispatcher
合约实现:
/**
* 为您的应用程序注册任何其他事件。
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @return void
*/
public function boot(DispatcherContract $events)
{
parent::boot($events);
$events->listen('event.name', function ($foo, $bar) {
//
});
}
通配符事件监听器
您甚至可以使用 *
作为通配符注册监听器,允许您在同一监听器上捕获多个事件。通配符监听器接收整个事件数据数组作为单个参数:
$events->listen('event.*', function (array $data) {
//
});
定义事件
事件类只是一个数据容器,用于保存与事件相关的信息。例如,假设我们生成的 PodcastWasPurchased
事件接收一个 Eloquent ORM 对象:
<?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
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation
{
/**
* 创建事件监听器。
*
* @return void
*/
public function __construct()
{
//
}
/**
* 处理事件。
*
* @param PodcastWasPurchased $event
* @return void
*/
public function handle(PodcastWasPurchased $event)
{
// 使用 $event->podcast 访问播客...
}
}
您的事件监听器还可以在其构造函数上进行类型提示任何依赖项。所有事件监听器都是通过 Laravel 服务容器 解析的,因此依赖项将自动注入:
use Illuminate\Contracts\Mail\Mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
停止事件的传播
有时,您可能希望停止事件向其他监听器的传播。您可以通过从监听器的 handle
方法返回 false
来实现。
队列事件监听器
需要将事件监听器 入队 吗?这再简单不过了。只需将 ShouldQueue
接口添加到监听器类。由 event:generate
Artisan 命令生成的监听器已经在当前命名空间中导入了此接口,因此您可以立即使用它:
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation implements ShouldQueue
{
//
}
就是这样!现在,当此监听器被事件调用时,它将由事件调度器使用 Laravel 的 队列系统 自动入队。如果在队列执行监听器时没有抛出异常,则在处理后,队列作业将自动删除。
手动访问队列
如果您需要手动访问底层队列作业的 delete
和 release
方法,可以这样做。Illuminate\Queue\InteractsWithQueue
trait,默认情况下由生成的监听器导入,提供了对这些方法的访问:
<?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
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
辅助函数来触发事件:
event(new PodcastWasPurchased($podcast));
广播事件
在许多现代 Web 应用程序中,Web 套接字用于实现实时、实时更新的用户界面。当服务器上的某些数据更新时,通常会通过 Web 套接字连接发送一条消息,以由客户端处理。
为了帮助您构建这些类型的应用程序,Laravel 使得“广播”您的事件通过 Web 套接字连接变得容易。广播您的 Laravel 事件允许您在服务器端代码和客户端 JavaScript 框架之间共享相同的事件名称。
配置
所有事件广播配置选项都存储在 config/broadcasting.php
配置文件中。Laravel 支持多种广播驱动程序: Pusher、Redis 和用于本地开发和调试的 log
驱动程序。每个驱动程序都包含一个配置示例。
广播先决条件
事件广播需要以下依赖项:
- Pusher:
pusher/pusher-php-server ~2.0
- Redis:
predis/predis ~1.0
队列先决条件
在广播事件之前,您还需要配置并运行一个 队列监听器。所有事件广播都是通过队列作业完成的,以便不会严重影响应用程序的响应时间。
标记事件以进行广播
要通知 Laravel 某个事件应被广播,请在事件类上实现 Illuminate\Contracts\Broadcasting\ShouldBroadcast
接口。ShouldBroadcast
接口要求您实现一个方法:broadcastOn
。broadcastOn
方法应返回事件应广播的“频道”名称数组:
<?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];
}
}
然后,您只需像往常一样 触发事件。一旦事件被触发,一个 队列作业 将自动通过您指定的广播驱动程序广播事件。
覆盖广播事件名称
默认情况下,广播事件名称将是事件的完全限定类名。使用上面的示例类,广播事件将是 App\Events\ServerCreated
。您可以使用 broadcastAs
方法自定义此广播事件名称:
/**
* 获取广播事件名称。
*
* @return string
*/
public function broadcastAs()
{
return 'app.server-created';
}
广播数据
当事件被广播时,其所有 public
属性将自动序列化并作为事件的有效负载广播,允许您从 JavaScript 应用程序访问其任何公共数据。例如,如果您的事件有一个包含 Eloquent 模型的公共 $user
属性,则广播有效负载将是:
{
"user": {
"id": 1,
"name": "Jonathan Banks"
...
}
}
但是,如果您希望对广播有效负载进行更细粒度的控制,可以向事件添加一个 broadcastWith
方法。此方法应返回您希望与事件一起广播的数据数组:
/**
* 获取要广播的数据。
*
* @return array
*/
public function broadcastWith()
{
return ['user' => $this->user->id];
}
消费事件广播
Pusher
您可以方便地使用 Pusher 的 JavaScript SDK 消费使用 Pusher 驱动程序广播的事件。例如,让我们消费之前示例中的 App\Events\ServerCreated
事件:
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.io
和 ioredis
Node 库,您可以快速编写一个事件广播器来发布您的 Laravel 应用程序广播的所有事件:
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
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
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',
];
}
框架事件
Laravel 提供了多种“核心”事件,用于框架执行的操作。您可以像订阅自己的自定义事件一样订阅它们:
事件 | 参数 |
---|---|
artisan.start | $application |
auth.attempt | $credentials, $remember, $login |
auth.login | $user, $remember |
auth.logout | $user |
cache.missed | $key |
cache.hit | $key, $value |
cache.write | $key, $value, $minutes |
cache.delete | $key |
connection.{name}.beganTransaction | $connection |
connection.{name}.committed | $connection |
connection.{name}.rollingBack | $connection |
illuminate.query | $query, $bindings, $time, $connectionName |
illuminate.queue.after | $connection, $job, $data |
illuminate.queue.failed | $connection, $job, $data |
illuminate.queue.stopping | null |
mailer.sending | $message |
router.matched | $route, $request |
composing: | $view |
creating: | $view |