事件是什么?
抛开程序来讲,事件到底是什么
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
中,主要提供
listen和
fire`方法 - 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处理事件】都讲完啦,你会使用【事件】了吗
本站部分资源来源于网络,仅限用于学习和研究目的,请勿用于其他用途。
如有侵权请发送邮件至1943759704@qq.com删除
码农资源网 » Laravel Events(事件)学习笔记