Yii中的核心CComponent类详解

CComponet类是Yii中影响最广的类。不仅Application继承自它,Event,Behavior,Action,Controller,Widget总之很多都是继承自它。在这里我们不一一列举,到底有哪些类是继承字CComponent类的。我们在这里主要是从现实意义上来剖析CComponet的作用,角色。当读完本篇后,你应该对继承自Yii的CComponent的类有什么样的属性,什么样的特性和作用,了然于胸。

CComponent类是一个很庞大的类,我们就按照由粗到细的过程来讲解。让我们先直观感受下CComponet类是长什么样子的。

下图为CComponet的类视图

在解释这个类前,我们先简单介绍下php中几个重要的函数,在yii中,他们被频繁的使用。

__set() is run when writing data to inaccessible properties.

__get() is utilized for reading data from inaccessible properties.

__isset() is triggered by calling isset() or empty() on inaccessible properties.

__unset() is invoked when unset() is used on inaccessible properties.

__call() is triggered when invoking inaccessible methods in an object context.

__callStatic() is triggered when invoking inaccessible methods in a static context.

上面是总php.net复制过来的,这里的英文都很简单,这里最关键的是要注意inaccessible这个字眼。

不过,为了下面能讲解的更顺畅,我捎带解释下吧。

__set表示,当以$obj->variable=value这种形式保存值时,如果当这个variable不是class的属性时,也就是压根没有定义时,就会运行__set方法。那么有什么用呢?这样我们可以利用这个特性,伪造出很多属性来,比如这里,variable虽然不存在,但是当他执行这个__set函数的时候,我可以让其运行另一个函数,来获取对应的数值。因此,也可以借由这个来隐含使用setter。这样无论在使用便捷性上,还是安全性上都会更好。

__get表示,当以$obj->variable这种形式获取值时候,如果当这个variable不是class的属性时,也就是压根没有定义时,就会运行__get方法。这个特点常备用来封装getter,具体的类似__set中的讲解。

__isset表示,当以isset($obj->variable)这种方式验证时候,如果当这个variable不是class的属性时,也就是压根没有定义时,就会运行__isset方法,注意此时不会再运行__get方法了。因为运行__get方法就根本不是变量行为了。有悖于isset的初衷。

__unset正好是__isset的逆向,这里就偷个懒不解释了,但是注意也不会运行__get。

__call表示,当以$obj->method()这种形式进行函数调用时,如果这个method不存在,那么就会调用__call方法。

__callStatic,当以$obj::method()这种形式进行函数调用时,如果这个method不存在,那么就会调用__callStatic方法。

Ok讲解完了,让我们言归正传。想要整体把握,需要深刻理解CComponet中的__call,__get,__isset,__set,__unset方法。

接下来,让我们逐个分解。

__set                                                            

 1     public function __set($name,$value)
 2     {
 3         $setter='set'.$name;
 4         if(method_exists($this,$setter))
 5             return $this->$setter($value);
 6         else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
 7         {
 8             // duplicating getEventHandlers() here for performance
 9             $name=strtolower($name);
10             if(!isset($this->_e[$name]))
11                 $this->_e[$name]=new CList;
12             return $this->_e[$name]->add($value);
13         }
14         else if(is_array($this->_m))
15         {
16             foreach($this->_m as $object)
17             {
18                 if($object->getEnabled() && (property_exists($object,$name) || $object->canSetProperty($name)))
19                     return $object->$name=$value;
20             }
21         }
22         if(method_exists($this,'get'.$name))
23             throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.',
24                 array('{class}'=>get_class($this), '{property}'=>$name)));
25         else
26             throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',
27                 array('{class}'=>get_class($this), '{property}'=>$name)));
28     }

首先这个函数分4大块,分别为4-5行,6-13行,14-21行,22-27行,每一块都有特殊的作用。

首先看4-5行,这里是看是否存在相应的setter(就是以set开头的,比如setComponents之类的),有的话则执行这个setter。

然后是6-13行,这个是专门为Yii的Event机制提供的,如果以on开头并且定义了同名的方法时,会执行此段。这里的函数体,实现了,即便你没有在类中定义形如onClick这样的属性,还是可以写入的。主要作用是当钱定义了Event后,将对应的EventHandler导入$_e变量。这里涉及到你需要对Event有一定的理解,如果不太了解的,推荐阅读Yii中的Event和Behaviour理解

Ok,注意了,要记住$_e是用来存放event Handler的并且是个CList(其实可以认为是一个Array)。

再看14-21行,当$_m是个数组时,会遍历$_m中的条目,这里插播一下,$_m是什么?他其实就是一个装着Behavior的数组。那么为什么要判断起是不是数组,因为只有当是数组的时候,才说明里面存在Behavior。插播结束,这就回来。我们接着看代码,遍历操作将存在$_m中的Behavior取了出来,当behavior必须启用,同时还要满足这个behavior类中存在需要写入的属性(这个属性可是实际存在的),如果不满足这个则需要满足这个behaivor中存在这个属性名对应的setter。当满足这些条件后,就说明可以通过$object->$name方式访问到,所以运行。

最后看22-27行,这部分只是报错用的。当存在getter时候,就提示read only。当不存在getter时,就提示not defined。

好了,完工一个__set了,后面可比这个轻松多了,因为很多基础在这里讲掉了,所以,不用担心后面路漫漫。

__get                                                               

 1 public function __get($name)
 2     {
 3         $getter='get'.$name;
 4         if(method_exists($this,$getter))
 5             return $this->$getter();
 6         else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
 7         {
 8             // duplicating getEventHandlers() here for performance
 9             $name=strtolower($name);
10             if(!isset($this->_e[$name]))
11                 $this->_e[$name]=new CList;
12             return $this->_e[$name];
13         }
14         else if(isset($this->_m[$name]))
15             return $this->_m[$name];
16         else if(is_array($this->_m))
17         {
18             foreach($this->_m as $object)
19             {
20                 if($object->getEnabled() && (property_exists($object,$name) || $object->canGetProperty($name)))
21                     return $object->$name;
22             }
23         }
24         throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',
25             array('{class}'=>get_class($this), '{property}'=>$name)));
26     }

有了__set的分析,这里就会很多,这里是5块,比__set多的一块,是用来获取behaivor的。咦,那为什么__set中没有写入behavior的操作?为了更好的语义,在yii中选择没有写入behavior这个概念,只能通过attach,所以你要attach一个behavior,就用attachBehavior。明白了,这点,让我们继续看代码,这5块分别为,4-5行,6-13行,14-15行,16-23行,24-25行。

4-5行:看有没有getter,有的话就运行getter。

6-13行:如果是on开头的话,就返回$_e中对应的项,如果没有就初始化一个CList并传回。

14-15行:如果在$_m中存在相同名称的内容,就返回之。(这个就是返回behavior的那个)。

16-23行:此处的判断和__set的14-21行的情况极其类似。在这里就不多分析了。

24-25行:异常处理。
__isset                                                              

 1     public function __isset($name)
 2     {
 3         $getter='get'.$name;
 4         if(method_exists($this,$getter))
 5             return $this->$getter()!==null;
 6         else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
 7         {
 8             $name=strtolower($name);
 9             return isset($this->_e[$name]) && $this->_e[$name]->getCount();
10         }
11         else if(is_array($this->_m))
12         {
13              if(isset($this->_m[$name]))
14                  return true;
15             foreach($this->_m as $object)
16             {
17                 if($object->getEnabled() && (property_exists($object,$name) || $object->canGetProperty($name)))
18                     return true;
19             }
20         }
21         return false;
22     }

原先在PHP中isset主要是用于对变量是否存在作判断的,在这里yii将其的使用范围扩大了。感觉更贴切的说isset变成了一个判断是否存在相应的getter(4-5行),对应的Event Handler是否含有真正的handler(6-10行),对应的behavior是否存在(11-20行)。这里需要注意下15-19行,这里使得,不仅仅可以判断behavior本身,包括其所属的属性和getter方法也是可以判断是否存在的。简单的理解就是当isset作用于不存在的属性时,其变成了一个判断是否存在getter,event handler,behavior的方法。

__unset                                                              

 1     public function __unset($name)
 2     {
 3         $setter='set'.$name;
 4         if(method_exists($this,$setter))
 5             $this->$setter(null);
 6         else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
 7             unset($this->_e[strtolower($name)]);
 8         else if(is_array($this->_m))
 9         {
10             if(isset($this->_m[$name]))
11                 $this->detachBehavior($name);
12             else
13             {
14                 foreach($this->_m as $object)
15                 {
16                     if($object->getEnabled())
17                     {
18                         if(property_exists($object,$name))
19                             return $object->$name=null;
20                         else if($object->canSetProperty($name))
21                             return $object->$setter(null);
22                     }
23                 }
24             }
25         }
26         else if(method_exists($this,'get'.$name))
27             throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.',
28                 array('{class}'=>get_class($this), '{property}'=>$name)));
29     }

unset的作用就是释放资源,了解了unset的目的,我们可以看看__unset做了哪些,

4-5行:当有setter存在的时候,调用setter,并且设置值为null。

6-7行:将对应的Event Handler卸载掉(可能清空更适合,毕竟是存在一个数组中的)。

8-25行:将对应的behavior进行detach操作,如果不是behavior,而是其setter或属性的话,就分别调用setter方法传入null,或将该属性设置成null

26-28行:报错用的,表示只读,所以也不可操作。

 

__call                                                   

 终于到了最后一个了,阶段性小胜利。。。不过可不是终点。。。

 1     public function __call($name,$parameters)
 2     {
 3         if($this->_m!==null)
 4         {
 5             foreach($this->_m as $object)
 6             {
 7                 if($object->getEnabled() && method_exists($object,$name))
 8                     return call_user_func_array(array($object,$name),$parameters);
 9             }
10         }
11         if(class_exists('Closure', false) && $this->canGetProperty($name) && $this->$name instanceof Closure)
12             return call_user_func_array($this->$name, $parameters);
13         throw new CException(Yii::t('yii','{class} and its behaviors do not have a method or closure named "{name}".',
14             array('{class}'=>get_class($this), '{name}'=>$name)));
15     }

这段代码,首先判断了$_m是不是为空,就是有没有behavior。这里比较奇怪的是,之前都是使用is_array判断的,现在使用!=null判断。。估计有可能是不同时期或者不同的人写的。如果有behavior的话,则看是否启用了该behavior,如果启用的话,并且存在指定的方法的话,就运行之。

Ok,至此我们讲解完了基础。

让我们进一步更深入一些,也更和实际接轨吧

 查看余下的函数,我们很容易发现剩下的函数都是和Event和Behavior有关的。包含了绑定或解绑Behavior和EventHandler,禁用和启用Behavior。另外还有些是工具类的,用于辅助判断Behavior或Event是否存在,或者获取Behavior或Event或EventHandler。这里我们不准备深入细节,每个函数的代码都相对简单。我们这里主要是对CComponent的抽象概念做个总结。

根据以上我们收集的信息。我们可以对CComponent做以下总结:

1、首先他是一个Component。。。实在也没有办法找出更好的描述了。。。,这个Component可以认为是Application中一个元件,该元件没有特定的行为(行为由子类定义)。

2、具有把getter,setter,behavior中的getter,setter,以及EventHandler,如果属性般访问。

3、虽然他没有特定的行为,但是有响应一个事件或者响应一系列事件(就是Behavior)的能力。

4、具有获取自身需要响应的事件的能力。

 

还是很抽象吧。。。。那就对了。因为CComponent本来就是高度抽象的。我们关键要理解他的这四个特性。之后的Widget啊,Application,只要知道具有CComponent的特点即可了





 » 转载请注明来源:AnyKoro Blog » 《Yii中的核心CComponent类详解》
 » 订阅本站:RSS订阅

发表评论

您也可以使用微博账号登陆

无觅相关文章插件,快速提升流量