概述
事件是一种常见的观察者模式的应用。简单的来说,当 (event)… 干 (listener)…
放置 event 和 listener 文件的位置分别是:
app/Events
app/Listeners
这些目录只有当你使用 Artisan 命令来生成事件和监听器时才会被自动创建。
对于产品经理来说,事件主要用来规范你的业务逻辑,使支线逻辑与主线逻辑独立分拆。对于程序员来说,事件可以让 Controller 变得非常简洁,解耦,可维护。
定义事件 (Event)
Laravel 应用中的 EventServiceProvider 有个 listen 数组包含所有的事件(键)以及事件对应的监听器(值)来注册所有的事件监听器,可以灵活地根据需求来添加事件。例如,让我们增加一个 RegEvent(注册) 事件:
/**
* 应用程序的事件监听器映射。
*
* @var array
*/
protected $listen = [
'App\Events\RegEvent' => [
// 发送邮件
'App\Listeners\SendUserEmail',
// 发送短信
'App\Listeners\SendSms',
....
],
];
为每个事件和监听器手动创建文件是件很麻烦的事情,而在这里,你只需将监听器和事件添加到 EventServiceProvider 中,再使用 event:generate 命令即可。这个命令会生成在 EventServiceProvider 中列出的所有事件和监听器。当然,已经存在的事件和监听器将保持不变:
php artisan event:generate
<?php
namespace App\Events;
use Event;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\User;
class RegEvent extends Event
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
这样就定义了一个事件,这个事件里没有任何业务逻辑,就是一个数据传输层 DTL(Data Transpotation Layer), 记住这个概念,在很多设计模式中都需要涉及到。
定义事件的侦听和处理器(Listener and Handler)
你在用 artisan 命令生成 Event 的时候,对应的 Listner 也一并生成好了(此处只展示一个示例)
<?php
namespace App\Listeners;
use App\Events\RegEvent;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendUserEmail
{
public function __construct()
{
}
public function handle(RegEvent $event)
{
}
}
handler 里就是写业务逻辑的地方了
经过上面的设置,你的事件和事件处理器就可以在 controller 里使用了:
<?php
...
use Event;
use App\Model\User;
use App\Events\RegEvent;
public function action(Request $request)
{
$uid = $request->post('uid','1');
$user = User::firstOrCreate(['user_id'=>$uid]);
if($user->save()){
event(new RegEvent($user));//触发注册事件
}
}
...
>?
触发事件:Event::fire();
5.8版本开始不支持,改为:Event::dispatch()
保险起见,使用辅助函数 event 触发事件
程序运行到这里,就会触发跟这个事件绑定的 listener 的(handler)。
如果有多个事件,如果要处理的事件很多,那么会影响当前进程的执行效率,这时我们需要把事件加入队列,让它延迟异步执行。
定义队列执行是在 Listener 那里定义的:
<?php
...
use Illuminate\Contracts\Queue\ShouldQueue;
class SendUserEmail implements ShouldQueue
{
....
}
只要 implements ShouldQueue 一下就好了。(队列知识点击这里)
为什么要使用 Event???
使用 Event 一段时间后,你可以觉得比较麻烦,想知道到底有什么好处。
假设创建一个类 Event, 那么 e v e n t − > s e n d M e s s a g e ( event->sendMessage ( event−>sendMessage(user) 这样去使用, 和用观察者模式的事件有啥区别,观察者模式好处在哪里?
所谓钩子(Hook)机制,是从windows编程中流行开的一种技术。其主要思想是提前在可能增加功能的地方埋好(预设)一个钩子,这个钩子并没有实际的意义,当我们需要重新修改或者增加这个地方的逻辑的时候,把扩展的类或者方法挂载到这个点即可。
首先你要明白,事件是一种钩子,Fire 事件的位置就是放置钩子的地方。而上面那种写法是直接嵌入的,没有钩子,也就是说,上面的写法没有事件的概念,事件是不用管你怎么做的,事件只定义发生了什么事(当… 时),这样就可以解耦。
区别就在于,在主逻辑线上的事件,没有做任何事情,它只是说有这样一件事,对于这件事,你可以做点事情,也可以什么都不做。
作为团队的一个 领导者,你可以把主逻辑定义后,然后在主逻辑线上设计事件节点,然后把具体怎么处理这些事件的事务交给团队里的成员去做,成员根本不用管主逻辑和插入事件(钩子)的地方,成员只用写触发事件时要处理的逻辑就可以了。
这样是不是很方便合理啊,如果把所有处理逻辑都写在 Event 类里面,那多人处理的时候岂不是要同时修改一个文件,这样就会有版本冲突问题。
另外 Event 还可以异步队列执行,这也是好处之一。
====================================================
先说一下在什么场景会使用这个事件功能。
事情大概是这样的,需求要在用户注册的时候发一些帮助邮件给用户(原本用户在注册之后已经有发别的邮件的了,短信,IM 什么的)
原来这个注册的方法也就 10 多行代码。但是有时候我们为了省事,直接在注册代码后面添加了各种代码。
例如这个注册方法本来是这样的
<?php
namespace App\Htt\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function register(Request $request)
{
//获取参数
//验证参数
//写入数据库
//return 注册信息
}
}
现在有一个需求,要求注册之后给用户的邮箱发一个广告,绝大多数的人(也包括以前的我)就直接在这后面接着写代码了
public function register(Request $request)
{
//获取参数
//验证参数
//写入数据库
//发送广告邮件
//return 注册信息
}
这是比较直观的写法,后来又有需求要发个短信。
public function register(Request $request)
{
//获取参数
//验证参数
//写入数据库
//发送广告邮件
//发送短信
//return 注册信息
}
然后又有需求,要发 IM 消息,这样的需求很多。这些方法如果你封装了,可能也就一行代码。
但是,在实际项目中,这个注册方法里面已经加了很多东西。如果多人开发的话各种不方便。
laravel 的事件功能实际上更倾向是一种管理手段,并不是没了它我们就做不到了,只是它能让我们做得更加好,更加优雅。
laravel 的事件是一种管理 + 实现的体现,它首先有一个总的目录,然后我们可以宏观的看到所有的事件,而不需要每次都要打开控制器的方法我们才能知道注册后会发生什么,这一点很重要,非常的方便.
现在我们无非就是要在注册之后要做一系列的事情,首先得注册完之后调用一个事件,然后这个事件再做各种各样的事
<?php
namespace App\Htt\Controllers;
use Illuminate\Http\Request;
//引入一个事件类,名字自定义的
use App\Events\RegEvent;
class UserController extends Controller
{
public function register(Request $request)
{
//获取参数
//验证参数
//写入数据库
//触发事件,以后所有需要注册后要做的事情,都不需要再这里加代码了,我们只需要管理事件就好了
//event方法是laravel自带方法, $uid是外部参数,看你需要做什么,传什么参数了。
event(new RegEvent($uid));
//return 注册信息
}
}
然后事件会找到 \app\Providers\EventServiceProvider.php 文件。告诉系统,有人用 event () 调用了RegEvent事件之后要被谁监听得到。
protected $listen = [
'App\Events\RegEvent' => [
// 发送邮件
'App\Listeners\SendUserEmail',
// 发送短信
'App\Listeners\SendSms',
....
],
];
这里是注册类事件的入口,相当于一个总目录,这样就可以跟注册代码解耦了,以后要加东西我们就不需要再去看注册方法的代码了
现在用户注册完之后会触发这个 App\Events\RegEvent 类,然后这个类会被 App\Listeners\SendUserEmail,App\Listeners\SendSms,…监听得到
<?php
namespace App\Listeners;
use App\Events\RegEvent;
use App\Models\User;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendUserEmail implements ShouldQueue
{
public function __construct()
{
//
}
public function handle(RegEvent $event)
{
$uid = $event->uid;
$user = User::find($uid);
//......各种实现
}
}
这个 handle 方法就是我们要做的具体实现了,有个很方便的功能就是如果 implements ShouldQueue 这个接口的话就会异步队列执行,如果去掉的话就是同步执行。很方便有没有,这样代码就解耦了,不需要再管注册代码了,在这里就能很方便的管理了。多人开发也是单独写自己的 Listeners 就可以了。