zoukankan      html  css  js  c++  java
  • 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有一个回调函数,该回调函数作为是否进行短回路的依据。

  • 相关阅读:
    DB2 for Z/os Statement prepare
    Foreign key (referential) constraints on DB2 LUW v105
    复制Informational constraints on LUW DB2 v105
    DB2 SQL Mixed data in character strings
    DB2 create partitioned table
    MVC中使用EF的技巧集(一)
    Asp.Net MVC 开发技巧(二)
    Linq使用技巧及查询示例(一)
    Asp.Net MVC 开发技巧(一)
    Asp.Net MVC Identity 2.2.1 使用技巧(八)
  • 原文地址:https://www.cnblogs.com/san-fu-su/p/5726995.html
Copyright © 2011-2022 走看看