Listening to the Words

Hyperf事件实现的机制分析

观察者模式

观察者模式是面向对象23个设计模式之一,是应用最为广泛的设计模式应该没有之一。

观察者模式应用在十分多的领域:epoll事件驱动机制,生产者和消费者模式,订阅者发布者模式等。

具体描述见文章: PHP设计模式之—观察者模式

事件机制

事件event机制的存在就是为了解耦,就像小孩啼哭时,爸爸妈妈和狗狗同时会收到通知,作出不同的反应,他们的共同点就是都订阅 obeserver了小孩哭的这个事件event

hyperf实现事件机制

hyperf基于psr14事件规范实现了事件机制功能,分别要继承接口约束EventDispatcherInterfaceListenerProviderInterface :

    <?php
    declare(strict_types=1);

    namespace Psr\EventDispatcher;

    /**
     * Defines a dispatcher for events.
     */
    interface EventDispatcherInterface
    {
        /**
         * Provide all relevant listeners with an event to process.
         *
         * @param object $event
         *   The object to process.
         *
         * @return object
         *   The Event that was passed, now modified by listeners.
         */
        public function dispatch(object $event);
    }

 <?php
        declare(strict_types=1);

        namespace Psr\EventDispatcher;

        /**
         * Mapper from an event to the listeners that are applicable to that event.
         */
        interface ListenerProviderInterface
        {
            /**
             * @param object $event
             *   An event for which to return the relevant listeners.
             * @return iterable[callable]
             *   An iterable (array, iterator, or generator) of callables.  Each
             *   callable MUST be type-compatible with $event.
             */
            public function getListenersForEvent(object $event) : iterable;
        } 

注册监听事件

hyperf规定如果想要实现事件机制,必须首先实现一个Listener即监听器(观察者),就像是爸爸妈妈狗狗,监听器必须有两个方法,listen,process

//爸爸妈妈观察的事件,小孩哭,小孩笑
  public function listen (): array
    {
        return [
            CryEvent::class,
        ];
    }

  //监听器要执行的任务,可以理解为爸爸妈妈要做的事情;
    public function process (object $event)
    {

    }

其次还要定义发生的事件:

 //小孩哭的事件
   class CryEvent
   {
       public $user;

       public function __construct ($user)
       {
           $this->user = $user;
       }
    } 

接下来框架启动会扫描到ListenerProviderFactory类触发__invoke,进而完成监听器提供者的注册

首先注解获取到所有监听器实体类:

 private function register(ListenerProvider $provider, ContainerInterface $container, string $listener, int $priority = 1): void
    {
        $instance = $container->get($listener);
        if (method_exists($instance, 'process') && method_exists($instance, 'listen')) {
            foreach ($instance->listen() as $event) {
                $provider->on($event, [$instance, 'process'], $priority);
            }
        }
    }

接着注册到监听器收集类里:

class ListenerProvider implements ListenerProviderInterface
  {
      public $listeners = [];
      public function getListenersForEvent($event): iterable
      {
          $queue = new SplPriorityQueue();
          foreach ($this->listeners as $listener) {
              if ($event instanceof $listener->event) {
                  $queue->insert($listener->listener, $listener->priority);
              }
          }
          return $queue;
      }
        //此处将事件和监听器加入到了监听器收集容器里。
         public function on(string $event, callable $listener, int $priority = 1): void
         {
             //ListenerData只是一个监听器数据类,$priority在后面有用
             $this->listeners[] = new ListenerData($event, $listener, $priority);
         }
     }

此时框架完成来了事件和监听器的注册,也就是:爸爸妈妈狗狗对所观察的小孩发生事情(事件)完成了关注。

小孩小孩你别哭(dispatcher)

如何触发这些事件呢?框架里实现了psr14规范的EventDispatcherInterface接口约束:

class EventDispatcher implements EventDispatcherInterface
    {
  /**
      * @var ListenerProviderInterface
      */
     private $listeners;

     /**
      * @var null|StdoutLoggerInterface
      */
     private $logger;

     public function __construct(
         ListenerProviderInterface $listeners,
         ?StdoutLoggerInterface $logger = null
     ) {

      //此处通过容器拿到了所有注册过的监听器
         $this->listeners = $listeners;
         $this->logger = $logger;
     }

     //这就是业务逻辑中所调用的方法,传递这是事件的类实例:new CryEvent();
  public function dispatch(object $event)
   {
       //getListenersForEvent 这是监听器里实现的方法,在所有监听器结合里找到监听了这个事件的监听器;就像是小孩哭了,找到所有关注小孩哭的人,妈妈或者爸爸
       foreach ($this->listeners->getListenersForEvent($event) as $listener) {

           //此处发现,关注小孩哭的是妈妈和爸爸。于是把事件(其实是个事件类包含数据),轮询给爸爸妈妈;
           //但是为什么是这样的方式呢?
           $listener($event);

           $this->dump($listener, $event);
           if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
               break;
           }
       }
       return $event;
   }

      ...
    }

但是为什么是这样 $listener($event)的方式?

private function register(ListenerProvider $provider, ContainerInterface $container, string $listener, int $priority = 1): void
        {
            $instance = $container->get($listener);
            if (method_exists($instance, 'process') && method_exists($instance, 'listen')) {
                foreach ($instance->listen() as $event) {
                    $provider->on($event, [$instance, 'process'], $priority);
                }
            }
        }

$listener($event)其实就是 监听器里process方法的回调形式,相当于 (new Lister())->proess($event);

到此处监听器就执行完成了。

爸爸妈妈谁先来?

又有一个问题,爸爸妈妈都听到了小孩的哭声,那么谁先来执行呢?通常是谁先听到谁先来,可是呢更多时刻都是妈妈先来抱抱,爸爸拿个奶瓶等着小孩子吃,此时就涉及到顺序的问题了?

hyperf框架处理的十分巧妙,它利用php底层提供的堆队列特性来处理:

ListenerProvider

   public function getListenersForEvent($event): iterable
  {
     //此处就是标准优先排序堆队列,利用这个特性,把所有监听了此事件的监听器放进队列里,当出队时会根据priority输赢由大到小出队
      $queue = new SplPriorityQueue();
      foreach ($this->listeners as $listener) {
          if ($event instanceof $listener->event) {
              //监听器里设置好priority值
              $queue->insert($listener->listener, $listener->priority);
          }
      }
     //此处返回的就是一个可以迭代的队列数组
      return $queue;
  }

php手册链接 什么是堆队列

结束

这就是整个hyperf实现事件机制的过程,整个核心其实都在解耦。

点赞