• zendframework 事件管理(二)


    首先需要明确的几个问题:

    Q1、什么是事件?

    A:事件就是一个有名字的行为。当这个行为发生的时候,称这个事件被触发。

    Q2、监听器又是什么?

    A:监听器决定了事件的逻辑表达,由事件触发。监听器和事件往往是成对的,当然也可以是一个事件对应多个监听器。监听器是对事件的反应。当事件被触发时,由监听器做出反应。这样一来,多个事件的触发可以导致一个监听器做出反应。一个事件也可以有多个监听器做出反应。(一句话:监听器和事件之间的关系既可以是一对多,也可以是多对一)

    Q3、事件管理器又是干嘛的?

    A:事件管理器(EventManager),从名字上就可以看出来是管理事件用的。但他怎么管理呢?事件管理器往往会为多个事件聚合多个监听器(这里的事件和监听器都是不定数【就是可以是一个也可以是多个】)。事件管理器还负责触发事件。

      一般来说我们用对象来表示事件。一个事件对象描述了事件的基本元素,包括何时以及如何触发这个事件。

      关于事件的基本元素:事件名称、target(触发事件的对象,一般是事件对象本身)、事件参数。之前我们讲过事件相当与一个行为,在程序里面我们经常使用方法或函数来表示行为。因此事件的参数往往也是函数的参数。

      另外关于Shared managers: 之前讲过一个事件可以针对多个监听器。这就是通过Shared managers实现的。EventManager的实现包含(组合)了SharedEventManagerInterface【在构造函数或者setSharedManager里面使用了代码注入的方式,详情可以查看源码】),而SharedEventManagerInterface描述了一个聚合监听器的对象,这些监听器只连接到拥有指定识别符的事件。SharedEventManager并不会触发事件,他只提供监听器并连接到事件。EventManger通过查询SharedEventMangaer来获取具有特定标识符的监听器。

      EventManager里面几个重要的行为:

    1、创建事件:创建事件实际上只是创建EventManagerInterface的一个实例

    2、触发事件:一般在事件行为里面使用trigger触发,这样我们执行该行为的时候便可以直接触发该事件。函数原型:trigger($eventName,$target=null,$argv=[]);$eventName一般为时间行为名(常用__FUNCTION__代替),$target则为事件对象本身可用$this代替,$argv为传入事件的参数(一般为事件行为的参数)。

      当然事件触发方式不仅仅只有trigger一种,还有triggerUntil,triggerEvent,triggerEventUntil。从名字上我们就可以看出分两类:trigger和triggerEvent;trigger类只单纯的触发事件,不需要实现创建事件实例只需要一个事件名字就可以了,而trigger不仅触发事件还顺带着触发监听器,需要事件实例。而带有Until后缀的方法都需要一个回调函数,每一个监听器的结果都会传到该回调函数中,如果回调函数返回了一个true的bool值,EventManager必须使监听器短路。(关于短路见下文的短回路)

    更多内容请查看官方API,或者EventMangerInterface的具体注释。

    3、创建监听器并连接到事件

      监听器可以通过EventManager创建,也可以通过SharedEventManager创建。两者都是使用attach方法,但参数有点儿不一样。

      我们先看EventManager的方式:

    方法原型:attach($eventName, callable $listener, $priority = 1)

      很简单,我们只需要事件名,还有一个可调用函数,最后是优先级默认为1(zend里面的自带事件的优先级多为负数,所以如果你想让自定义的监听器优先级比较高,直接赋值一个正数就行了。)

      可调用函数也就是我们的监听器。事件名有个特殊情况:“*”。这类似于正则匹配,将所有的事件都连接到本监听器中。

      我们现在看看SharedEventManager方式:

    方法原型:attach($identifier, $eventName, callable $listener, $priority = 1);

      与之前唯一不同的地方多了个identifier参数。关于identifier的源码注释如下:

    used to pull shared signals from SharedEventManagerInterface instance;

    用来从SharedEventmanager实例中拉取分享信号。identifier是一个数组,按照我的理解:如果一个事件(注意SharedEventmanager无法创建事件的)定义了identifier,就意味着该事件是可共享的。让后SharedEventManger实例使用attach创建监听器的时候传入identifier参数。EventManager就可以使用identifier参数查询所有的监听器。

      令人困惑的是既然有了事件名,那就可以通过事件名来查询相关监听器,那为何还要多此一举的添加identifier属性?我考虑到的是事件继承问题:假设有一个事件类Foo包含一个事件行为act,SubFoo继承了Foo类并重写了里面的事件行为act。两个类都的事件行为都具有相同的事件名act。这时候如果通过事件名来查询监听器,显然会有冲突。这时候我们定义identifier[__CLASS__, get_class($this)],并在监听器中指定identifier为SubFoo,显然会匹配到SubFoo类中的事件行为act。

      以上我们通过SharedEventManager可以监听多个事件,另外我们还可以通过listener aggregates实现。通过ZendEventManagerListenerAggregateInterface,让一个类监听多个事件,连接一个或多个实例方法作为监听器。同样的该接口也定义了attach(EventManagerInterface $events)和detach(EventManagerInterface $events)。我们在attach的具体实现中使用EventManager的实例的方法attach监听到多个事件。

    use ZendEventManagerEventInterface;
    use ZendEventManagerEventManagerInterface;
    use ZendEventManagerListenerAggregateInterface;
    use ZendLogLogger;
    
    class LogEvents implements ListenerAggregateInterface
    {
        private $listeners = [];
        private $log;
    
        public function __construct(Logger $log)
        {
            $this->log = $log;
        }
    
        public function attach(EventManagerInterface $events)
        {
            $this->listeners[] = $events->attach('do', [$this, 'log']);
            $this->listeners[] = $events->attach('doSomethingElse', [$this, 'log']);
        }
    
        public function detach(EventCollection $events)
        {
            foreach ($this->listeners as $index => $listener) {
                $events->detach($listener);
                unset($this->listeners[$index]);
            }
        }
    
        public function log(EventInterface $e)
        {
            $event  = $e->getName();
            $params = $e->getParams();
            $this->log->info(sprintf('%s: %s', $event, json_encode($params)));
        }
    }

    使用Aggregate的好处:

    1、允许你使用有状态的监听器

    2、在单一的类中组合多个相近的监听器,并一次性连接他们

    内省监听器返回的结果

      我们有了监听器,但如何接收他返回的结果呢?EventManager默认实现会返回一个ResponseCollection的实例。这个类继承于PHP的SplStack。基本结构是一个栈,所以允许你反序遍历Responses。

      ResponseCollection提供了有用的几个方法:

    first():  获取第一个结果

    last():  获取最后一个结果

    contains($value):  查看是否栈里面是否包含某一个值,如果包含则返回true,否则false。

    短回路监听器执行:

      什么叫短回路呢?假设你要做一件事情,直到这件事有了结果,这是一个回路。如果你提前知道了这件事的结果(比如之前做过这件事),那你就没比要把这件事完完全全的做完,这时候你只需要执行一个短回路。

      我们在添加EventManager的时候有一个缓存机制。在一个方法中触发一个事件,如果我们找到一个缓存结果就直接返回。如果找不到缓存结果,我们就将触发的事件缓存下来以备后用。实际上和计算机硬件里面的高速缓存一个道理。

      EventManager组件提供两种处理的方式:1、triggerUntil();2、triggerEventUntil。这两个方法都接受一个回调函数作为第一个参数。如果回调函数返回true,那执行停止。

    public function someExpensiveCall($criteria1, $criteria2)
    {
        $params = compact('criteria1', 'criteria2');
        $results = $this->getEventManager()->triggerUntil(
            function($r){
                return ($r instanceof SomeResultClass);
            },
            __FUNCTION__,
            $this,
            $params
        );
    
        if($results->stopped()) {
            return $results->last()'
        }
    }

    从上面范例中,我们知道,如果执行停止了很有可能是因为栈里面最后的结果满足我们的要求。这样一来,我们只要返回该结果,何必还要进行多余的计算呢?

      处理在事件中停止执行,我们还可以在监听器中停止执行。理由是我们曾经接收过某一个事件,现在我们又接收到了相同事件,理所当然的使用之前的结果就好了。这种情况下,监听器调用stopPropagation(true),然后EventManager会直接返回而不会继续通知额外的监听器。

    $events->attach('do', function($e) {
        $e->stopPropagation();
        return new SomeResultClass();
    });

    当然,使用触发器范例可能会导致歧义,毕竟你并不知道最终的结果是否满足要求。

      Keeping it in order.

      偶尔你会关心监听器的执行顺序。我们通过监听器的优先级来控制执行顺序(上面说讲的短回路也会影响执行顺序)。每一个EventManager::attach()和SharedEventManager::attach()都会接受一个而外的参数:priority。默认情况下为1,我们可以省略该参数。如果你提供了该参数:高优先级执行的早,低优先级的可能会推迟执行。

      自定义事件对象:

      我们之前使用trigger()触发事件,在这同时我们也创建了事件。但trigger()的参数有限,我们只能指定事件的对象,参数,名称。实际上我们可以创建一个自定义事件,在Zendframework里面有个很重要的事件:MvcEvent。很显然MvcEvent便是一个自定义事件,该事件组合了application实例,路由器,路由匹配对象,请求和应答对象,视图模型还有结果。我们查看MvcEvent的源码会发现MvcEvent类实际上继承了Event类。同理我们的自定义事件对象也可以继承Event类或者继承MvcEvent。

    $event = new CustomEvent();
    $event->setName('foo');
    $event->setTarget($this);
    $event->setSomeKey($value);
    
    //injected with event name and target:
    $events->triggerEvent($event);
    
    //Use triggerEventUntil() for criteria-based short-circuiting:
    $results = $events->triggerEventUntil($callback, $event);

    上面的代码可以看到我们使用自定义事件类创建了一个事件对象,调用相关拦截器为事件对象设置属性。我们有了事件对象还是用trigger()触发事件吗?显然不是,我们使用triggerEvent($event)方法,该方法接收一个事件对象。而triggerEventUntil有一个回调函数,该回调函数作为是否进行短回路的依据。

  • 相关阅读:
    mysql之数据类型以及操作数据表
    mysql之提示符
    神经网络-1
    matlab使用摄像头人脸识别
    使用git和intelliJ
    VS配置使用第三方库
    Qt(1)
    附录:其他相关知识
    附录:python and numpy
    上手Caffe(二)
  • 原文地址:https://www.cnblogs.com/san-fu-su/p/5726995.html
Copyright © 2020-2023  润新知