作为程序员一定要保持良好的睡眠,才能好编程

laravel Event事件 及Linsten 监听器

发布时间:2020-02-21


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  实现了队列。



例如:

image.png


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('短信发送结束');

    }


}


image.png


日志记录的是:

确实执行了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 目录下。



https://learnku.com/docs/laravel/5.8/events/3917