Laravel中的Event和事件
https://learnku.com/articles/20712
event 是什么?
事件是一种常见的观察者模式的应用。简单的来说,就是当... 干...。这个当... 和干... 在 Laravel 事件中分别对应:
当 (event)... 干 (listener)...
放置 event 和 listener 文件的位置分别是:
app/Events
app/Listeners
什么时间使用event?
究竟为什么要使用 Event
使用 Event 一段时间后,你可以觉得比较麻烦,想知道到底有什么好处。
假设创建一个类 Event, 那么 $event->sendWelcomeMessage ($user) 这样去使用, 和用观察者模式的事件有啥区别,观察者模式好处在哪里?
首先你要明白,事件是一种『钩子』,Fire 事件的位置就是放置钩子的地方。而上面那种写法是直接嵌入的,没有钩子,也就是说,上面的写法没有事件的概念,事件是不用管你怎么做的,事件只定义发生了什么事(当... 时),这样就可以解耦。
区别就在于,在主逻辑线上的事件,没有做任何事情,它只是说有这样一件事,对于这件事,你可以做点事情,也可以什么都不做。而 $event->sendWelcomeMessage ($user) 这种写法就是 hardcoding 了,到了那个地方必须发生 sendWelcomeMessage 这个行为。
作为团队的一个 leader,你可以把主逻辑定义后,然后在主逻辑线上设计事件节点,然后把具体怎么处理这些事件的事务交给团队里的成员去做,成员根本不用管主逻辑和插入事件(钩子)的地方,成员只用写触发事件时要处理的逻辑就可以了。
这样是不是很方便合理啊,如果把所有处理逻辑都写在 Event 类里面,那多人处理的时候岂不是要同时修改一个文件,这样就会有版本冲突问题。
另外 Event 还可以异步队列执行,这也是好处之一。
evnet 触发
handler 里就是写业务逻辑的地方了,这里可以用 type-hint 依赖注入的方式,注入任何你需要的类。
将 Event 和 Listener 绑定并注册
这里就用到 Service Provider: providers/EventServiceProvider.php 注册事件和 Listener:
Event::fire(new PodcastWasPurchased($podcast));
Event::fire (new PodcastWasPurchased ($podcast)); 就是触发事件的写法,程序运行到这里,就会触发跟这个事件绑定的 listener (handler)。
Event::fire () 有个辅助函数可以简写:
event(new PodcastWasPurchased($podcast));
终止事件执行
在Listener中的handle中 return false 即可执行后续的操作
其他注意事项
若handle中存在无法获取到的变量,会抛出一个错误,后续的监听器都不会去执行。
如果多个监听器,其中任意一个有异常或错误 后续的加监听器都不会执行。
如果多个监听器 最后一个有问题 则前面的会执行成功,最后一个失败。
写在监听器中的代码请使用 try{} catch 包起来,防止代码报错,影响后续的逻辑
将事件加入队列
如果要处理的事件很多,那么会影响当前进程的执行效率,这时我们需要把事件加入队列,让它延迟异步执行。
定义队列执行是在 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实例
用 Artisan 命令可以快速生成一个模板:
G:\phpstudy\WWW\laravel-shop>php artisan make:job Test Job created successfully. G:\phpstudy\WWW\laravel-shop>php artisan make:listener Test Listener created successfully.
<?php
namespace App\Events;
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;
class PushMessage
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $noticeMessage;
/**
* Create a new event instance.
*
* @param $noticeMessage
*/
public function __construct($noticeMessage)
{
$this->noticeMessage = $noticeMessage;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}这样就定义了一个事件,这个事件里没有任何业务逻辑,就是一个数据传输层 DTL(Data Transpotation Layer), 记住这个概念,在很多设计模式中都需要涉及到。
定义一个Listener
<?php
namespace App\Listeners;
use App\Events\PushMessage;
use App\Models\Inquiry\RegisterOrderModel;
use App\Services\Support\SmsService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
class SendSmsNotice implements ShouldQueue
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param PushMessage $event
* @return void
*/
public function handle(PushMessage $event)
{
Log::info('记录短信发送开始的时间');
Log::info('事件中的内容信息:' . $event->noticeMessage);
sleep(10);
Log::info('短信发送结束');
}
}实现 ShouldQueue 这个接口后,就能实现消息异步发送
implements ShouldQueue
前提条件: 配置了 config/queue.php 实现了队列。
例如:

1、controller中控制器方法:
public function list(){
dump(get_current_date());
dump('开始做你该做的事情');
event(new PushMessage(10)); //做这件事事情需要10秒钟,则放在消息队列中操作
dump('调用过任务了,快要结束了');
dump(get_current_date(),'我结束了');
}
}2、pushMessage.php event事件 消息搬运工
<?php
namespace App\Events;
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;
class PushMessage
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $noticeMessage;
/**
* Create a new event instance.
*
* @param $noticeMessage
*/
public function __construct($noticeMessage)
{
$this->noticeMessage = $noticeMessage;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}3、Listeners 观察者 处理事情的类 handle
<?php
namespace App\Listeners;
use App\Events\PushMessage;
use App\Models\Inquiry\RegisterOrderModel;
use App\Services\Support\SmsService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
class SendSmsNotice implements ShouldQueue
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param PushMessage $event
* @return void
*/
public function handle(PushMessage $event)
{
Log::info('记录短信发送开始的时间');
Log::info('事件中的内容信息:' . $event->noticeMessage);
sleep(10);
Log::info('短信发送结束');
}
}
日志记录的是:
确实执行了10秒。
但是接口中确实是1秒内就返回了。实现了异步消费、程序解耦
[2020-02-21 01:55:35] development.INFO: 当前方法:GET 当前请求路由地址:/news/list [2020-02-21 01:55:35] development.INFO: requestParamsData: [2020-02-21 01:55:38] development.INFO: 记录短信发送开始的时间 [2020-02-21 01:55:38] development.INFO: 事件中的内容信息:10 [2020-02-21 01:55:48] development.INFO: 短信发送结束
事件订阅 (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',
];
}下面是一个真实案例:
Listen:
<?php
namespace App\Listeners;
use App\Events\UserEvent;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
class SendOrderMail implements ShouldQueue{
public function handle(UserEvent $event){
Log::info('发送邮件');
$data=$event->getData();
Log::info('发送邮件参数是:'.json_encode($data,JSON_UNESCAPED_UNICODE));
foreach($data as $k=>$val){
echo "<pre style='color:blue;font-size:16px;'>";
echo __FILE__." ".__LINE__."<br>";
print_r($val);
echo "</pre>";
}
Log::info('开始发送邮件参数是:');
sleep(10);
Log::info('发送邮件花费了8秒');
}
}SendSms.php
<?php
namespace App\Listeners;
use App\Events\UserEvent;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendSms implements ShouldQueue{
public function handle(UserEvent $event){
\Log::info('开始执行短信发送');
echo "短信发送";
echo "<pre style='color:blue;font-size:16px;'>";
echo __FILE__ . " " . __LINE__ . "<br>";
echo microtime(TRUE);
echo "</pre>";
\Log::info('执行短信发送'.microtime(true));
sleep(8);
\Log::info('执行短信发送执行过程中8秒 '.microtime(true));
echo "短信发送success";
echo "<pre style='color:blue;font-size:16px;'>";
echo __FILE__ . " " . __LINE__ . "<br>";
echo microtime(TRUE);
echo "</pre>";
}
}App\Providers\EventServiceProvider.php
<?php
namespace App\Providers;
use App\Events\UserEvent;
use App\Listeners\SendOrderMail;
use App\Listeners\SendSms;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider {
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
UserEvent::class => [
SendOrderMail::class,
SendSms::class
]
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot() {
parent::boot();
//
}
}对event的调用
event(new UserEvent(['123', '456']));
event(new ExampleEvent);
Event::fire(new ExampleEvent);
Laravel 的事件提供了一个简单的观察者实现,能够订阅和监听应用中发生的各种事件
事件类通常存放在 app/Events 目录下,而这些事件类的监听器则存放在 app/Listeners 目录下。