Yii框架组件的事件机制原理与用法分析


Posted in PHP onApril 07, 2020

本文实例讲述了Yii框架组件的事件机制原理与用法。分享给大家供大家参考,具体如下:

在深入分析 Yii 的运行之前,我们先来看一下 Yii 框架中一个很重要的机制 - 事件。

Yii 官方参考文档关于组件事件的解释:

=======================================================================

组件事件是一些特殊的属性,它们使用一些称作 事件句柄 ( event handlers )的方法作为其值。 附加 ( 分配 ) 一个方法到一个事件将会引起方法在事件被唤起处自动被调用。因此, 一个组件的行为可能会被一种在部件开发过程中不可预见的方式修改。

组件事件以 on 开头的命名方式定义。和属性通过 getter/setter 方法来定义的命名方式一样, 事件的名称是大小写不敏感的。以下代码定义了一个 onClicked 事件 :

public function onClicked($event)
{
  $this->raiseEvent('onClicked', $event);
}

这里作为事件参数的 $event 是 CEvent 或其子类的实例。

我们可以附加一个方法到此 event ,如下所示 :

$component->onClicked=$callback;

这里的 $callback 指向了一个有效的 PHP 回调。它可以是一个全局函数也可以是类中的一个方法。 如果是后者,它必须以一个数组的方式提供 : array($object,'methodName').

事件句柄的结构如下:

function methodName($event)
{
  ......
}

这里的 $event 即描述事件的参数(它来源于 raiseEvent() 调用)。 $event 参数是 CEvent 或其子类的实例。 至少,它包含了关于谁触发了此事件的信息。

从版本 1.0.10 开始,事件句柄也可以是一个 PHP 5.3 以后支持的匿名函数。例如,

$component->onClicked=function($event) {
  ......
}

如果我们现在调用 onClicked() , onClicked 事件将被触发(在 onClicked() 中), 附属的事件句柄将被自动调用。

一个事件可以绑定多个句柄。当事件触发时, 这些句柄将被按照它们绑定到事件时的顺序依次执行。如果句柄决定组织后续句柄被执行,它可以设置 $event->handled 为 true 。

=======================================================================

从这一句开始”我们可以附加一个方法到此 event “,读者可能 就不知道是什么意思了,于是看一下 CComponent 的源码:

/**
   * Raises an event.
   * This method represents the happening of an event. It invokes
   * all attached handlers for the event.
   * @param string the event name
   * @param CEvent the event parameter
   * @throws CException if the event is undefined or an event handler is invalid.
   */
  public function raiseEvent($name,$event)
{
  //事件名称同一小写化处理
    $name=strtolower($name);
    //先查看成员变量是否有以此命名的事件
    if(isset($this->_e[$name]))
    {
      //如果有,这个成员保存的是每一个事件处理器
      //以数组的方式保存
      foreach($this->_e[$name] as $handler)
      {
        //如果事件处理器是一个字符串,那么就是一个全局函数
        if(is_string($handler))
          call_user_func($handler,$event);
        //如果不是,那么有可能是一个数组,该数组包含一个对象和方法名
        //参考http://php.net/manual/en/function.is-callable.php
        else if(is_callable($handler,true))
        {
          // an array: 0 - object, 1 - method name
          list($object,$method)=$handler;
          //如果对象是一个对象名
          if(is_string($object)) // static method call
            call_user_func($handler,$event);
          //判断对象是否有要调用的方法
          else if(method_exists($object,$method))
            $object->$method($event);
          else
            throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler
"{handler}".',
              array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>$handler[1])));
        }
        else
          throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler
"{handler}".',
            array('{class}'=>get_class($this), '{event}'=>$name, '{handler}'=>gettype($handler))));
        // stop further handling if param.handled is set true
        //如果想停止继续循环获取事件的handler
//那么需要设置event的handled为true
        if(($event instanceof CEvent) && $event->handled)
          return;
      }
    }
    else if(YII_DEBUG && !$this->hasEvent($name))
      throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.',
        array('{class}'=>get_class($this), '{event}'=>$name)));
    //如果_e中没有这个成员也没关系
  }

我们再看一下 CEvent 的代码( CComponent.php ):

class CEvent extends CComponent
{
  /**
   * @var object the sender of this event
   */
  public $sender;
  /**
   * @var boolean whether the event is handled. Defaults to false.
   * When a handler sets this true, the rest uninvoked handlers will not be invoked anymore.
   */
  public $handled=false;

  /**
   * Constructor.
   * @param mixed sender of the event
   */
  public function __construct($sender=null)
  {
    $this->sender=$sender;
  }
}

CEvent 只包含两个变量 $sender 记录事件触发者, $handled 表示事件是否已经被“解决”。

接着我们再看一下如何给一个组件注册一个事件处理器:

/**
   * Attaches an event handler to an event.
   *
   * An event handler must be a valid PHP callback, i.e., a string referring to
   * a global function name, or an array containing two elements with
   * the first element being an object and the second element a method name
   * of the object.
   *
   * An event handler must be defined with the following signature,
   * <pre>
   * function handlerName($event) {}
   * </pre>
   * where $event includes parameters associated with the event.
   *
   * This is a convenient method of attaching a handler to an event.
   * It is equivalent to the following code:
   * <pre>
   * $component->getEventHandlers($eventName)->add($eventHandler);
   * </pre>
   *
   * Using {@link getEventHandlers}, one can also specify the excution order
   * of multiple handlers attaching to the same event. For example:
   * <pre>
   * $component->getEventHandlers($eventName)->insertAt(0,$eventHandler);
   * </pre>
   * makes the handler to be invoked first.
   *
   * @param string the event name
   * @param callback the event handler
   * @throws CException if the event is not defined
   * @see detachEventHandler
   */
  public function attachEventHandler($name,$handler)
  {
    $this->getEventHandlers($name)->add($handler);
  }
  /**
   * Returns the list of attached event handlers for an event.
   * @param string the event name
   * @return CList list of attached event handlers for the event
   * @throws CException if the event is not defined
   */
  public function getEventHandlers($name)
  {
    if($this->hasEvent($name))
    {
      $name=strtolower($name);
      if(!isset($this->_e[$name]))
        //新建一个CList保存事件的处理器
        $this->_e[$name]=new CList;
      return $this->_e[$name];
    }
    else
      throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.',
        array('{class}'=>get_class($this), '{event}'=>$name)));
}

由此可以看出,首先获取事件处理器对象,如果没有则使用 CList ( Yii 实现的一个链表)创建,然后将事件处理器 add 进这个对象中,这样就可以在 raiseEvent 时遍历所有的事件处理器进行处理了,有点儿类似 jQuery 中注册了多个 click 事件处理器之后,当 click 事件触发时,会按顺序调用之前注册的事件处理器。

希望本文所述对大家基于Yii框架的PHP程序设计有所帮助。

PHP 相关文章推荐
PHP EOT定界符的使用详解
Sep 30 PHP
简单的cookie计数器实现源码
Jun 07 PHP
php数组保存文本与文本反编成数组实例
Nov 13 PHP
smarty高级特性之过滤器的使用方法
Dec 25 PHP
thinkphp3.x自定义Action、Model及View的简单实现方法
May 19 PHP
ZendFramework2连接数据库操作实例
Apr 18 PHP
PHP实现基于回溯法求解迷宫问题的方法详解
Aug 17 PHP
基于ThinkPHP5.0实现图片上传插件
Sep 25 PHP
PHP创建自己的Composer包方法
Apr 09 PHP
PHP PDOStatement::columnCount讲解
Jan 30 PHP
php引用和拷贝的区别知识点总结
Sep 23 PHP
php上传后台无法收到数据解决方法
Oct 28 PHP
Yii框架多语言站点配置方法分析【中文/英文切换站点】
Apr 07 #PHP
php设计模式之适配器模式实例分析【星际争霸游戏案例】
Apr 07 #PHP
php设计模式之迭代器模式实例分析【星际争霸游戏案例】
Apr 07 #PHP
解决Laravel5.x的php artisan migrate数据库迁移创建操作报错SQLSTATE[42000]
Apr 06 #PHP
4种Windows系统下Laravel框架的开发环境安装及部署方法详解
Apr 06 #PHP
Laravel5.5+ 使用API Resources快速输出自定义JSON方法详解
Apr 06 #PHP
Laravel 5+ .env环境配置文件详解
Apr 06 #PHP
You might like
PHP递归返回值时出现的问题解决办法
2013/02/19 PHP
PHP中判断变量为空的几种方法小结
2013/11/12 PHP
PHP实现的统计数据功能详解
2016/12/06 PHP
用jQuery技术实现Tab页界面之二
2009/09/21 Javascript
jquery validation插件表单验证的一个例子
2010/03/03 Javascript
javascript中的onkeyup和onkeydown区别介绍
2013/04/28 Javascript
等待指定时间后自动跳转或关闭当前页面的js代码
2013/07/09 Javascript
js window.onload 加载多个函数和追加函数详解
2014/01/08 Javascript
jQuery学习笔记之jQuery.extend(),jQuery.fn.extend()分析
2014/06/09 Javascript
jQuery中next方法用法实例
2015/04/24 Javascript
fastclick插件导致日期(input[type=&quot;date&quot;])控件无法被触发该如何解决
2015/11/09 Javascript
基于JS代码实现实时显示系统时间
2016/06/16 Javascript
js跨域资源共享 基础篇
2016/07/02 Javascript
详细总结Javascript中的焦点管理
2016/09/17 Javascript
DOM事件探秘篇
2017/02/15 Javascript
angular之ng-template模板加载
2017/11/09 Javascript
JS 正则表达式验证密码、邮箱格式的实例代码
2018/10/28 Javascript
cordova+vue+webapp使用html5获取地理位置的方法
2019/07/06 Javascript
小程序自定义模板实现吸顶功能
2020/01/08 Javascript
微信小程序实现下滑到底部自动翻页功能
2020/03/07 Javascript
vue 验证两次输入的密码是否一致的方法示例
2020/09/29 Javascript
[31:33]2014 DOTA2国际邀请赛中国区预选赛 TongFu VS DT 第一场
2014/05/23 DOTA
Python3.6基于正则实现的计算器示例【无优化简单注释版】
2018/06/14 Python
python实现植物大战僵尸游戏实例代码
2019/06/10 Python
Windows下PyCharm2018.3.2 安装教程(图文详解)
2019/10/24 Python
通过实例解析python and和or使用方法
2020/11/14 Python
python中子类与父类的关系基础知识点
2021/02/02 Python
HTML5地理定位_动力节点Java学院整理
2017/07/12 HTML / CSS
一文彻底解决HTML5页面中长按保存图片功能
2019/06/10 HTML / CSS
英国最大的奢侈品零售网络商城:Flannels
2016/09/16 全球购物
Tarte Cosmetics官网:美国最受欢迎的化妆品公司之一
2017/08/24 全球购物
伦敦剧院及景点门票:Encore Tickets
2018/07/01 全球购物
电厂职工自我鉴定
2014/02/20 职场文书
2014年扫黄打非工作总结
2014/12/03 职场文书
MySQL性能压力基准测试工具sysbench的使用简介
2021/04/21 MySQL
SQL实现LeetCode(196.删除重复邮箱)
2021/08/07 MySQL