Yii框架base包代码分析

周日闲来无事,打开Yii的源代码学习一下,今天主要看的代码都是base包里面的,也就是框架根目录下的base目录里的代码。这个目录里的代码不多,但是类很多,而且这些类对整个框架来说至关重要,都是非常基本的组件。老实说,这里面的代码,我已经看了无数次了,但是总觉得常看常新,转念一想,这也实属正常,毕竟这是原作者数年框架开发经验的结晶,岂是我这等工作一两年的菜鸟能随便吃透的?

最近又玩了两次Visual Paradigm这个软件,觉得自己操作水平又有进步,甚感欣喜,所以今天既然分析源代码,就正好再练练手,于是乎,我开始用VP UML开始画base包里的类,得图如下。(说明:这个图是基于Yii的1.1.8版本,另外这里面少了两个类,因为这两个类的功能比较特殊,我没有细看,所以也就没有画在图里,看后面是不是有兴致再来专门画一张,缺失的两个类是CSecurityManager和CStatePersister。)

从这个图里各个类占的面积里,我们不难看出,最重要的类有四个,CComponent,CModule,CApplication,CModel。事实上,这四个类也贯穿着Yii框架应用开发过程的始终,扮演着至关重要的角色。然而,如果你真的用Yii开发应用,可能一个应用做完了,却又没怎么见过这四个类的踪影,因为他们太基础了,始终处在水面之下,使得你可能根本就忽视了它们的存在。下面,我就一个一个来简单说一下我的学习心得。

CComponent类,是整个Yii框架组件模型的基础,Yii框架内几乎所有的对象都是组件,其基类都是CComponent。细看其方法,我们发现基本上可以归类为三种,一是魔术方法,就是一些双下划线开头的方法,一是跟Behavior有关的方法,最后是跟Event有关的方法。这也就引出了组件的三大主要特性。属性成员,事件驱动和行为。

第一个是“属性(Property)成员”,加引号是因为这个名词是我自己拼凑的,大家尝试理解一下好了,类(class)里面有的东西,统称为成员(member),这里面又有细分,有域(field),有属性(property),有方法(method)。在这三个东西里面,其实属性是一种比较特殊的域,所以,在很多编程语言里面,并没有专门去实现属性这个东西,比如PHP里面就没有,PHP里面只有域的概念,也就是我们说的成员变量,可以是公有的、保护的或者私有的。属性比起域来说,有更高的要求,比如属性有访问控制,有的属性是只读的、只写的、读写的,这是普通公有成员变量做不到的;属性还可以进行类型检查;属性还可以附加一些逻辑;在这些好处的基础上,属性还可以像普通成员变量那样去访问。这些主要是通过PHP5里面的魔术方法来实现的,可以看看CComponent里面的几个魔术方法来了解详情。

第二个是事件驱动,组件模型里面实现了事件驱动的全套机制。包括事件触发,事件处理器的绑定,解绑定。组件内部,使用一个私有数组来保存事件和其对应的处理器(handler),数组的键值是事件的名称,数组的值是一个Yii实现的数据结构CList,这个链表里每个元素都都是一个回调函数。在Yii框架中,要使用事件是比较简单,首先创建一个以on+事件名称的函数,作为这个事件的句柄,同时也是这个事件的触发器,在这个函数里调用CComponent的RaiseEvent函数,来使这个事件发生。虽然有attachEvnentHandler函数用于绑定事件处理器,但是CComponent提供了一种更加优雅便捷的方式来绑定处理器,可以直接给on+事件名的属性赋值一个回调函数即可。这个也是因为在魔术方法__set中做了相应的处理。

第三个是行为,行为对我来说,一直都是一种难以理解的东西,简单来说,就是一种在运行时扩展一个类的方法的方案,听起来非常激动人心,但是更多的时候,并没有什么用处,因为我们扩展一个类,经常的做法是继承,甚至事件模型本身就可以极大地丰富一个对象能做到地事情,也同样是一种松耦合。官方文档库里有一篇东西,试图去解释这个问题,里面提到的是这个行为模式,可以代替C++里面的多继承,又说可以像Ruby里面那样,在运行时扩展一个对象的方法。事实上,像js这样的原型继承语言,可以在运行时,给一个对象增加方法,增加完了马上就可以调用,你可以认为,在Yii框架下,所有组件都有这个能力,事实上,我还没有碰到一个让人信服的实例,非用Behavior才能解决问题的。

组件扯得有点多了,不过,确实是因为这个东西事关重要,是整个Yii框架所有对象得基类,确实值得去花功夫透彻理解。接下来看以下Module,翻成中文是模块,或者模组,看了这个东西的实现后,我更倾向于模组。因为最小的功能单位是组件,然后若干组件的逻辑集合,就是Module了,这么看,模组更形象一点,事实上几个模组在一起,可以组成一个更大的模组。模组里面的公有成员变量很少,主要就是一个$preload和$behaviors,剩下的都是一些刚才提到的属性,这个在图里比较好区分,属性我没有用$开头。模组主要就是管理自身组成的组件,其他模组,以及相关的路径信心,参数配置信息等。功能比较简单,就不多说了。

然后看一个非常特殊的模组,也就是我们说的应用(Application)。应用的实质就是一个模组,那么它到底特殊在什么地方呢?在我看来,主要有:首先是多了一些应用属性的配置,比如路径、语言、编码格式、日期格式、数字格式等等;其次是多了一些缺省的组件,有DB,Request,UrlManager等等,最后,应用比普通模组多了一些方法,比如run,代表运行,就像C程序的main函数一样,是整个应用的执行起始点,再比如processRequest,专门处理请求的函数等等。Application类看起来虽然东西很多,事实上,其思路非常清楚,功能也相对简单,所以,也没有什么太多好说的。

最后要说的是Model,有即模型,不过我觉得这个中文词不太能表达Model的意思,我后面还是用原来的单词。CModel是Model的抽象类,虽然代码很多,很长,其功能同样,相对比较简单,画出图来后,我们不难对Model进行一个归纳,就是一组属性的集合,所有的方法都是在管理属性。属性的读写、属性的验证、属性验证后的错误管理等等等等,此外还有很多属性验证器的管理。特别想说的一点,这个Model里面放入了对属性标签的管理,就是每个attribute都有对应的label,事实上,这些东西应该属于展示用途的,而Model在定义上,应该更加关注数据抽象,而不应该关注表现,我不知道原作者这里到底是怎么想的,这些label之类的东西,理论上不应该进入到抽象类里面的,至少我不这么认为。不过,关于label的方法都是一些空方法,也还可以忍受吧。最后一提,Model里有个scenario的概念,中文就是说的是场景,关于这个东西,我理解得还不是太透彻,今后如果有机会,再做说明。