Laravel框架事件实现

序言

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

观察者模式

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

下面是一张观察者模式的类图,左边的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中完成事件的注册,便于更好地解耦和复用。优化

相关文章
相关标签/搜索