使用Laravel事件创建实时喊话箱
介绍
Laravel 无疑是一个非常强大的框架,其中包含很多电池。 我喜欢 Laravel 的原因之一是它建立在事件之上。
什么是 Laravel 事件?
事件是由程序识别的可以由程序处理的动作或事件。 Laravel 中的一些事件示例如下:
- 新用户已注册
- 发表了一条评论
- 用户创建博客文章
- 用户喜欢一张照片
- 多得多…
为什么使用事件?
我们使用事件的原因是它们允许我们分离应用程序的关注点并为我们创建一种机制来挂钩我们应用程序中的操作。 当一个事件被触发时,事件不需要知道任何关于它的实现,所有事件需要知道的是一个动作被执行,事件被触发,事件发送一些数据到某个地方的监听器或订阅者处理它。
没有事件,大量逻辑集中在一个地方
为了展示一个应用程序在没有事件的情况下如何开始,我们将执行以下操作:
Route::get('signup', function() {
// create the new user
$user = User::create($userData);
// a user is created, do things!
// send the user a confirmation email
// set up that users account
// email them introductory information
// do more stuff
});
我们可以看到列表如何开始变得有点长。 我们可以将其分离为一个事件,以便将许多逻辑与事件本身分离。
我们使用事件的另一个原因是加速应用程序数据处理。
注意:如果您已经熟悉 Pub Sub,事件不应该是一个新概念。
何时使用事件
仍然使用社交博客应用程序,当用户创建博客文章时,我们可能有一个要做的事情清单,例如:
- 使用 REST API 检查帖子是否存在抄袭。
- 通知新帖子的关注者。
- 将帖子提交到社交网络和搜索引擎等。
像这样的衬里操作会减慢您的应用程序的速度,并且可以使用事件来消除这个瓶颈。
Laravel 还会触发很多系统事件,例如:
- 每当 Eloquent 执行任何 CRUD 操作时,Laravel 都会触发一个事件。
- 当 Laravel 编译刀片模板时,会触发一个事件。
这些只是 Laravel 触发的一些事件。
定义我们的活动
要在 Laravel 中生成事件,我们只需使用旧的 artisan
php artisan make:event ActionDone
这将在 app/Events 目录中创建一个名为 ActionDone 的类,它应该如下所示:
<?php namespace App\Events;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class ActionDone extends Event {
use SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the channels the event should be broadcast on.
*
* @return array
*/
public function broadcastOn()
{
return [];
}
}
事件创建非常简单,只需要很少的设置。
事件监听器(事件触发时)
触发事件后,应用程序需要知道如何处理该事件,为此我们需要一个 Listener。 要在 Laravel 中生成监听器,我们可以简单地使用 artisan:
php artisan make:listener ThingToDoAfterEventWasFired --event="ActionDone"
为事件生成侦听器的命令接受一个必需的 --event 标志和要侦听的事件的名称。 上述命令在 app/Listeners 中创建了一个名为 ThingToDoAfterEventWasFired 的类,并包含;
<?php namespace App\Listeners;
use App\Events\ActionDone;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class ThingToDoAfterEventWasFired {
/**
* Create the event listener.
*
* @return void
*/
public function __construct() { // }
/**
* Handle the event.
*
* @param ActionDone $event
* @return void
*/
public function handle(ActionDone $event)
{
//
}
}
在事件侦听器类中,handle 方法由 Laravel 以编程方式触发,它类型提示触发事件的类。
注册事件
在我们到处触发事件之前,Laravel 的命令总线需要了解事件及其监听器,以及如何处理它们。
要注册事件,我们导航到 app/Providers/EventServiceProvider.php,我们在 EventServiceProvider 类中找到受保护的侦听属性,并且:
protected $listen = [
'App\Events\ActionDone' => [
'App\Listeners\ThingToDoAfterEventWasFired',
],
];
这就是我们需要做的所有事情来让命令总线知道一个事件及其监听器。 请注意,有一个侦听器数组,这意味着事件可以有多个订阅者。
要让一个事件触发多个侦听器,只需添加到该数组:
protected $listen = [
'App\Events\ActionDone' => [
'App\Listeners\ThingToDoAfterEventWasFired',
'App\Listeners\OtherThingToDoAfterEventWasFired',
'App\Listeners\AnotherThingToDoAfterEventWasFired',
],
];
事件订阅者
事件订阅者是可以从类本身订阅多个事件的类,允许您在单个类中定义多个事件处理程序。 在上面的示例中,我们明确命名了 EventServiceProvider.php 文件中事件的侦听器。 我们将在一个新的 UserEventListener.php 文件中定义事件和监听器。
订阅者应该定义一个订阅方法,该方法将传递一个事件调度程序实例:
<?php namespace App\Listeners;
class UserEventListener {
/**
* Handle user login events.
*/
public function onUserLogin($event) {}
/**
* Handle user logout events.
*/
public function onUserLogout($event) {}
public function subscribe($events)
{
$events->listen(
'App\Events\UserLoggedIn',
'App\Listeners\UserEventListener@onUserLogin'
);
$events->listen(
'App\Events\UserLoggedOut',
'App\Listeners\UserEventListener@onUserLogout'
);
}
}
要注册事件订阅者,请返回 EventServiceProvider 类,并在 subscribe 属性中添加订阅者类:
protected $subscribe = [
'App\Listeners\UserEventListener',
];
现在在 UserEventListener 中创建的所有事件和侦听器都将起作用。
调度事件
要触发事件,我们可以使用 Event Facade 来触发事件
use Event;
use App\Events\ActionDone;
...
Event::fire(new ActionDone());
或者我们可以使用 event 辅助方法来触发事件
use App\Events\ActionDone;
...
event(new ActionDone());
要将数据传递给侦听器,我们可以简单地将事件类用作 DTO。
排队事件监听器
有时,我们不希望事件/侦听器阻止我们的应用程序的处理。 例如,我们不希望用户在发送创建新帐户的请求时必须等待他们的新用户注册电子邮件收到电子邮件。
我们可以将事件侦听器添加到我们的应用程序队列中,它将由您指定的任何队列驱动程序处理。 这不会阻碍我们的应用程序处理。 为此,我们只需让 Listener 类实现 Illuminate\Contracts\Queue\ShouldQueue。
<?php namespace App\Listeners;
use App\Events\ActionDone;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class ThingToDoAfterEventWasFired implements ShouldQueue { /*...*/ }
实时广播事件
在许多现代 Web 应用程序中,WebSockets 用于实现实时、实时更新的用户界面。 当服务器上的某些数据更新时,通常会通过 WebSocket 连接发送一条消息,由客户端处理。
要使事件可广播,请使事件类实现 Illuminate\Contracts\Queue\ShouldBroadcast
<?php namespace App\Events;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class ActionDone extends Event implements ShouldBroadcast { /*...*/ }
ShouldBroadcast 接口要求事件类实现 broadcastOn 方法,该方法返回事件应在其上广播的频道数组。
public function broadcastOn()
{
return ['action-did-occur'];
}
默认情况下,Laravel 会序列化事件的公共属性并将其作为 JSON 发送给客户端。 如果您想更好地控制发送到客户端的内容,您可以将 broadcastWith 方法添加到事件类并返回要转换为 JSON 的数据。
public function broadcastWith()
{
return [
'user' => [
'name' => 'Klark Cent',
'age' => 30,
'planet' => 'Crypton',
'abilities' => 'Bashing'
]
];
}
目前,Laravel 只有两个驱动程序(pusher 和 socket.io)来帮助客户端的数据消耗。 在本文的范围内,我们将使用 pusher。
Pusher 是一种 WebSocket 即服务,您从应用程序向服务发送请求,然后 pusher 在所有客户端上广播消息。
要使用 pusher 在客户端使用数据,只需执行以下操作:
var pusher = new Pusher('pusher-key');
var channel = pusher.subscribe('action-did-occur');
channel.bind('App\Events\ActionDone', function(data) {
console.log(data);
});
注意:页面需要pusher的JavaScript SDK:
<script src="//js.pusher.com/2.2/pusher.min.js"></script>
您还需要 pusher 的 PHP SDK,可通过 composer 获得:
composer require pusher/pusher-php-server:~2.0
创建喊话箱
演示应用程序是一个简单的喊话框,用户在表格中填写他们的 Twitter 句柄、电子邮件地址以及他们想要喊出的内容。
配置你的 .env 文件,我的看起来像这样:
APP_ENV=local
APP_DEBUG=true
APP_KEY=APP_KEY
DB_HOST=localhost
DB_DATABASE=scotchbox
DB_USERNAME=root
DB_PASSWORD=password
PUSHER_KEY=PUSHER_KEY
PUSHER_SECRET=PUSHER_SECRET
PUSHER_APP_ID=PUSHER_APP_ID
有关 .env 文件及其作用的更多信息,请阅读以下内容:Understanding Laravel Environment Variables
Pusher 是一项很棒的服务,它使我们的应用程序的实时组件变得毫不费力。 它们是一项付费服务,但有一个免费的沙盒选项(最多 20 个连接,每天 10 万条消息),对于小型应用程序或仅用于开发来说应该足够了。 继续在 Pusher 上创建一个帐户以获取您的凭据。
建立我们的数据库
让我们的数据库准备好保存我们的 shoutouts。 我们需要使用 migration 和 Eloquent 模型 创建 shoutouts 表。
从项目根目录的命令行运行以下命令:
php artisan make:migration create_shoutouts_table --create=shoutouts && php artisan make:model Models/Shoutout
artisan (database/migrations/create_shoutouts_table.php) 创建的迁移包含:
$table->increments('id');
$table->string('handle');
$table->string('email');
$table->text('content');
$table->timestamps();
模型应具有受保护的 fillable 和 table 属性
/**
* The database table used by the model.
*
* @var string
*/
protected $table = 'shoutouts';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['handle', 'email', 'content'];
路由和控制器
接下来,我们将设置路由并继续创建我们的控制器。 在我们的 app\Http\routes.php 文件中,我们将创建一个 资源路由。
Route::resource('shoutouts', 'SiteController');
现在我们已经绘制好了路线,让我们使用 artisan 创建 SiteController。
php artisan make:controller SiteController
此资源路由会自动为我们的应用程序创建一些路由,包括 CRUD 操作所需的路由。 要查看创建的路由,只需使用:
php artisan route:list
处理创建一个扩音器
新创建的 SiteController 中的 store 方法将是我们处理创建和存储喊话箱到数据库的地方。 在我们的控制器 store 方法中,让我们添加以下内容:
<?php namespace App\Http\Controllers;
...
use App\Models\Shoutbox;
public function store(Request $request) {
$validator = Validator::make($request->all(), Shoutout::$rules);
/**
* Try validating the request
* If validation failed
* Return the validator's errors with 422 HTTP status code
*/
if ($validator->fails())
{
return response($validator->messages(), 422);
}
$shoutout = Shoutout::create($request->only('email', 'handle', 'content'));
// fire ShoutoutAdded event if shoutout successfully added to database
event(new ShoutoutAdded($shoutout));
return response($shoutout, 201);
}
在事物的客户端(JavaScript)上,连接到 Pusher 并监听 App\Events\ShoutoutAdded 事件。
var notifyUser = function (data) {
var data = data.shoutout;
if (! ('Notification' in window)) {
alert('Web Notification is not supported');
return;
}
Notification.requestPermission(function(permission){
var notification = new Notification('@'+ data.handle +' said:', {
body: data.content,
icon: document.getElementById('site_image').content
});
};
var loadPusher = function (){
Pusher.log = function(message) {
if (window.console && window.console.log) {
window.console.log(message);
}
};
var pusher = new Pusher(document.getElementById('pusher_key').content);
var channel = pusher.subscribe('shoutout-added');
channel.bind('App\\Events\\ShoutoutAdded', notifyUser);
};
客户端侦听任何广播事件并使用 Notification API 来提醒当前在站点上的任何用户。 为了触发我们的事件链,我们所要做的就是在我们的控制器中使用以下行:
// fire ShoutoutAdded event if shoutout successfully added to the database
event(new ShoutoutAdded($shoutout));
您可以看到我们如何将应用程序逻辑从控制器中分离出来并进入事件侦听器。 这使我们的应用程序保持精简和平均。
结论
希望这可以帮助您了解使用事件的难易程度以及它们如何简化我们的应用程序。
事件通知应用程序发生了操作。 它们不应该被滥用,不是每个操作都需要一个事件,但是如果它们可以将大量逻辑从你更臃肿的控制器中移出,那么我们会更好地组织起来。