事件是什么?

抛开程序来讲,事件到底是什么

ps: 我发现程序的问题,如果抛出程序来讲,也能讲的清楚,那么用程序实现的时候就会更清晰,这就是数据模型的抽象能力吧

我的理解【就是发生了什么事情】。

比如【吃饭】是一件事情,这个事情包括了吃什么东西,吃饭的时间, 谁在吃等。

怎么导致【吃饭】这件事情的发生呢,就是当有人肚子饿了他选择去吃饭。

事件触发了之后,需要有人【处理】这个事件。

这个角色就是事件的监听者。

拿【吃饭】来说,监听者可能是女朋友。

当触发了这个事件,她想知道你今天吃了什么,什么时候吃的,有没有吃饱等。

好像不对,女朋友可能没有这么周到,不管怎么说,【事件】需要有个监听者。

事件还可能有多个监听着,除了女朋友,男朋友,老妈什么的也关心【吃饭】事件

把事件和监听者关联在一起,这个操作就叫做【事件注册】。

负责这个操作的角色我们可以理解为【事件注册分发中心】

这里就有四个角色:

  • 事件(Event)
  • 监听者(Listener)
  • 事件注册分发中心 (EventDispatcher)
  • 触发事件的主体(Someone)

事件流程就是:

Listener 向 EventDispatcher 注册了一个Event,

当Someone 吃饭时,主动fire(触发)Event,

事件触发后Dispatcher调用注册的Listener处理Event。

下面是抽象的数据模型。注意只是伪代码。


# 事件
class EatEvent
{
    public function __construct($author)
    {
        $this->author = $author
    }

    # 谁在吃
    public function who()
    {
        return $this->author->name();
    }

    # 什么时候吃的
    public function time()
    {
        return $this->author->timeToEat();
    }

    # 吃什么
    public function menu()
    {
        return $this->author->whatToEat();
    }
}

# 监听者
class  GrilFriendListener
{
    # 获取吃饭发送的时间和吃的东西
    public function handle(EatEvent $event)
    {
        echo "who: " . $event->who();

        echo "eat time: " . $event->time();

        echo "eat: " . $event->menu();
    }
}

# 事件注册分发中心
class static EventDispatcher
{
    # 事件注册,event是定义的eatEvent对象的类名
    public static function listen($event, listener)
    {
        # 一个事件很可能有多个监听者
        $this->listens[$event][] = $listener
    }

    # 触发事件
    public static function fire($event, $payload = [])
    {
        # 获取监听者列表
        $listeners = $this->getListeners($event);

        # 监听者处理事件
        foreach($listeners as $listen) {
            $this->creatLisenter($listen)->handler($event, $payload);
        }
    }

    # 注册时间订阅者
    public function subscribe($subscriber)
    {
        $subscriber = $this->resolveSubscriber($subscriber);

        $subscriber->subscribe($this);
    }
}

# 事件注册
EventDispatcher::listen("EatEvent", "GrilFriendListener");


# 事件触发者
class Someone
{
    public function eating()
    {
        # 初始化一个事件, 注意author是this
        $event = $this->contanier->makeWith("EatEvent", $this);

        # 触发一个事件
        EventDispatcher::fire($event);
    }
}

现在你理解了事件的整个模型了吧。

【事件】中还有一个角色叫做【订阅者】。

订阅者就是可以【订阅】【处理】多个事件, 他也需要向【EventDispatcher】注册一个订阅者

class UserEventSubscriber
{
    /**
     * 处理用户登录事件。
     */
    public function onUserLogin($event) {}

    /**
     * 处理用户注销事件。
     */
    public function onUserLogout($event) {}

    /**
     * 为订阅者注册监听器。
     *
     * @param  IlluminateEventsDispatcher  $events
     */
    public function subscribe($events)
    {
        $events->listen(
            'IlluminateAuthEventsLogin',
            'AppListenersUserEventSubscriber@onUserLogin'
        );

        $events->listen(
            'IlluminateAuthEventsLogout',
            'AppListenersUserEventSubscriber@onUserLogout'
        );
    }

}
# 向注册中心注册订阅者
EventDispatcher::subscribe("UserEventSubscriber");

事件模型的进一步探索

上文我们提到,【事件】一个是【类】,这个【类】记录了事件发生时的相关内容。

但是,很多时候,我们不关心事件的内容,我们只需要知道某个事件发生了。

【事件】可以仅仅是一个【动作】,不需要带有任何信息。

所以 用一个字符串就可以表示事件

事件的监听者,也不再是一个【类】,而是变成了一个【匿名函数】

这就是【事件模型】的【精简版】


# cart.create 就是一个事件,这个事件就是创建购物车,
# 事件发生后,把购物车信息放到cookie里面
EventDispatcher::listen('cart.create', function ($cartId) {
    Cookie::queue('cart', (new CartStorage())->setId($cartId)->getString());
});

class Cart
{
    public function store()
    {
        # 创建购物车
        $cartId = Cart::create();

        # 触发【创建购物车】事件
        EventDispatcher::fire($cartId);
    }
}

所以,laravel中,【事件】不一定要当成一个【类】来使用。

事件通配符

比如有三个事件【睡觉】【吃饭】【看电影】这些生活上面的事件,【女朋友】这个监听者都要监听

于是可以这样


# 通配符事件的监听者函数第一个参数一定是event。
EventDispatcher::listen('life.*', function ($event, $payload) {
    # do something
});

# 这样可以触发事件监听的回调函数
EventDispatcher::fire('life.sleep', 'sunli');

事件监听者队列化

比如【事件】发生后, 【监听者】处理这个【事件】特别耗时

所以我们可以把【监听者】处理逻辑放到队列异步处理。

这里涉及到队列的相关知识,可以在队列文章找到详细的说明

Laravel 是怎么处理事件模型

上面讲了这么多,只是普及了【事件模型】,下面将详细说明laravel是处理方式。

在laravel里面有同样有4个角色

  • Event(事件类)在laravel中,把一个【事件】当作类来处理
  • Listener(监听者类),这个类默认需要定义handle方法处理事件
  • Dispatcher(事件注册触发中心) 在IlluminateEventsDispatcher中,主要提供listenfire`方法
  • Anyone(事件触发器),任何对象都可以触发事件

事件类 (Event) 类通常保存在 app/Events 目录下,而它们的监听类 (Listener) 类被保存在 app/Listeners 目录下。

如果你在应用中看不到这些文件夹也不要担心,因为当你使用 Artisan 命令来生成事件和监听器时他们会被自动创建

laravel在初始化的时候,注册了EventServiceProvider

public function registerBaseServiceProviders()
{
    # IlluminateEventsEventServiceProvider
    $this->register(new EventServiceProvider($this));
}

class EventServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton('events', function ($app) {
            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                return $app->make(QueueFactoryContract::class);
            });
        });
    }
}

# laravel提供了`Event` Facade 模式去访问 Dispatcher
'Event' => IlluminateSupportFacadesEvent::class,

# 所以,可以直接使用Event::listen()方式去注册一个事件

下面主要分析Dispatcher的源码。


# events必须是字符串数组,或者字符串
# 那么这个events可能就是【事件类】的类名集合 或者是【事件动作】的字符串表示的集合
# listener可以是一个匿名函数,也可以是一个【监听者类名@类里面的一个方法】如果方法不存在讲默认执行【handle】方法

public function listen($events, $listener)
{
    foreach ((array) $events as $event) {
        if (Str::contains($event, '*')) {
            $this->setupWildcardListen($event, $listener);
        } else {
            $this->listeners[$event][] = $this->makeListener($listener);
        }
    }
}


# 创建一个Listener
public function makeListener($listener, $wildcard = false)
{
    # listener是【监听者类名@类里面的一个方法】
    if (is_string($listener)) {
        return $this->createClassListener($listener, $wildcard);
    }

    # listener是一个匿名函数, 注意为啥不直接返回listener呢,因为需要传递参数
    # 所以在wrapper了listener, 类似于python的装饰器
    # 注意,如果是事件是通配符,那么匿名函数第一个参数是event, 第二个参数是payload
    # payload必须是一个数组
    return function ($event, $payload) use ($listener, $wildcard) {
        if ($wildcard) {
            # 如果是通配符的话,会多传递一个event过去,第二个参数是payload数组
            return $listener($event, $payload);
        } else {
            # 最后调用的匿名函数参数是payload数组的values
            return $listener(...array_values($payload));
        }
    };
}

# 解析【监听者类名@类里面的一个方法】,也返回一个闭包函数。
public function createClassListener($listener, $wildcard = false)
{
    return function ($event, $payload) use ($listener, $wildcard) {
        if ($wildcard) {
            # 如果是通配符,参数会多传递一个event过去
            return call_user_func($this->createClassCallable($listener), $event, $payload);
        } else {
            # payload是一个数组,请查看call_user_func_array的用法
            return call_user_func_array(
                $this->createClassCallable($listener), $payload
            );
        }
    };
}

# 创建一个可callable的Class的对象
protected function createClassCallable($listener)
{
    list($class, $method) = $this->parseClassCallable($listener);

    # 请特别学习一下,怎么判断是不是需要队列化的逻辑
    if ($this->handlerShouldBeQueued($class)) {
        # 如果是需要队列,则加入到队列里面
        return $this->createQueuedHandlerCallable($class, $method);
    } else {
        return [$this->container->make($class), $method];
    }
}


# 触发事件

# event 必须是一个字符串或者是一个【事件类】的实例化对象
public function dispatch($event, $payload = [], $halt = false)
{
    # 经过这一步操作,如果event是一个object,
    # 那么payload = [event],event = get_class($event),
    list($event, $payload) = $this->parseEventAndPayload(
        $event, $payload
    );

    if ($this->shouldBroadcast($payload)) {
        $this->broadcastEvent($payload[0]);
    }

    $responses = [];

    # 根据event 获取Listeners
    foreach ($this->getListeners($event) as $listener) {

        # 注意调用$listener的参数,一个event, 一个是payload
        # 可以获取看一下makeListener的生成闭包函数
        $response = $listener($event, $payload);

        if ($halt && ! is_null($response)) {
            return $response;
        }
        if ($response === false) {
            break;
        }

        $responses[] = $response;
    }

    return $halt ? null : $responses;
}

到这里, 【事件模型】和【laravel处理事件】都讲完啦,你会使用【事件】了吗