1. Yii PHP 框架分析(二) 
  2. 作者:wdy 
  3.  
  4. http://hi.baidu.com/delphiss/blog/item/54597af595085ad3f3d38552.html 
  5.  
  6. Yii是基于组件(component-based)的 web框架,CComponent类是所有组件的基类。 
  7.  
  8. CComponent类为子类提供了基于属性 (property)、事件(event)、行为(behavior)编程接口。 
  9.  
  10. 组件的属性(property) 
  11.  
  12. Ccomponent类并没有提供属性的变量存储,需要由子类来提供两个方法来实现。子类的getPropertyName()方法提供$component->PropertyName的取值操作数据,子类的setPropertyName($val)方法提供$component->PropertyName赋值操作。 
  13.  
  14. $width=$component->textWidth;     // 获取 textWidth 属性 
  15.  
  16. 实现方式为调用子类提供的方法 $width=$component->getTextWidth() 
  17.  
  18. $component->textWidth=$width;     // 设置 textWidth 属性 
  19.  
  20. 实现方式为调用子类提供的方法 $component->setTextWidth($width
  21.  
  22. public function getTextWidth() 
  23.     return $this->_textWidth; 
  24.  
  25. public function setTextWidth($value
  26.     $this->_textWidth=$value
  27.  
  28. 组件的属性值是大小写不敏感的(类的成员时大小写敏感的) 
  29.  
  30. 组件的事件(event) 
  31.  
  32. 组件事件是一种特殊的属性,它可以将事件处理句柄(可以是函数名、类方法或对象方法)注册(绑定)到一个事件名上,句柄在事件被唤起的时候被自动调用。 
  33. 组件事件存放在CComponent 的$_e[]数组里,数组的键值为事件的名字,键值的数值为一个Clist对象,Clist是Yii提供的一个队列容器,Clist的方法add()添加事件的回调handle。 
  34.  
  35. //添加一个全局函数到事件处理 
  36. $component-> onBeginRequest=”logRequest”; 
  37. //添加一个类静态方法到事件处理 
  38. $component-> onBeginRequest=array(“CLog”,” logRequest”); 
  39. //添加一个对象方法到事件处理 
  40. $component-> onBeginRequest=array($mylog,” logRequest”); 
  41.  
  42. 唤起事件: 
  43. $component ->raiseEvent('onBeginRequest '$event); 
  44. 会自动调用: 
  45. logRequest($event), Clog:: logRequest($event)和$mylog.logRequest($event
  46.  
  47. 事件句柄必须按照如下来定义 : 
  48. function methodName($event
  49.     ...... 
  50. $event 参数是 CEvent 或其子类的实例,它至少包含了"是谁挂起了这个事件"的信息。 
  51.  
  52. 事件的名字以”on”开头,在__get()和 __set()里可以通过这个来区别属性和事件。 
  53.  
  54. 组件行为(behavior) 
  55.  
  56. 组件的行为是一种不通过继承而扩展组件功能的方法(参见设计模式里的策略模式)。 
  57.  
  58. 行为类必须实现 IBehavior 接口,大多数行为可以从 CBehavior 基类扩展而来。 
  59.  
  60. IBehavior接口提供了4个方法。 
  61. attach($component)将自身关联到组件,detach($component) 解除$component关联,getEnabled()和setEnabled()设置行为对象的有效性。 
  62.  
  63. 行为对象存放在组件的$_m[]数组里,数组键值为行为名字符串,数组值为行为类对象。 
  64.  
  65. 组件通过attachBehavior ($name,$behavior)来扩展一个行为: 
  66. $component-> attachBehavior (‘render’,$htmlRender
  67. $component添加了一个名字为render的行为,$htmlRender 需是一个实现 IBehavior 接口的对象,或是一个数组: 
  68. array'class'=>'path.to.BehaviorClass'
  69.    'property1'=>'value1'
  70.    'property2'=>'value2'
  71.    * ) 
  72. 会根据数组的class来创建行为对象并设置属性值。 
  73.  
  74. $htmlRender被存储到$_m[‘render’]中。 
  75.  
  76. 外部调用一个组件未定义的方法时,魔术方法__call() 会遍历所有行为对象,如果找到同名方法就调用之。 
  77.  
  78.   
  79.  
  80. 例如 $htmlRender 有个方法 renderFromFile(),则可以直接当做组件的方法来访问: 
  81.  
  82. $component-> renderFromFile () 
  83.  
  84.   
  85.  
  86. CComponent源码分析 
  87.  
  88. //所有部件的基类 
  89. class CComponent 
  90. private $_e
  91. private $_m
  92.  
  93. //获取部件属性、事件和行为的magic method 
  94. public function __get($name
  95.    $getter='get'.$name
  96.    //是否存在属性的get方法 
  97.    if(method_exists($this,$getter)) 
  98.     return $this->$getter(); 
  99.    //以on开头,获取事件处理句柄 
  100.    else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name)) 
  101.    { 
  102.     // 事件名小写 
  103.     $name=strtolower($name); 
  104.     // 如果_e[$name] 不存在,返回一个空的CList事件句柄队列对象 
  105.     if(!isset($this->_e[$name])) 
  106.      $this->_e[$name]=new CList; 
  107.     // 返回_e[$name]里存放的句柄队列对象 
  108.     return $this->_e[$name]; 
  109.    } 
  110.    // _m[$name] 里存放着行为对象则返回 
  111.    else if(isset($this->_m[$name])) 
  112.     return $this->_m[$name]; 
  113.    else 
  114.     throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.'
  115.      array('{class}'=>get_class($this), '{property}'=>$name))); 
  116.  
  117. /** 
  118. * PHP magic method 
  119. * 设置组件的属性和事件 
  120. */ 
  121. public function __set($name,$value
  122.    $setter='set'.$name
  123.    //是否存在属性的set方法 
  124.    if(method_exists($this,$setter)) 
  125.     $this->$setter($value); 
  126.    //name以on开头,这是事件处理句柄 
  127.    else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name)) 
  128.    { 
  129.     // 事件名小写 
  130.     $name=strtolower($name); 
  131.     // _e[$name] 不存在则创建一个CList对象 
  132.     if(!isset($this->_e[$name])) 
  133.      $this->_e[$name]=new CList; 
  134.     // 添加事件处理句柄 
  135.     $this->_e[$name]->add($value); 
  136.    } 
  137.    // 属性没有set方法,只有get方法,为只读属性,抛出异常 
  138.    else if(method_exists($this,'get'.$name)) 
  139.     throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.'
  140.      array('{class}'=>get_class($this), '{property}'=>$name))); 
  141.    else 
  142.     throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.'
  143.      array('{class}'=>get_class($this), '{property}'=>$name))); 
  144.  
  145. /** 
  146. * PHP magic method 
  147. * 为isset()函数提供是否存在属性和事件处理句柄的判断 
  148. */ 
  149. public function __isset($name
  150.    $getter='get'.$name
  151.    if(method_exists($this,$getter)) 
  152.     return $this->$getter()!==null; 
  153.    else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name)) 
  154.    { 
  155.     $name=strtolower($name); 
  156.     return isset($this->_e[$name]) && $this->_e[$name]->getCount(); 
  157.    } 
  158.    else 
  159.     return false; 
  160.  
  161. /** 
  162. * PHP magic method 
  163. * 设置属性值为空或删除事件名字对应的处理句柄 
  164. */ 
  165. public function __unset($name
  166.    $setter='set'.$name
  167.    if(method_exists($this,$setter)) 
  168.     $this->$setter(null); 
  169.    else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name)) 
  170.     unset($this->_e[strtolower($name)]); 
  171.    else if(method_exists($this,'get'.$name)) 
  172.     throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.'
  173.      array('{class}'=>get_class($this), '{property}'=>$name))); 
  174.  
  175.   
  176.  
  177. /** 
  178. * PHP magic method 
  179. * CComponent未定义的类方法,寻找行为类里的同名方法,实现行为方法的调用 
  180. */ 
  181. public function __call($name,$parameters
  182.    // 行为类存放的$_m数组不空 
  183.    if($this->_m!==null) 
  184.    { 
  185.     // 循环取出$_m数组里存放的行为类 
  186.     foreach($this->_m as $object
  187.     { 
  188.      // 行为类对象有效,并且方法存在,调用之 
  189.      if($object->enabled && method_exists($object,$name)) 
  190.       return call_user_func_array(array($object,$name),$parameters); 
  191.     } 
  192.    } 
  193.    throw new CException(Yii::t('yii','{class} does not have a method named "{name}".'
  194.     array('{class}'=>get_class($this), '{name}'=>$name))); 
  195.  
  196. /** 
  197. * 根据行为名返回行为类对象 
  198. */ 
  199. public function asa($behavior
  200.    return isset($this->_m[$behavior]) ? $this->_m[$behavior] : null; 
  201.  
  202. /** 
  203. * Attaches a list of behaviors to the component. 
  204. * Each behavior is indexed by its name and should be an instance of 
  205. * {@link IBehavior}, a string specifying the behavior class, or an 
  206. * array of the following structure: 
  207. * <pre> 
  208. * array( 
  209. *     'class'=>'path.to.BehaviorClass', 
  210. *     'property1'=>'value1', 
  211. *     'property2'=>'value2', 
  212. * ) 
  213. * </pre> 
  214. * @param array list of behaviors to be attached to the component 
  215. * @since 1.0.2 
  216. */ 
  217. public function attachBehaviors($behaviors
  218.    // $behaviors为数组 $name=>$behavior 
  219.    foreach($behaviors as $name=>$behavior
  220.     $this->attachBehavior($name,$behavior); 
  221.  
  222.  
  223. /** 
  224. * 添加一个行为到组件 
  225. */ 
  226. public function attachBehavior($name,$behavior
  227.    /* $behavior不是IBehavior接口的实例,则为 
  228.    * array( 
  229.    *     'class'=>'path.to.BehaviorClass', 
  230.    *     'property1'=>'value1', 
  231.    *     'property2'=>'value2', 
  232.    * ) 
  233.    * 传递给Yii::createComponent创建行为了并初始化对象属性 
  234.    */ 
  235.    if(!($behavior instanceof IBehavior)) 
  236.     $behavior=Yii::createComponent($behavior); 
  237.    $behavior->setEnabled(true); 
  238.    $behavior->attach($this); 
  239.    return $this->_m[$name]=$behavior
  240.  
  241. /** 
  242. * Raises an event. 
  243. * This method represents the happening of an event. It invokes 
  244. * all attached handlers for the event. 
  245. * @param string the event name 
  246. * @param CEvent the event parameter 
  247. * @throws CException if the event is undefined or an event handler is invalid. 
  248. */ 
  249. public function raiseEvent($name,$event
  250.    $name=strtolower($name); 
  251.    // _e[$name] 事件处理句柄队列存在 
  252.    if(isset($this->_e[$name])) 
  253.    { 
  254.      // 循环取出事件处理句柄 
  255.     foreach($this->_e[$nameas $handler
  256.     { 
  257.      // 事件处理句柄为全局函数 
  258.      if(is_string($handler)) 
  259.       call_user_func($handler,$event); 
  260.      else if(is_callable($handler,true)) 
  261.      { 
  262.       // an array: 0 - object, 1 - method name 
  263.       list($object,$method)=$handler
  264.       if(is_string($object)) // 静态类方法 
  265.        call_user_func($handler,$event); 
  266.       else if(method_exists($object,$method)) 
  267.        $object->$method($event); 
  268.       else 
  269.        throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".'
  270.         array('{class}'=>get_class($this), '{event}'=>$name'{handler}'=>$handler[1]))); 
  271.      } 
  272.      else 
  273.       throw new CException(Yii::t('yii','Event "{class}.{event}" is attached with an invalid handler "{handler}".'
  274.        array('{class}'=>get_class($this), '{event}'=>$name'{handler}'=>gettype($handler)))); 
  275.     // $event 的handled 设置为true后停止队列里剩余句柄的调用 
  276.     if(($event instanceof CEvent) && $event->handled) 
  277.       return
  278.     } 
  279.    } 
  280.    else if(YII_DEBUG && !$this->hasEvent($name)) 
  281.     throw new CException(Yii::t('yii','Event "{class}.{event}" is not defined.'
  282.      array('{class}'=>get_class($this), '{event}'=>$name)));