Laravel框架事件实现

序言

如果你正在使用Laravel作为你的开发框架,那么肯定会了解或者使用过它的事件API。Laravel的事件API是基于观察者这种设计模式实现的,并且结合了实际情况做了优化和改进。下面就依次介绍和Laravel事件实现相关的一些内容。

观察者模式

首先举一个例子,假如你是一个司机,在马路上开车时违反了交通规则,被交警拦下后罚了款。这是在现实中生活中应用到事件机制的一个例子。司机就是一个被观察的对象,而交警就是观察者,当交警观察到司机违法时,就像被打开了一个开关一样,会不自觉地对司机进行处罚。

下面是一张观察者模式的类图,左边的Concrete Subject类就好比司机,而右边的Concrete Observer就好比交警。Concrete Subject的Notify方法是将司机的违法行为暴露给交警,而Concrete Observer的Update方法是交警的处罚行为。

输入图片说明

Laravel事件与监听器

输入图片说明

Laravel中的事件API包含了监听(Listen)和触发(Fire)两个核心的方法,其中Listen方法就是将事件(Event)与监听器(Listener)进行绑定,监听和触发方法都封装在Dispatcher类中。Dispatcher类就是一个Concrete Subject,Event就好比司机的违法行为,Fire方法就是上面说到的Notify方法,Listener就好比是交警,它是一个Concrete Observer,Listener类中需要实现handle方法用来做类似交警罚款的动作。

注册事件与监听器

输入图片说明

  • 判断Listener是否为字符串
    public function makeListener($listener)
    {
        return is_string($listener) ? $this->createClassListener($listener) : $listener;
    }
  • 解析Listener类和handle方法,handle方法可以替换成自定义的方法
    protected function parseClassCallable($listener)
    {
        $segments = explode('@', $listener);

        return [$segments[0], count($segments) == 2 ? $segments[1] : 'handle'];
    }
  • 判断Listener的handle方法是否需要队列异步执行(考虑为何需要异步执行?)
    protected function handlerShouldBeQueued($class)
    {
        try {
            return (new ReflectionClass($class))->implementsInterface(
                'Illuminate\Contracts\Queue\ShouldQueue'
            );
        } catch (Exception $e) {
            return false;
        }
    }
  • 创建Listener的Callable
    protected function createClassCallable($listener, $container)
    {
        list($class, $method) = $this->parseClassCallable($listener);

        if ($this->handlerShouldBeQueued($class)) {
            return $this->createQueuedHandlerCallable($class, $method);
        } else {
            return [$container->make($class), $method];
        }
    }
  • 判断Event字符串是否包含通配符,把上面生成的Listener放入对应的类属性,注意$priority
    public function listen($events, $listener, $priority = 0)
    {
        foreach ((array) $events as $event) {
            if (Str::contains($event, '*')) {
                $this->setupWildcardListen($event, $listener);
            } else {
                $this->listeners[$event][$priority][] = $this->makeListener($listener);

                unset($this->sorted[$event]);
            }
        }
    }

    protected function setupWildcardListen($event, $listener)
    {
        $this->wildcards[$event][] = $this->makeListener($listener);
    }

触发事件

输入图片说明

  • 判断Event是否为对象,解析生成payload和Event字符串
    if (is_object($event)) {
            list($payload, $event) = [[$event], get_class($event)];
    }
  • 格式化payload,将event字符串写入firing属性
    if (! is_array($payload)) {
            $payload = [$payload];
    }

    $this->firing[] = $event;
  • 通过队列广播事件
    if (isset($payload[0]) && $payload[0] instanceof ShouldBroadcast) {
            $this->broadcastEvent($payload[0]);
    }
  • 根据Event字符串获取Listeners
    public function getListeners($eventName)
    {
        $wildcards = $this->getWildcardListeners($eventName);

        if (! isset($this->sorted[$eventName])) {
            $this->sortListeners($eventName);
        }

        return array_merge($this->sorted[$eventName], $wildcards);
    }
  • 对不包含通配符的事件Listener按注册时的priority进行排序
    protected function sortListeners($eventName)
    {
        $this->sorted[$eventName] = [];

        if (isset($this->listeners[$eventName])) {
            krsort($this->listeners[$eventName]);

            $this->sorted[$eventName] = call_user_func_array(
                'array_merge', $this->listeners[$eventName]
            );
        }
    }
  • 循环执行Listener Callable,payload作为参数传过去。$halt表示返回内容不为空时中断执行,立即返回响应的内容。执行结束后,将Event字符串从firing属性中删除。如果返回内容为false,则跳出循环。
    foreach ($this->getListeners($event) as $listener) {
            $response = call_user_func_array($listener, $payload);

            if (! is_null($response) && $halt) {
                array_pop($this->firing);

                return $response;
            }

            if ($response === false) {
                break;
            }

            $responses[] = $response;
    }

    array_pop($this->firing);

    return $halt ? null : $responses;

总结

Laravel的事件API支持一个事件绑定多个监听器,也支持包含通配符的事件,但是包含通配符的事件不支持按优先级触发监听器。Laravel的事件触发后,Listener可以通过队列异步执行,保证了程序可以不中断,且快速响应,就好比本文开头的举例中,如果被电子警察拍到违法行为,可以在指定时间内延迟缴纳罚款。Laravel的事件也支持通过队列广播,Dispatcher可以被Listener订阅,在Listener中完成事件的注册,便于更好地解耦和复用。

转载于:https://my.oschina.net/luoxiaojun1992/blog/747527

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值