Laravel 中的 Event 和事件的概念
19

概述

事件是一种常见的观察者模式的应用。简单的来说,就是当...干...。这个当...和干...在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就可以了。

具体的建议大家去看看手册吧,有些内容我这里就不完全说了。我只是抛砖引玉

本帖由系统于 22小时前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 1

好文

1周前

  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!