概述
事件是一种常见的观察者模式的应用。简单的来说,就是当... 干...。这个当... 和干... 在 Laravel 事件中分别对应:
当 (event)... 干 (listener)...
放置 event 和 listener 文件的位置分别是:
app/Events
app/Listeners
对于产品经理来说,事件主要用来规范你的业务逻辑,使支线逻辑与主线逻辑独立分拆。对于程序员来说,事件可以让 Controller 变得非常简洁,解耦,可维护。
定义事件 (Event)
用 Artisan 命令可以快速生成一个模板:
php artisan event:generate
<?php namespace App\Events; use App\Podcast; use App\Events\Event; use Illuminate\Queue\SerializesModels; class PodcastWasPurchased extends Event { use SerializesModels; public $podcast; /** * Create a new event instance. * * @param Podcast $podcast * @return void */ public function __construct(Podcast $podcast) { $this->podcast = $podcast; } }
这样就定义了一个事件,这个事件里没有任何业务逻辑,就是一个数据传输层 DTL(Data Transpotation Layer), 记住这个概念,在很多设计模式中都需要涉及到。
定义事件的侦听和处理器(Listener and Handler)
你在用 artisan 命令生成 Event 的时候,对应的 Listner 也一并生成好了:
<?php namespace App\Listeners; use App\Events\PodcastWasPurchased; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; class EmailPurchaseConfirmation { /** * Create the event listener. * * @return void */ public function __construct() { // } /** * Handle the event. * * @param PodcastWasPurchased $event * @return void */ public function handle(PodcastWasPurchased $event) { // Access the podcast using $event->podcast... } }
handler 里就是写业务逻辑的地方了,这里可以用 type-hint 依赖注入的方式,注入任何你需要的类。
将 Event 和 Listener 绑定并注册
这里就用到 Service Provider: providers/EventServiceProvider.php 注册事件和 Listener:
protected $listen = [ 'App\Events\PodcastWasPurchased' => [ 'App\Listeners\EmailPurchaseConfirmation', ], ];
触发事件
经过上面的设置,你的事件和事件处理器就可以在 controller 里使用了:
<?php namespace App\Http\Controllers; use Event; use App\Podcast; use App\Events\PodcastWasPurchased; use App\Http\Controllers\Controller; class UserController extends Controller { /** * Show the profile for the given user. * * @param int $userId * @param int $podcastId * @return Response */ public function purchasePodcast($userId, $podcastId) { $podcast = Podcast::findOrFail($podcastId); // Purchase podcast logic... Event::fire(new PodcastWasPurchased($podcast)); } } Event::fire (new PodcastWasPurchased ($podcast));
就是触发事件的写法,程序运行到这里,就会触发跟这个事件绑定的 listener (handler)。
Event::fire () 有个辅助函数可以简写:
event(new PodcastWasPurchased($podcast));
将事件加入队列
如果要处理的事件很多,那么会影响当前进程的执行效率,这时我们需要把事件加入队列,让它延迟异步执行。
定义队列执行是在 Listener 那里定义的:
<?php namespace App\Listeners; use App\Events\PodcastWasPurchased; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; class EmailPurchaseConfirmation implements ShouldQueue { // }
只要 implements ShouldQueue 一下就好了。
如果你想手动指定一下任务延迟执行的时间:
<?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(10); } } }
触发后延迟 10 秒执行。
事件订阅 (Event Subscribers)
Event Subscribers 是一种特殊的 Listener, 前面讲的是一个 listener 里只能放一个 hander(),事件订阅可以把很多处理器(handler)放到一个类里面,然后用一个 listner 把它们集合起来,这样不同的事件只要对应一个 listner 就可以了。
<?php namespace App\Listeners; class UserEventListener { /** * Handle user login events. */ public function onUserLogin($event) { } /** * Handle user logout events. */ public function onUserLogout($event) { } /** * Register the listeners for the subscriber. * * @param Illuminate\Events\Dispatcher $events * @return array */ public function subscribe($events) { $events->listen( 'App\Events\UserLoggedIn', 'App\Listeners\UserEventListener@onUserLogin' ); $events->listen( 'App\Events\UserLoggedOut', 'App\Listeners\UserEventListener@onUserLogout' ); } }
看后面的 subscribe (),每个事件和处理器是一一对应的。
绑定 Event Subscriber 到 Service Provider
<?php namespace App\Providers; use Illuminate\Contracts\Events\Dispatcher as DispatcherContract; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider { /** * The event listener mappings for the application. * * @var array */ protected $listen = [ // ]; /** * The subscriber classes to register. * * @var array */ protected $subscribe = [ 'App\Listeners\UserEventListener', ]; }
究竟为什么要使用 Event
使用 Event 一段时间后,你可以觉得比较麻烦,想知道到底有什么好处。
假设创建一个类 Event, 那么 $event->sendWelcomeMessage ($user) 这样去使用, 和用观察者模式的事件有啥区别,观察者模式好处在哪里?
首先你要明白,事件是一种『钩子』,Fire 事件的位置就是放置钩子的地方。而上面那种写法是直接嵌入的,没有钩子,也就是说,上面的写法没有事件的概念,事件是不用管你怎么做的,事件只定义发生了什么事(当... 时),这样就可以解耦。
区别就在于,在主逻辑线上的事件,没有做任何事情,它只是说有这样一件事,对于这件事,你可以做点事情,也可以什么都不做。而 $event->sendWelcomeMessage ($user) 这种写法就是 hardcoding 了,到了那个地方必须发生 sendWelcomeMessage 这个行为。
作为团队的一个 leader,你可以把主逻辑定义后,然后在主逻辑线上设计事件节点,然后把具体怎么处理这些事件的事务交给团队里的成员去做,成员根本不用管主逻辑和插入事件(钩子)的地方,成员只用写触发事件时要处理的逻辑就可以了。
这样是不是很方便合理啊,如果把所有处理逻辑都写在 Event 类里面,那多人处理的时候岂不是要同时修改一个文件,这样就会有版本冲突问题。
另外 Event 还可以异步队列执行,这也是好处之一。
=====================================================================================================
概念 + 基础使用
先说一下在什么场景会使用这个事件功能。
事情大概是这样的,需求要在用户注册的时候发一些帮助邮件给用户(原本用户在注册之后已经有发别的邮件的了,短信,IM 什么的)
原来这个注册的方法也就 10 多行代码。但是有时候我们为了省事,直接在注册代码后面添加了各种代码。
例如这个注册方法本来是这样的
<?php namespace App\Htt\Controllers; use Illuminate\Http\Request; class UserController extends Controller { public function register(Request $request) { //获取参数 //验证参数 //写入数据库 //return 注册信息 } }
现在有一个需求,要求注册之后给用户的邮箱发一个广告,绝大多数的人(也包括以前的我)就直接在这后面接着写代码了
<?php namespace App\Htt\Controllers; use Illuminate\Http\Request; class UserController extends Controller { public function register(Request $request) { //获取参数 //验证参数 //写入数据库 //发送广告邮件 //return 注册信息 } }
这是比较直观的写法,后来又有需求要发个短信。
<?php namespace App\Htt\Controllers; use Illuminate\Http\Request; class UserController extends Controller { public function register(Request $request) { //获取参数 //验证参数 //写入数据库 //发送广告邮件 //发送短信 //return 注册信息 } }
然后又有需求,要发 IM 消息,这样的需求很多。这些方法如果你封装了,可能也就一行代码。
但是,在实际项目中,这个注册方法里面已经加了很多东西。如果多人开发的话各种不方便。然后想到了 laravel 似乎有这个功能,但是一直都不知道怎么应用,仔细看了一下手册,发现和自己的想法不谋而合。
laravel 的事件功能实际上更倾向是一种管理手段,并不是没了它我们就做不到了,只是它能让我们做得更加好,更加优雅。
laravel 的事件是一种管理 + 实现的体现,它首先有一个总的目录,然后我们可以宏观的看到所有的事件,而不需要每次都要打开控制器的方法我们才能知道注册后会发生什么,这一点很重要,非常的方便,我就不按着 laravel 的顺序来讲,而是按着实际情况来建立这种关系。
现在我们无非就是要在注册之后要做一系列的事情,首先得注册完之后调用一个事件,然后这个事件再做各种各样的事
<?php namespace App\Htt\Controllers; use Illuminate\Http\Request; //我们先引入一个事件类,名字自定义的,之后再一步一步创建 use App\Events\Register; class UserController extends Controller { public function register(Request $request) { //获取参数 //验证参数 //写入数据库 //触发事件,以后所有需要注册后要做的事情,都不需要再这里加代码了,我们只需要管理事件就好了 //event方法是laravel自带方法, $uid是外部参数,看你需要做什么,传什么参数了。注册之后肯定有$uid的嘛 event(new Register($uid)); //return 注册信息 } }
找到 \app\Providers\EventServiceProvider.php 文件。给它添加关系,告诉系统,有人用 event () 调用了事件之后要被谁监听得到。
<?php namespace App\Providers; use Laravel\Lumen\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider { /** * The event listener mappings for the application. * * @var array */ protected $listen = [ // 用户注册后的事件 'App\Events\Register' => [ // 发送广告邮件 'App\Listeners\SendAdMail', // 发送短信 'App\Listeners\SendSms', // 发送帮助信息 'App\Listeners\SendHelpInformation', ], ]; }
这里是注册事件的入口,相当于一个总目录,这样就可以跟注册代码解耦了,以后要加东西我们就不需要再去看注册方法的代码了
现在注册完之后会触发这个 App\Events\Register 类,然后这个类会被 App\Listeners\SendAdMail,App\Listeners\SendSms,App\Listeners\SendHelpInformation 监听得到,我们进入 app\Events 目录,创建 Register 这个类
<?php namespace App\Events; class Register { public $uid; /** * 创建一个新的事件实例. * * @param Order $order * @return void */ public function __construct($uid) { $this->uid = $uid; } }
这样就可以了。
然后去 app\Listeners 目录创建各种要做的事件监听类。
<?php namespace App\Listeners; use App\Events\Register; use App\Models\User; use Illuminate\Contracts\Queue\ShouldQueue; class SendHelpInformation implements ShouldQueue { public function __construct() { // } public function handle(Register $event) { $uid = $event->uid; $user = User::find($uid); //......各种实现 } }
这个 handle 方法就是我们要做的具体实现了,有个很方便的功能就是如果 implements ShouldQueue 这个接口的话就会异步队列执行,如果去掉的话就是同步执行。很方便有没有,这样代码就解耦了,不需要再管注册代码了,在这里就能很方便的管理了。多人开发也是单独写自己的 Listeners 就可以了。
具体的建议大家去看看手册吧,有些内容我这里就不完全说了。我只是抛砖引玉