1. 简介

Yii 是一个基于部件、用于开发大型 Web 应用的高性能 PHP 框架。它将 Web 编程中的可重用性发挥到极致,能够显著加速开发进程。Yii(读作“易”)代表简单(easy)、高效(efficient)、可扩展 (extensible)。它是轻量级的,又装配了很好很强大的缓存部件,因此尤其适合开发大流量的应用,比如门户、论坛、内容管理系统(CMS)、电子商务系统,等等。

Yii 是一个纯OOP的通用Web编程框架,和大多数 PHP 框架一样,Yii 是一个 MVC 框架。

Yii在设计时借鉴和集成了很多其他著名web编程框架和应用的思想和工作:

● Prado:这是Yii的思想的主要来源。Yii采用了它的基于部件和事件驱动的编程范式,数据库抽象层,模块应用框架,国际化和本地化以及其它一些东西。
● Ruby on Rails:Yii继承了它的易于配置的特点。Yii还参考了它的活动记录设计模式的实现。
● jQuery:作为JavaScript框架的基础集成到Yii中
● Symfony:Yii参考了它的过滤器设计以及插件框架
● Joomla:Yii参考了它的模块设计以及消息传递体系。

2. YII的设计模式

下面将以一个树图描述了一个简单的web服务所产生的目录结构。通过这个目录结构,我们会发现除了Web常见的资源、图片、CSS、主题等元素外,还有Yii所特有的部件、控制器、模块、消息、视图等元素。下面我们将介绍Yii特有的元素的功能及实现。

testdrive/
index.php Web 应用入口脚本文件
assets/ 包含公开的资源文件
css/ 包含 CSS 文件
p_w_picpaths/ 包含图片文件
themes/ 包含应用主题
protected/ 包含受保护的应用文件
yiic yiic 命令行脚本
yiic.bat Windows 下的 yiic 命令行脚本
commands/ 包含自定义的 'yiic' 命令
shell/ 包含自定义的 'yiic shell' 命令
components/ 包含可重用的用户部件
MainMenu.php 'MainMenu' 挂件类
Identity/php 用来认证的 'Identity' 类
views/ 包含挂件的视图文件
mainMenu.php 'MainMenu' 挂件的视图文件
config/ 包含配置文件
console.php 控制台应用配置
main.php Web 应用配置
controllers/ 包含控制器的类文件
SiteController.php 默认控制器的类文件
extensions/ 包含第三方扩展
messages/ 包含翻译过的消息
models/ 包含模型的类文件
LoginForm.php 'login' 动作的表单模型
ContactForm.php 'contact' 动作的表单模型
runtime/ 包含临时生成的文件
views/ 包含控制器的视图和布局文件
layouts/ 包含布局视图文件
main.php 所有视图的默认布局
site/ 包含 'site' 控制器的视图文件
contact.php 'contact' 动作的视图
index.php 'index' 动作的视图
login.php 'login' 动作的视图
system/ 包含系统视图文件


2.1. YII的MVC模式

Yii 实现了 Web 编程中广为采用的“模型-视图-控制器”(MVC)设计模式。MVC 致力于分离业务逻辑和用户界面,这样开发者可以很容易地修改某个部分而不影响其它。在 MVC 中,模型表现信息(数据)和业务规则;视图包含用户界面中用到的元素,比如文本、表单输入框;控制器管理模型和视图间的交互。

除了 MVC,Yii 还引入了一个叫做 application 的前端控制器,它表现整个请求过程的运行环境。Application 接收用户的请求并把它分发到合适的控制器作进一步处理。

下图描述了一个 Yii 应用的静态结构:

图 1 Yii 应用的静态结构

下图描述了一个 Yii 应用处理用户请求时的典型流程:

图 2 Yii 应用的典型流程

1. 用户访问 http://www.example.com/index.php?r=post/show&id=1,Web 服务器执行入口脚本 index.php 来处理该请求。
2. 入口脚本建立一个应用实例并运行之。
3. 应用从一个叫 request 的应用部件获得详细的用户请求信息。
4. 通过 urlManager 这个应用部件,确定用户要请求的控制器和动作。
5. 应用建立一个被请求的控制器实例来进一步处理用户请求,控制器确定由它的actionShow 方法来处理 show 动作。然后它建立并应用和该动作相关的过滤器,如果过滤器允许的话,动作被执行。
6. 动作从数据库读取一个 ID 为 1 的 Post 模型。
7. 动作使用 Post 模型来渲染一个叫 show 的视图。
8. 视图读取 Post 模型的属性并显示之。
9. 视图运行一些挂件。
10. 视图的渲染结果嵌在布局中。
11. 动作结束视图渲染并显示结果给用户。

2.1.1. 调试模式

一个 Yii 应用能够根据 YII_DEBUG 常量的指示以调试模式或者生产模式运行。默认情况下该常量定义为 false,代表生产模式。要以调试模式运行,在包含 yii.php 文件前将此常量定义为 true。应用以调试模式运行时效率较低,因为它会生成许多内部日志。从另一个角度来看,发生错误时调试模式会产生更多的调试信息,因而在开发阶段非常有用。但在正式上线之后,则需要去掉调试模式,因为调试模式会打印很多额外信息,这些信息会在一定程度上影响其运行的性能。

2.2. 应用

应用表示一个请求处理的上下文。它的主要任务是分解用户的请求并且把它分发到恰当的控制器中进行进一步的处理。它也保存应用级的配置,因此也被称为前端控制器。

默认情况下,应用是 CWebApplication 类的一个实例。 要对其进行定制, 通常是在应用实例被创建的时候提供一个配置文件 (或数组) 来初始化其属性值。 另一个定制应用的方法就是扩展 CWebApplication 类。配置是一个键值对的数组。 每个键名都对应应用实例的一个属性, 相应的值为属性的初始值。 如果应用的配置非常复杂,也可以将其分解成多个文件,每个文件返回配置数组的一部分。

应用根目录表示包含所有安全敏感的PHP脚本和数据的根目录。缺省情况下,它是包含入口脚本的目录的名为protected的子目录。应当组织Web用户访问这个目录。

可以通过灵活的部件框架方便的进行应用功能的定制和扩展。应用管理着部件的集合,每个部件实现特定的功能。例如,应用可能在CUrlManager 和 CHttpRequest的帮助下处理一个用户的请求。

Yii预定义了一个核心部件集合来提供Web应用中的常用功能,下面是CWebApplication类中预定义 核心部件:

assetManager:CAssetManager - 管理私有资源文件的发布
authManager:CAuthManager – 管理基于角色的访问控制(RBAC)
cache:CCache – 提供数据缓存功能
clientScript:CClientScript – 管理客户端脚本(javascript和CSS)
coreMessages:CPhpMessageSource – 提供Yii框架所使用的转换过的核心消息
db:CDbConnection – 提供数据库连接
errorHandler:CErrorHandler – 处理未被捕获的PHP错误和异常
messages:CPhpMessageSource – 提供Yii应用使用的转换过的消息
request:CHttpRequest – 提供和用户请求相关的信息
securityManager:CSecurityManager – 提供安全相关的服务,如散列,加密
session:CHttpSession – 提供会话相关的功能
statePersister:CStatePersister – 提供全局持续状态方法
urlManager:CUrlManager – 提供URL解析和创建功能
user:CWebUser – 表示当前用户的身份信息
themeManager:CThemeManager – 管理主题

处理一个用户请求时,一个应用将会经历这样一个生命周期:

1. 设置自动加载和错误处理类
2. 注册核心应用部件
3. 加载应用配置
4. 使用CApplication::init()初始化应用
加载静态应用部件
5. 触发onBeginRequest事件
6. 处理用户请求:
解析用户请求
创建控制器
运行控制器
7. 触发onEndRequest事件

2.3. 控制器

控制器是 CController 或者其子类的实例。 用户请求应用时,创建控制器。 控制器执行请求action,action通常引入必要的模型并提供恰当的视图。 最简单的action仅仅是一个控制器类方法,此方法的名字以action开始。

控制器有默认的action。用户请求没指定哪一个action执行时,将执行默认的action。 缺省情况下,默认的action名为index。可以通过设置CController::defaultAction改变默认的action。

下边是最小的控制器。因为控制器未定义任何action,请求时会抛出异常。

class SiteController extends CController
{
}


CWebApplication在处理一个新请求时,实例化一个控制器。程序通过控制器的ID,并按一定规则确定控制器类及控制器类所在位置。

一个动作可以定义为一个函数或者一个动作类。当请求这个动作时,控制器实例化这个类。这样就可以复用和重用动作。

过滤器是配置为控制器动作之前或之后执行的一段代码。例如可以设计一个性能过滤器用于计时动作执行的时间。一个动作可以有多个过滤器。过滤器可以定义为控制器类的方法,也可以是CFilter类的子类。

2.4. 模型

模型是 CModel 或其子类的实例。 模型用于保持数据以及和数据相关的业务规则。模型描述了一个单独的数据对象。它可以是数据表中的一行数据或者用户输入的一个表单。数据中的各个字段都描述了模型的一个属性。这些属性都有一个标签,都可以被一套可靠的规则验证。

Yii 从表单模型和 active record 实现了两种模型。 它们都继承自基类 CModel。

表单模型是CFormModel的实例。表单模型用于保存通过收集用户输入得来的数据。这样的数据通常被收集,使用,然后被抛弃。例如,在一个登录页面上,我们可以使用一个表单模型来描述诸如用户名,密码这样的由最终用户提供的信息。

Active Record (AR) 是一种面向对象风格的,用于抽象数据库访问的设计模式。任何一个 AR 对象都是 CActiveRecord 或其子类的实例, 它描述的数据表中的单独一行数据。这行数据中的字段被描述成 AR 对象的一个属性。

2.5. 视图


视图是一个包含了主要的用户交互元素的PHP脚本。他可以包含PHP语句,但是建议这些语句不要去改变数据模型,且最好能够保持其单纯性(单纯作为视图)!为了实现逻辑和界面分离,大部分的逻辑应该被放置于控制器或模型里,而不是视图里。

视图有一个当其被渲染(render)时用于校验的名称。视图的名称与其脚本名称是一样的。例如:视图 edit 的名称出自一个名为 edit.php 的脚本文件。通过 CController::render() 调用视图的名称可以渲染一个视图。这个方法将在 protected/views/ControllerID 目录下寻找对应的视图文件。

在视图脚本内部,可以通过 $this 来访问控制器实例。可以在视图里以 $this->propertyName 的方式 pull 控制器的任何属性。也可以用以下 push 的方式传递数据到视图里:

$this->render('edit', array(
'var1'=>$value1,
'var2'=>$value2,
));


在以上的方式中, render() 方法将提取数组的两个参数到变量里。其产生的结果是,在视图脚本里,我们可以直接访问变量 $var1 和 $var2。

布局是一种特殊的视图文件用来修饰视图。它通常包含了用户交互过程中常用到的一部分视图。

组件是 CWidget 或其子类的实例。它是一个主要用于描述特定意图的组成部分。组件通常内嵌于一个视图来产生一些复杂却独立的用户界面。例如,一个日历组件可以用于渲染一个复杂的日历界面。组件可以在用户界面上更好的实现重用。

系统视图的渲染通常用于展示 Yii 的错误和日志信息。例如,当用户请求来一个不存在的控制器或动作时,Yii 会抛出一个异常来解释这个错误。 这时,Yii 就会使用一个特殊的系统视图来展示这个错误。

2.6. 部件

Yii 应用构建于对象是规范编写的部件之上。部件是 CComponent 或其衍生类的实例。使用部件主要就是涉及访问其属性和挂起/处理它的事件。基类 CComponent 指定了如何定义属性和事件。

部件的属性就像对象的公开成员变量,可以被读取或设置部件属性。

部件事件是一种特殊的属性,它可以将方法(称之为事件句柄(event handlers))作为它的值。绑定(分配)一个方法到一个事件将会导致方法在事件被挂起处自动被调用。因此部件行为可能会被一种在部件开发过程中不可预见的方式修改。部件事件以 on 开头的命名方式定义。

部件可以绑定一个或者多个行为。一个行为(behavior) 就是一个对象,其方法可以被它绑定的部件通过收集功能的方式来实现 '继承(inherited)',而不是专有化继承(即普通的类继承)。简单的来说,就是一个部件可以以'多重继承'的方式实现多个行为的绑定。

2.7. 开发流程


用yii开发一个web程序的基本流程,如下所示:

1. 创建目录结构
2. 配置 application,即修改application配置文件
3. 每种类型的数据都创建一个 model 类来管理
4. 每种类型的用户请求都创建一个 controller 类。 依据实际的需求对用户请求进行分类。一般来说,如果一个model类需要用户访问,就应该对应一个controller类。
5. 实现 actions 和相应的 views。这是真正需要我们编写的工作。
6. 在controller类里配置需要的action filters 。
7. 如果需要主题功能,编写 themes。
8. 如果需要 internationalization国际化功能,编写翻译语句。
9. 使用 caching 技术缓存数据和页面。
10. 最后调整好程序和发布。

3. 测试考虑

Yii提供了自动化测试的方法,包括单元测试和功能测试,其中单元测试是验证一个单元的代码能够像预想的一样工作,对于Yii来说,就是验证类中的每个方法都可以正确的工作,输入不同的参数和数据,来验证类中每个方法能够输出符合预期的数据和行为。

功能测试是确定一个功能、特征(例如博客系统中的发表管理)可以像预想的一样工作。由于一个功能可能包含多个类,因此相比较单元测试,功能测试在更高的级别。

Yii的测试框架是建立在PHPUnit之上的。

3.1. 定义Fixtures

一套自动化的case往往会被执行多次。为了保证这个测试的过程是可重复的,我们需要在执行case的时候,让其初始状态是处于一个已知确定的状态。这个状态,我们叫做fixture,通俗的讲,就是在测试之前所需要的环境的所有初始状态。

建立一个数据库的fixture在web应用开发过程中可能是一个最耗时的部分。Yii框架引入了一个叫做CDbFixtureManager的应用组件来缓和这个问题,大体来说,在执行一个测试集合时它做了这样几件事情:
在所有case运行前,它把与本次运行case相关的所有数据表置于一个已知的状态。在一个单一的case运行前,它把一些特定的表复位到一个已知的状态。

在执行一个方法的过程中,它提供了对与这些fixture有关的数据的访问。

为了使用CDbFixtureManager这个类,一般会进行如下配置:

return array(
'components'=>array(
'fixture'=>array(
'class'=>'system.test.CDbFixtureManager',
),
),
);

对于这些我们想提供作为fixture的数据,一般会将其放在/protected/tests/fixtures目录下。这些数据文件按照php的形式组织在一起,统称为fixture file。每个fixture file会返回一个数组,该数组包含一些特定的表的某些行的预设值。并且文件名跟表名保持一致。

3.2. 单元测试

我们可以总结出如下在Yii中书写单元测试的原则:

● 单元测试是通过实现继承自CTestCase或CDbTestCase类的XyzTest类来实现,其中Xyz表示要测试的类名。CTestCase用于通用单元测试而CDbTestCase用于活动记录模型类的测试。由于PHPUnit_Framework_TestCase是这两个类的父类,因此我们可以使用继承自此类的所有方法。
● 单元测试类以文件XyzTest.php的形式保存。为了方便起见,一般单元测试的case会存储在protected/tests/unit下。
● 测试类主要是一个待测试的类的方法的集合,如果这个待测试的类叫做Abc,那么这个测试类一般会起名为testAbc。
● 一个测试类经常包含一系列的断言,(例如assertTrue, assertEquals),作为验证目标类行为的检验点。

以如何为active record模型类建立单测为例,我们来看看究竟如何写一个单测case。

假设我们想测试一个blog的评论模型类,首先要创建一个CommonTest.php并保存在/protected/tests/unit/CommentTest.php,代码如下:

class CommentTest extends CDbTestCase
{
public $fixtures=array(
'posts'=>'Post',
'comments'=>'Comment',
);

......

}


在上述类中,我们制定了fixture的成员变量是一个数组,该数组将会用户接下来的测试case中。

Fixture names允许我们在测试方法中以一种非常方便的方式来访问fixture data。最典型的用法如下:

$comments = $this->comments;

接下来,我们就可以书写textApprove方法来测试评论视图类中的approve 方法了。

3.3. 功能测试

● 像单元测试一样,功能测试时通过实现继承自CWebTestCase类的XyzTest类来实现,其中Xyz表示要测试的类名。由于 PHPUnit_Extensions_SeleniumTestCase是CwebTestCase的父类,因此可以采用继承自此类的所有方法。
● 功能测试类以文件XyzTest.php的形式保存。
● 测试类主要包含命名为testABC的测试方法的集合,其中Abc经常是要测试的功能的名字,例如testLogin
● 一个测试方法经常包含一系列的指令来发出命令以与被测试Web应用交互。它也包含一些断言状态来验证Web应用如预想一样响应。

3.4. 其他

3.4.1基于module的单元测试方法

有的yii版本不支持基于phpUnit 的单元的测试,那么我们也可以有其他的办法来做单元测试。具体的方法就是把test作为一个module。目录部署结构为:

webRoot/protected/modules/test/controller/ABC.php

在上述ABC.php里,我们可以创建继承自业务的类,并在这些类中构建对应的action,每个action都可以调用需要测试的函数。通过在浏览器上直接请求这些action,并打印出相关信息,即可实现对上述这些函数的测试。如想测试的action函数为actionAdd。

根据yii的url解析规则,上述Add方法的调用即为该url的请求http://*.*.*.*/index.php?r=test/ABC/Add 。 如果适当的加上返回结果的展现,那么就能很明确的知道该函数的返回值与预期的是否相符。

3.4.2Firebug辅助测试

Firebug也是当今web开发程序员的一个利器,同时也可以作为测试人员的一个参考。Firebug会记录各个请求的参数,post数据,响应数据。当功能出现问题时,测试人员可以根据事先设计的参数和预期的返回的数据来判定是view传递的参数错误,还是php根据逻辑所计算出的返回数据值不对。从而来定位错误的真正位置。

(全文完)
作者:mimical

本文首发于: 百度测试技术空间http://hi.baidu.com/baiduqa/blog/item/271d1a5313886a15367abecd.html
关注百度技术沙龙