概述
事件是一种常见的观察者模式的应用。简单的来说,就是当...干...。这个当...和干...在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还可以异步队列执行,这也是好处之一。