Ⅰ、基本概念
一、入口文件
入口文件内容:一般格式如下:
$yii=dirname(__FILE__).'/../../framework/yii.php';//Yii框架位置
$config=dirname(__FILE__).'/protected/config/main.php';//当前应用程序的主配置文件位置
// 部署正式环境时,去掉下面这行
// defined('YII_DEBUG') or define('YII_DEBUG',true);//是否运行在调试模式下
require_once($yii);//包含Yii框架
Yii::createWebApplication($config)->run();//根据主配置文件建立应用实例,并运行。你可以在当前应用的任何位置通过Yii::app()来访问这个实例。
二、主配置文件
保存位置:你的应用/protected/config/main.php
文件内容:一般格式如下:
return array(
'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..', //当前应用根目录的绝对物理路径
'name'=>'Yii Blog Demo', //当前应用的名称
// 预载入log(记录)应用组件,这表示该应用组件无论它们是否被访问都要被创建。该应用的参数配置在下面以“components”为关键字的数组中设置。
'preload'=>array('log'), //log为组件ID
// 自动载入的模型和组件类
'import'=>array(
'application.models.*', //载入“application/models/”文件夹下的所有模型类
'application.components.*', //载入“application/components/”文件夹下的所有应用组件类
),
'defaultController'=>'post', //设置默认控制器类
// 当前应用的组件配置。更多可供配置的组件详见下面的“核心应用组件”
'components'=>array(
'user'=>array( //user(用户)组件配置,“user”为组件ID
// 可以使用基于cookie的认证
'allowAutoLogin'=>true, //允许自动登录
),
'cache'=>array( //缓存组件
'class'=>'CMemCache', //缓存组件类
'servers'=>array( //MemCache缓存服务器配置
array('host'=>'server1', 'port'=>11211, 'weight'=>60), //缓存服务器1
array('host'=>'server2', 'port'=>11211, 'weight'=>40), //缓存服务器2
),
),
'db'=>array( //db(数据库)组件配置,“db”为组件ID
'connectionString' => 'sqlite:protected/data/blog.db', //连接数据库的DSN字符串
'tablePrefix' => 'tbl_', //数据表前缀
),
// 如果要使用一个MySQL数据库,请取消下面的注释
/*
'db'=>array(
'connectionString' => 'mysql:host=localhost;dbname=blog', //连接mysql数据库
'emulatePrepare' => true,
'username' => 'root', //MySQL数据库用户名
'password' => '', //MySQL数据库用户密码
'charset' => 'utf8', //MySQL数据库编码
'tablePrefix' => 'tbl_', //MySQL数据库表前缀
),
*/
'errorHandler'=>array(
// 使用SiteController控制器类中的actionError方法显示错误
'errorAction'=>'site/error', //遇到错误时,运行的操作。控制器名和方法名均小写,并用斜线“/”隔开
),
//URL路由管理器
'urlManager'=>array(
'urlFormat'=>'path', //URL格式。 共支持两种格式:'path'格式(如:/path/to/EntryScript.php/name1/value1/name2 /value2...)和'get'格式(如: /path/to/EntryScript.php?name1=value1&name2=value2...)。当使用'path'格式时, 需要设置如下的规则:
'rules'=>array( //URL规则。语法:<参数名:正则表达式>
'post//'=>'post/view', //将post/12/helloword指向post/view?id=12&title=helloword
'posts/'=>'post/index', //将posts/hahahaha指向post/index?tag=hahahaha
'/'=>'/',
),
),
'log'=>array( //记录
'class'=>'CLogRouter', //处理记录信息的类
'routes'=>array(
array(
'class'=>'CFileLogRoute', //处理错误信息的类
'levels'=>'error, warning', //错误等级
),
// 如要将错误记录消息在网页上显示,取消下面的注释即可
/*
array(
'class'=>'CWebLogRoute',
),
*/
),
),
), //应用组件配置结束
// 使用Yii::app()->params['参数名']可以访问应用层的参数
'params'=>require(dirname(__FILE__).'/params.php'),
);
核心应用组件:
Yii 预定义了一系列核心应用组件,提供常见 Web 应用中所用的功能。例如, request 组件用于解析用户请求并提供例如 URL,cookie 等信息。通过配置这些核心组件的属性,我们可以几乎任意的修改Yii 的默认行为。
下面我们列出了由 CWebApplication 预定义的核心组件。
assetManager: CAssetManager - 管理私有资源文件的发布。
authManager: CAuthManager - 管理基于角色的访问控制 (RBAC).
cache: CCache - 提供数据缓存功能。注意,你必须指定实际的类(例如CMemCache, CDbCache)。否则,当你访问此组件时将返回 NULL。
clientScript: CClientScript - 管理客户端脚本 (java scripts 和 CSS).
coreMessages: CPhpMessageSource - 提供 Yii 框架用到的核心信息的翻译。
db: CDbConnection - 提供数据库连接。注意,使用此组件你必须配置其 connectionString 属性。
errorHandler: CErrorHandler - 处理未捕获的 PHP 错误和异常。
format: CFormatter - 格式化数值显示。此功能从版本 1.1.0 起开始提供。
messages: CPhpMessageSource - 提供Yii应用中使用的信息翻译。
request: CHttpRequest - 提供关于用户请求的信息。
securityManager: CSecurityManager - 提供安全相关的服务,例如散列,加密。
session: CHttpSession - 提供session相关的功能。
statePersister: CStatePersister - 提供全局状态持久方法。
urlManager: CUrlManager - 提供 URL 解析和创建相关功能
user: CWebUser - 提供当前用户的识别信息。
themeManager: CThemeManager - 管理主题。
要访问一个应用组件,使用 Yii::app()->组件的ID
三、控制器(Controller)
控 制器 是 CController 类的子类的实例。它在当用户请求时由应用创建。当一个控制器运行时,它执行所请求的动作(控制器类方法),动作通常会引入所必要的模型并渲染相应的视图。 动作,就是一个名字以 action 开头的控制器类方法(action+大写首字母的动作名)。
控制器类文件保存位置protected/controllers/
控制器和动作以 ID 识别。
控制器ID 是一种 '父目录/子目录/控制器名' 的格式,对应相应的控制器类文件 protected/controllers/父目录/子目录/大写首字母的控制器名Controller.php;
动作ID 是除去 action 前缀的动作方法名。
1、路由
用户以路由的形式请求特定的控制器和动作。路由是由控制器 ID 和动作 ID 连接起来的,两者以斜线分割。
例如,路由 post/edit 代表 PostController 及其 edit 动作。默认情况下,URL http://hostname/index.php?r=post/edit 即请求此控制器和动作。
注 意: 默认情况下,路由是大小写敏感的。可以通过设置应用配置中的 CUrlManager::caseSensitive 为 false 使路由对大小写不敏感。当在大小写不敏感模式中时, 要确保你遵循了相应的规则约定,即:包含控制器类文件的目录名小写,且 控制器映射 和 动作映射 中使用的键为小写。
路由的格式:控制器ID/动作ID 或 模块ID/控制器ID/动作ID(如果是嵌套模块,模块ID 就是 父模块ID/子模块ID)
2、控制器实例化
应用将使用如下规则确定控制器的类以及类文件的位置:
1、如果指定了 CWebApplication::catchAllRequest , 控制器将基于此属性创建,而用户指定的控制器 ID 将被忽略。这通常用于将应用设置为维护状态并显示一个静态提示页面。
2、如果在 CWebApplication::controllerMap 中找到了 ID, 相应的控制器配置将被用于创建控制器实例。
3、 如果 ID 为 'path/to/xyz'的格式,控制器类的名字将判断为 XyzController,相应的类文件则为 protected/controllers/path/to/XyzController.php。如果类文件不存在,将触发一个 404 CHttpException 异常。
在使用了模块的情况下,应用将检查此 ID 是否代表一个模块中的控制器。如果是的话,模块实例将被首先创建,然后创建模块中的控制器实例。
3、动作(action)
动作 就是被定义为一个以 action 单词作为前缀命名的方法。而更高级的方式是定义一个动作类并让控制器在收到请求时将其实例化。这使得动作可以被复用,提高了可复用度。
1、定义一个动作类,基本格式如下:
class UpdateAction extends CAction
{
public function run()
{
// place the action logic here
}
}
2、使用动作类:为了让控制器注意到这个动作,我们要用如下方式覆盖控制器类的actions() 方法:
class PostController extends CController
{
public function actions()
{
return array(
'edit'=>'application.controllers.post.UpdateAction', //使用“应用程序文件夹/controllers/post/UpdateAction.php”文件中的类来处理edit动作
);
}
}
如上所示,我们使用了路径别名“application.controllers.post.UpdateAction”指定动作类文件为“protected/controllers/post/UpdateAction.php”。
通过编写基于类的动作,我们可以将应用组织为模块的风格。例如,如下目录结构可用于组织控制器相关代码:
protected/
controllers/
PostController.php
UserController.php
post/
CreateAction.php
ReadAction.php
UpdateAction.php
user/
CreateAction.php
ListAction.php
ProfileAction.php
UpdateAction.php
4、过滤器(filter)
过滤器是一段代码,可被配置在控制器动作执行之前或之后执行。
一个动作可以有多个过滤器。如有多个过滤器,则按照它们出现在过滤器列表中的顺序依次执行。过滤器可以阻止动作及后面其他过滤器的执行。
过滤器可以定义为一个控制器类的方法。过滤器方法名必须以 filter 开头。例如,现有的 filterAccessControl 方法定义了一个名为 accessControl 的过滤器。过滤器方法必须为如下结构:
public function filterAccessControl($filterChain)
{
// 调用 $filterChain->run() 以继续后续过滤器与动作的执行。
}
$filterChain (过滤器链)是一个 CFilterChain 的实例,代表与所请求动作相关的过滤器列表。在过滤器方法中,我们可以调用 $filterChain->run() 以继续执行后续过滤器和动作。
如 动作 一样,过滤器也可以是一个对象,它是 CFilter 或其子类的实例。如下代码定义了一个新的过滤器类:
class PerformanceFilter extends CFilter
{
protected function preFilter($filterChain)
{
// 动作被执行之前应用的逻辑
return true; // 如果动作不应被执行,此处返回 false
}
protected function postFilter($filterChain)
{
// 动作执行之后应用的逻辑
}
}
要对动作应用过滤器,我们需要覆盖 CController::filters() 方法。此方法应返回一个过滤器配置数组。例如:
class PostController extends CController
{
......
public function filters()
{
return array(
'postOnly + edit, create', //将postOnly过滤器应用于edit和create动作(这是基于方法的过滤器)
array( //使用了数组来配置过滤器
'application.filters.PerformanceFilter - edit, create', //将application.filters.PerformanceFilter过滤器应用于除了edit和create之外的所有动作(这是基于对 象的过滤器)
'unit'=>'second', //初始化过滤器对象中的unit属性值为second
),
);
}
}
上 述代码指定了两个过滤器: postOnly 和 PerformanceFilter。 postOnly 过滤器是基于方法的(相应的过滤器方法已在 CController 中定义);而 performanceFilter 过滤器是基于对象的。路径别名 application.filters.PerformanceFilter 指定过滤器类文件是 protected/filters/PerformanceFilter。我们使用一个数组配置 PerformanceFilter ,这样它就可被用于初始化过滤器对象的属性值。此处 PerformanceFilter 的 unit 属性值将被初始为 second。
使 用加减号,我们可指定哪些动作应该或不应该应用过滤器。上述代码中, postOnly 应只被应用于 edit 和 create 动作,而 PerformanceFilter 应被应用于 除了 edit 和 create 之外的动作。如果过滤器配置中没有使用加减号,则此过滤器将被应用于所有动作。
五、模型(Model)
模型是 CModel 或其子类的实例。模型用于保持数据以及与其相关的业务逻辑。
模型是单独的数据对象。它可以是数据表中的一行,或者一个用户输入的表单。
数据对象的每个字段对应模型中的一个属性。每个属性有一个标签(label),并且可以通过一系列规则进行验证。
Yii 实现了两种类型的模型:表单模型和 Active Record。二者均继承于相同的基类 CModel。
表单模型是 CFormModel 的实例。表单模型用于保持从用户的输入获取的数据。这些数据经常被获取,使用,然后丢弃。例如,在一个登录页面中,我们可以使用表单模型用于表示由最终用户提供的用户名和密码信息。
Active Record (AR) 是一种用于通过面向对象的风格抽象化数据库访问的设计模式。每个 AR 对象是一个 CActiveRecord 或其子类的实例。代表数据表中的一行。行中的字段对应 AR 对象中的属性。
六、视图
视图是一个包含了主要的用户交互元素的PHP脚本.
视 图有一个名字,当渲染(render)时,名字会被用于识别视图脚本文件。视图的名称与其视图脚本名称是一样的。例如:视图 edit 的名称出自一个名为 edit.php 的脚本文件。要渲染时,需通过传递视图的名称调用 CController::render()。这个方法将在“protected/views/控制器ID”目录下寻找对应的视图文件。
在视图脚本内部,我们可以通过 $this 来访问控制器实例。我们可以在视图里以“$this->属性名”的方式获取控制器的任何属性。
我们也可以用以下 推送 的方式传递数据到视图里:
$this->render('edit', array(
'var1'=>$value1,
'var2'=>$value2,
));
在以上的方式中, render() 方法将提取数组的第二个参数到变量里。其产生的结果是,在视图脚本里,我们可以直接访问变量 $var1 和 $var2。
1、布局
布局是一种用来修饰视图的特殊的视图文件。它通常包含了用户界面中通用的一部分视图。例如:布局可以包含header和footer的部分,然后把内容嵌入其间。
......header here......
......footer here......
其中的 $content 则储存了内容视图的渲染结果。
当 使用render()时,布局被隐式应用。视图脚本 protected/views/layouts/main.php 是默认的布局文件。这可以通过改变 CWebApplication::layout 进行自定义。要渲染一个不带布局的视图,则需调用 renderPartial() 。
2、小物件
小物件是 CWidget 或其子类的实例。它是一个主要用于表现数据的组件。小物件通常内嵌于一个视图来产生一些复杂而独立的用户界面。例如,一个日历小物件可用于渲染一个复杂的日历界面。小物件使用户界面更加可复用。
我们可以按如下视图脚本来使用一个小物件:
beginWidget('小物件类的路径别名'[,'包含属性初始化值的数组']); ?>
...可能会由小物件获取的内容主体...
endWidget(); ?>
或者
widget('小物件类的路径别名'[,'包含属性初始化值的数组']); ?>
后者用于不需要任何 body 内容的组件。
小物件可通过配置来定制它的表现。这是通过调用 CBaseController::beginWidget 或 CBaseController::widget 设置其初始化属性值来完成的。
我们通过传递一个携带这些属性初始化值的数组来实现,该数组的键是属性的名称,而数组的值则是小物件属性所对应的值。如下所示 :
$this->widget('CMaskedTextField',array(
'mask'=>'99/99/9999'
));
?>
继承 CWidget 并覆盖其init()和run()方法,可以定义一个新的小物件:
class MyWidget extends CWidget
{
public function init()
{
// 此方法会被 CController::beginWidget() 调用
}
public function run()
{
// 此方法会被 CController::endWidget() 调用
}
}
小物件可以像一个控制器一样拥有它自己的视图。
默 认情况下,小物件的视图文件位于包含了小物件类文件目录的 views 子目录之下(protected/components/views)。这些视图可以通过调用CWidget::render()渲染,这一点和控制器很 相似。唯一不同的是,小物件的视图没有布局文件支持。另外,小物件视图中的$this指向小物件实例而不是控制器实例。
3、系统视图
系统视图的渲染通常用于展示 Yii 的错误和日志信息。
系统视图的命名遵从了一些规则。比如像“errorXXX”这样的名称就是用于渲染展示错误号XXX的 CHttpException 的视图。例如,如果 CHttpException 抛出一个404错误,那么 error404 就会被显示。
在 framework/views 下, Yii 提供了一系列默认的系统视图. 他们可以通过在 protected/views/system 下创建同名视图文件进行自定义。
七、组件
Yii 应用建立于组件之上。组件是 CComponent 或其子类的实例。使用组件主要涉及访问它的属性以及触发或处理它的时间。基类 CComponent 指定了如何定义属性和事件。
1、组件属性
组件的属性就像对象的公共成员变量。它是可读写的。
要定义一个组件属性,我们只需在组件类中定义一个公共成员变量即可。
更灵活的方式是定义其 getter 和 setter 方法,例如:
public function getTextWidth() // 获取 textWidth 属性
{
return $this->_textWidth;
}
public function setTextWidth($value) // 设置 TextWidth 属性
{
$this->_textWidth=$value;
}
上 述代码定义了一个可写的属性名为 textWidth(名字是大小写不敏感的)。当读取属性时,getTextWidth() 就会被调用,其返回值则成为属性值;相似的,当写入属性时,setTextWidth() 被调用。如果 setter 方法没有定义,则属性将是只读的,如果对其写入则会抛出一个异常。使用 getter 和 setter 方法定义一个属性有一个好处:即当读取或写入属性时,可以执行额外的逻辑(例如,执行验证,触发事件)。
注意: 通过 getter/setter 定义的属性和类成员变量之间有一个细微的差异:属性的名字是大小写不敏感的, 而 类成员变量 是大小写敏感的。
2、组件事件
组件事件是一些特殊的属性,它们使用一些称作 事件句柄(event handlers)的方法作为其值。分配一个方法到一个事件将会引起方法在事件被唤起处自动被调用。因此,一个组件的行为可能会被一种在部件开发过程中不可预见的方式修改。
组件事件以 on 开头的命名方式定义。和属性通过 getter/setter 方法来定义的命名方式一样,事件的名称是大小写不敏感的。以下代码定义了一个 onClicked 事件:
public function onClicked($event)
{
$this->raiseEvent('onClicked', $event);
}
这里作为事件参数的 $event 是 CEvent 或其子类的实例。
我们可以分配一个方法到此事件,如下所示:
$component->onClicked=$callback;
这里的 $callback 指向了一个有效的 PHP 回调。它可以是一个全局函数也可以是类中的一个方法。如果是后者,它必须以一个数组的方式提供: array($object,'methodName')。
事件句柄的结构如下:
function 方法名($event)
{
......
}
这里的 $event 即描述事件的参数(它来源于 raiseEvent() 调用)。$event 参数是 CEvent 或其子类的实例。至少,它包含了关于谁触发了此事件的信息。
事件句柄也可以是一个PHP 5.3以后支持的匿名函数。例如:
$component->onClicked=function($event) {
......
}
如果我们现在调用 onClicked(),onClicked 事件将被触发(在 onClicked() 中),附属的事件句柄将被自动调用。
一个事件可以绑定多个句柄。当事件触发时,这些句柄将被按照它们绑定到事件时的顺序依次执行。如果句柄决定组织后续句柄被执行,它会设置 $event->handled 为 true。
3、组件行为
组件已添加了对 mixin 的支持,并可以绑定一个或多个行为。 行为是一个对象,其方法可以被它绑定的部件通过收集功能的方式来实现继承(inherited),而不是专有化继承(即普通的类继承)。一个部件可以以'多重继承'的方式实现多个行为的绑定。
行为类必须实现 IBehavior 接口。 大多数行为可以继承自 CBehavior 。如果一个行为需要绑定到一个模型, 它也可以从专为模型实现绑定特性的 CModelBehavior 或 CActiveRecordBehavior 继承。
要使用一个行为,它必须首先通过调用此行为的 attach() 方法绑定到一个组件。然后我们就可以通过组件调用此行为方法:
// $name 在组件中实现了对行为的唯一识别
$component->attachBehavior($name,$behavior);
// test() 是行为中的方法。
$component->test();
已绑定的行为可以像一个组件中的普通属性一样访问。例如,如果一个名为 tree 的行为绑定到了一个组件,我们就可以通过如下代码获得指向此行为的引用。
$behavior=$component->tree;
// 等于下行代码:
// $behavior=$component->asa('tree');
行为是可以被临时禁止的,此时它的方法就会在组件中失效。例如:
$component->disableBehavior($name);
// 下面的代码将抛出一个异常
$component->test();
$component->enableBehavior($name);
// 现在就可以使用了
$component->test();
两个同名行为绑定到同一个组件下是有可能的。在这种情况下,先绑定的行为则拥有优先权。
当和 events, 一起使用时,行为会更加强大。 当行为被绑定到组件时,行为里的一些方法就可以绑定到组件的一些事件上了。这样一来,行为就有机观察或者改变组件的常规执行流程。
一 个行为的属性也可以通过绑定到的组件来访问。这些属性包含公共成员变量以及通过 getters 和/或 setters 方式设置的属性。例如, 若一个行为有一个 xyz 的属性,此行为被绑定到组件 $a,然后我们可以使用表达式 $a->xyz 访问此行为的属性。
八、模块
模块是一个独立的软件单元,它包含 模型, 视图, 控制器 和其他支持的组件。在许多方面上,模块看起来像一个 应用。主要的区别就是模块不能单独部署,它必须存在于一个应用里。用户可以像他们访问普通应用的控制器那样访问模块中的控制器。
模块在一些场景里很有用。对大型应用来说,我们可能需要把它划分为几个模块,每个模块可以单独维护和部署。一些通用的功能,例如用户管理,评论管理,可以以模块的形式开发,这样他们就可以容易地在以后的项目中被复用。
1、创建模块
模块组织在一个目录中,目录名即为模块的唯一ID。模块目录的结构跟 应用基础目录 很相似。下面列出了一个 fourm 的模块的典型的目录结构:
forum/ 模块文件夹
ForumModule.php 模块类文件
components/ 包含可复用的用户组件
views/ 包含小物件的视图文件
controllers/ 包含控制器类文件
DefaultController.php 默认的控制器类文件
extensions/ 包含第三方扩展
models/ 包含模型类文件
views/ 包含控制器视图和布局文件
layouts/ 包含布局文件
default/ 包含 DefaultController 的视图文件
index.php 首页视图文件
模 块必须有一个继承自 CWebModule 的模块类。类的名字通过表达式 ucfirst($id).'Module' 确定, 其中的 $id 代表模块的 ID (或者说模块的目录名字)。模块类是存储模块代码间可共享信息的中心位置。例如,我们可以使用 CWebModule::params 存储模块参数,使用 CWebModule::components 分享模块级的 应用组件。
2、使用模块
要使用模块,首先将模块目录放在 应用基础目录 的modules文件夹中。然后在应用的modules属性中声明模块ID。例如,为了使用上面的forum模块,我们可以使用如下应用配置:
return array(
......
'modules'=>array('forum',...),
......
);
模块也可以在配置时带有初始属性值。做法和配置 应用组件 很类似。例如, forum 模块可以在其模块类中有一个名为 postPerPage 的属性,它可以在 应用配置 中配置如下:
return array(
......
'modules'=>array(
'forum'=>array(
'postPerPage'=>20,
),
),
......
);
模块的实例可通过当前活动控制器的 module 属性访问。在模块实例中,我们可以访问在模块级中共享的信息。例如,为访问上面的 postPerPage 信息,我们可使用如下表达式:
$postPerPage=Yii::app()->controller->module->postPerPage;
// 如如$this引用的是控制器实例,则可以使用下行语句
// $postPerPage=$this->module->postPerPage;
模 块中的控制器动作可以通过路由“模块ID/控制器ID/动作ID”或“模块ID/存放控制器类文件的子目录名/控制器ID/动作ID”访问。例如,假设上 面的 forum 模块有一个名为 PostController 的控制器,我们就可以通过路由 forum/post/create 访问此控制器中的 create 动作。此路由对应的 URL 即 http://www.example.com/index.php?r=forum/post/create。
3、嵌套的模块
模块可以无限级嵌套。这就是说,一个模块可以包含另一个模块,而这另一个模块又可以包含其他模块。我们称前者为 父模块 ,后者为 子模块。子模块必须定义在其父模块的 modules 属性中,就像我们前面在应用配置中定义模块一样。
要访问子模块中的控制器动作,我们应使用路由 父模块ID/子模块ID/控制器ID/动作ID。
九、路径别名
Yii 中广泛的使用了路径别名。路径别名关联于一个目录或文件的路径。它以点号语法指定,类似于广泛使用的名字空间(namespace)格式:
RootAlias.path.to.target
其中的 RootAlias 是某个现存目录的别名,通过调用 YiiBase::setPathOfAlias(), 我们可以定义新的路径别名。为方便起见,Yii 预定义了以下几个根别名:
system: 表示 Yii 框架目录;
zii: 表示 Zii 库 目录;
application: 表示应用的 基础目录;
webroot: 表示 入口脚本 文件所在的目录。
ext: 表示包含了所有第三方 扩展 的目录。
额外的,如果应用使用了 模块, (Yii) 也为每个模块ID定义了根别名,指向相应模块的跟目录。
通过使用 YiiBase::getPathOfAlias(), 别名可以被翻译为其相应的路径。
使用别名可以很方便的导入类的定义。例如,如果我们想包含 CController 类的定义,我们可以调用如下代码
Yii::import('system.web.CController');
import方法跟 include 和 require 不同,它更加高效。导入(import)的类定义并不会真正被包含进来,直到它第一次被引用。多次导入同样的名字空间也会比 include_once 和 require_once 快得多。
我们还可以使用如下语法导入整个目录,这样此目录下的类文件就会在需要时被自动包含。
Yii::import('system.web.*');
除 import 外, 别名还在其他许多地方指向类。例如,路径别名可以传递给 Yii::createComponent() 以创建相应类的实例。即使类文件在之前从未被包含。
不要将路径别名和名字空间混淆了,名字空间是指对一些类名的一个逻辑组合,这样它们就可以相互区分开,即使有相同的名字。而路径别名是用于指向一个类文件或目录。路径别名与名字空间并不冲突。
十、开发规范
下面我们讲解 Yii 编程中推荐的开发规范。为简单起见,我们假设 WebRoot 是 Yii 应用安装的目录。
1、URL
默认情况下,Yii 识别如下格式的 URL:
http://hostname/index.php?r=ControllerID/ActionID
r 变量意为 路由(route) ,它可以被Yii解析为 控制器和动作。如果 ActionID 被省略,控制器将使用默认的动作(在CController::defaultAction中定义);如果 ControllerID 也被省略(或者 r 变量不存在),应用将使用默认的控制器(在CWebApplication::defaultController中定义)。
通过 CUrlManager 的帮助,可以创建更加可识别,更加 SEO 友好的 URL,例如 http://hostname/ControllerID/ActionID.html。
2、代码
Yii 推荐命名变量、函数和类时使用驼峰风格,即每个单词的首字母大写并连在一起,中间无空格。变量名和函数名应该使它们的第一个单词全部小写,以使其区别于类名。对私有类成员变量来说,我们推荐以下划线作为其名字前缀(例如: $_actionList)。
一 个针对控制器类名的特殊规则是它们必须以单词 Controller 结尾。那么控制器ID就是类名的首字母小写并去掉单词Controller。例如,PageController类的ID就是 page。这个规则使应用更加安全。它还使控制器相关的URL更加简单(例如 /index.php?r=page/index 而不是 /index.php?r=PageController/index)。
3、配置
配置是一个键值对数组。每个键代表了所配置的对象中的属性名,每个值则为相应属性的初始值。
类中任何可写的属性都可以被配置。如果没有配置,属性将使用它们的默认值。当配置一个属性时,最好阅读相应文档以保证初始值正确。
4、文件
命名和使用文件的规范取决于它们的类型。
类文件应以它们包含的公有类命名。例如,CController 类位于 CController.php 文件中。公有类是可以被任何其他类使用的类。每个类文件应包含最多一个公有类。私有类(只能被一个公有类使用的类)可以放在使用此类的公有类所在的文件中。
视图文件应以视图的名字命名。例如, index 视图位于 index.php 文件中。视图文件是一个PHP脚本文件,它包含了用于呈现内容的HTML和PHP代码。
配置文件可以任意命名。配置文件是一个PHP脚本,它的主要目的是返回一个体现配置的关联数组。
5、目录
Yii 假定了一系列默认的目录用于不同的场合。如果需要,每个目录都可以自定义。
WebRoot/protected: 这是 应用基础目录,是放置所有安全敏感的PHP脚本和数据文件的地方。Yii 有一个默认的 application 别名指向此目录。此目录及目录中的文件应该保护起来防止Web用户访问。它可以通过 CWebApplication::basePath 自定义。
WebRoot/protected/runtime: 此目录放置应用在运行时产生的私有临时文件。此目录必须对 Web 服务器进程可写。它可以通过 CApplication::runtimePath自定义。
WebRoot/protected/extensions: 此目录放置所有第三方扩展。它可以通过 CApplication::extensionPath 自定义。
WebRoot/protected/modules: 此目录放置所有的应用 模块,每个模块使用一个子目录。
WebRoot/protected/controllers: 此目录放置所有控制器类文件。它可以通过 CWebApplication::controllerPath 自定义。
WebRoot/protected/views: 此目录放置所有试图文件,包含控制器视图,布局视图和系统视图。它可以通过 CWebApplication::viewPath 自定义。
WebRoot/protected/views/ControllerID: 此目录放置单个控制器类中使用的视图文件。此处的 ControllerID 是指控制器的ID 。它可以通过 CController::viewPath 自定义。
WebRoot/protected/views/layouts: 此目录放置所有布局视图文件。它可以通过 CWebApplication::layoutPath 自定义。
WebRoot/protected/views/system: 此目录放置所有系统视图文件。系统视图文件是用于显示异常和错误的模板。它可以通过CWebApplication::systemViewPath自定义。
WebRoot/assets: 此目录放置公共资源文件。资源文件是可以被发布的,可由Web用户访问的私有文件。此目录必须对 Web 服务器进程可写。它可以通过 CAssetManager::basePath 自定义
WebRoot/themes: 此目录放置应用使用的不同的主题。每个子目录即一个主题,主题的名字即目录的名字。它可以通过 CThemeManager::basePath 自定义。
6、数据库
多数Web 应用是由数据库驱动的。我们推荐在对表和列命名时使用如下命名规范。注意,这些规范并不是 Yii 所必须的。
㈠数据库表名和列名都使用小写命名。
㈡名字中的单词应使用下划线分割 (例如 product_order)。
㈢对于表名,你既可以使用单数也可以使用复数。但不要 同时使用两者。为简单起见,我们推荐使用单数名字。
㈣表名可以使用一个通用前缀,例如 tbl_ 。这样当应用所使用的表和另一个应用说使用的表共存于同一个数据库中时就特别有用。这两个应用的表可以通过使用不同的表前缀很容易地区别开。
Ⅱ、使用表单
在 Yii 中处理表单时,通常需要以下步骤:
1. 创建用于表现所要收集数据字段的模型类。
2. 创建一个控制器动作,响应表单提交。
3. 在视图脚本中创建与控制器动作相关的表单。
一、创建模型
在编写表单所需的 HTML 代码之前,我们应该先确定来自最终用户输入的数据的类型,以及这些数据应符合什么样的规则。模型类可用于记录这些信息。正如模型章节所定义的,模型是保存用户输入和验证这些输入的中心位置。
取 决于使用用户所输入数据的方式,我们可以创建两种类型的模型。如果用户输入被收集、使用然后丢弃,我们应该创建一个表单模型; 如果用户的输入被收集后要保存到数据库,我们应使用一个Active Record。两种类型的模型共享同样的基类 CModel ,它定义了表单所需的通用接口。
1、定义模型类
例如创建为一个表单模型:
class LoginForm extends CFormModel
{
public $username;
public $password;
public $rememberMe=false;
}
LoginForm 中定义了三个属性: $username, $password 和 $rememberMe。他们用于保存用户输入的用户名和密码,还有用户是否想记住他的登录的选项。由于 $rememberMe 有一个默认的值 false,相应的选项在初始化显示在登录表单中时将是未勾选状态。
我们将这些成员变量称为特性(attributes)而不是属性(properties),以区别于普通的属性(properties)。特性(attribute)是一个主要用于存储来自用户输入或数据库数据的属性(propertiy)。
2、声明验证规则
一旦用户提交了他的输入,模型被填充,我们就需要在使用前确保用户的输入是有效的。这是通过将用户的输入和一系列规则执行验证实现的。我们在 rules() 方法中指定这些验证规则,此方法应返回一个规则配置数组。
class LoginForm extends CFormModel
{
public $username;
public $password;
public $rememberMe=false;
private $_identity;
public function rules()
{
return array(
array('username, password', 'required'), //username 和 password 为必填项
array('rememberMe', 'boolean'), //rememberMe 应该是一个布尔值
array('password', 'authenticate'), //password 应被验证(authenticated)
);
}
public function authenticate($attribute,$params)
{
$this->_identity=new UserIdentity($this->username,$this->password);
if(!$this->_identity->authenticate())
$this->addError('password','错误的用户名或密码。');
}
}
rules() 返回的每个规则必须是以下格式:
array('AttributeList', 'Validator', 'on'=>'ScenarioList', ...附加选项)
其中:
AttributeList(特性列表)是需要通过此规则验证的特性列表字符串,每个特性名字由逗号分隔;
Validator(验证器) 指定要执行验证的种类;
on 参数是可选的,它指定此规则应被应用到的场景列表;
附加选项 是一个名值对数组,用于初始化相应验证器的属性值。
有三种方式可在验证规则中指定 Validator:
第一, Validator 可以是模型类中一个方法的名字,就像上面示例中的 authenticate 。验证方法必须是下面的结构:
/**
* @param string 所要验证的特性的名字
* @param array 验证规则中指定的选项
*/
public function 验证器名称($attribute,$params) { ... }
第二,Validator可以是一个验证器类的名字,当此规则被应用时,一个验证器类的实例将被创建以执行实际验证。规则中的附加选项用于初始化实例的属性值。验证器类必须继承自 CValidator。
第三,Validator 可以是一个预定义的验证器类的别名。在上面的例子中,required 名字是 CRequiredValidator 的别名,它用于确保所验证的特性值不为空。下面是预定义的验证器别名的完整列表:
boolean: CBooleanValidator 的别名,确保特性有一个 CBooleanValidator::trueva lue 或 CBooleanValidator::falseva lue 值。
captcha: CCaptchaValidator 的别名,确保特性值等于 CAPTCHA 中显示的验证码。
compare: CCompareva lidator 的别名,确保特性等于另一个特性或常量。
email: CEmailValidator 的别名,确保特性是一个有效的Email地址。
default: CDefaultValueva lidator 的别名,指定特性的默认值。
exist: CExistValidator 的别名,确保特性值可以在指定表的列中可以找到。
file: CFileva lidator 的别名,确保特性含有一个上传文件的名字。
filter: CFilterValidator 的别名,通过一个过滤器改变此特性。
in: CRangeva lidator 的别名,确保数据在一个预先指定的值的范围之内。
length: CStringValidator 的别名,确保数据的长度在一个指定的范围之内。
match: CRegularExpressionValidator 的别名,确保数据可以匹配一个正则表达式。
numerical: CNumberValidator 的别名,确保数据是一个有效的数字。
required: CRequiredValidator 的别名,确保特性不为空。
type: CTypeva lidator 的别名,确保特性是指定的数据类型。
unique: CUniqueva lidator 的别名,确保数据在数据表的列中是唯一的。
url: CUrlValidator 的别名,确保数据是一个有效的 URL。
下面我们列出了几个只用这些预定义验证器的示例:
// 用户名为必填项
array('username', 'required'),
// 用户名必须在 3 到 12 个字符之间
array('username', 'length', 'min'=>3, 'max'=>12),
// 在注册场景中,密码password必须和password2一致。
array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'),
// 在登录场景中,密码必须接受验证。
array('password', 'authenticate', 'on'=>'login'),
3、安全的特性赋值
在一个类的实例被创建后,我们通常需要用最终用户提交的数据填充它的特性。这可以通过如下块赋值(massive assignment)方式轻松实现:
$model=new LoginForm;
if(isset($_POST['LoginForm']))
$model->attributes=$_POST['LoginForm'];
最后的表达式被称作 块赋值(massive assignment) ,它将 $_POST['LoginForm'] 中的每一项复制到相应的模型特性中。这相当于如下赋值方法:
foreach($_POST['LoginForm'] as $name=>$value)
{
if($name 是一个安全的特性)
$model->$name=$value;
}
检测特性的安全非常重要,例如,如果我们以为一个表的主键是安全的而暴露了它,那么攻击者可能就获得了一个修改记录的主键的机会,从而篡改未授权给他的内容。
特性如果出现在相应场景的一个验证规则中,即被认为是安全的。例如:
array('username, password', 'required', 'on'=>'login, register'),
array('email', 'required', 'on'=>'register'),
如 上所示, username 和 password 特性在 login 场景中是必填项。而 username, password 和 email 特性在 register 场景中是必填项。于是,如果我们在 login 场景中执行块赋值,就只有 username 和 password 会被块赋值。因为只有它们出现在 login 的验证规则中。另一方面,如果场景是 register ,这三个特性就都可以被块赋值。
// 在登录场景中
$model=new User('login');
if(isset($_POST['User']))
$model->attributes=$_POST['User'];
// 在注册场景中
$model=new User('register');
if(isset($_POST['User']))
$model->attributes=$_POST['User'];
那么为什么我们使用这样一种策略来检测特性是否安全呢?背后的基本原理就是:如果一个特性已经有了一个或多个可检测有效性的验证规则,那我们还担心什么呢?
请记住,验证规则是用于检查用户输入的数据,而不是检查我们在代码中生成的数据(例如时间戳,自动产生的主键)。因此,不要为那些不接受最终用户输入的特性添加验证规则。
有时候,我们想声明一个特性是安全的,即使我们没有为它指定任何规则。例如,一篇文章的内容可以接受用户的任何输入。我们可以使用特殊的 safe 规则实现此目的:
array('content', 'safe')
还有一个用于声明一个属性为不安全的 unsafe 规则:
array('permission', 'unsafe')
unsafe 规则并不常用,它是我们之前定义的安全特性的一个例外。
4、触发验证
一 旦模型被用户提交的数据填充,我们就可以调用 CModel::validate() 触发数据验证进程。此方法返回一个指示验证是否成功的值。对 CActiveRecord 模型来说,验证也可以在我们调用其 CActiveRecord::save() 方法时自动触发。
我们可以通过设置scenario属性来设置场景属性,这样,相应场景的验证规则就会被应用。
验 证是基于场景执行的。 scenario属性指定了模型当前用于的场景和当前使用的验证规则集。例如,在 login 场景中,我们只想验证用户模型中的 username 和 password 输入;而在 register 场景中,我们需要验证更多的输入,例如 email, address, 等。下面的例子演示了如何在 register 场景中执行验证:
// 在注册场景中创建一个 User 模型。等价于:
// $model=new User;
// $model->scenario='register';
$model=new User('register'); //给模型类添加参数,该参数就是要触发的验证场景
// 将输入的值填充到模型
$model->attributes=$_POST['User'];
// 执行验证
if($model->validate()) // 如果输入有效
...
else
...
规则关联的场景可以通过规则中的 on 选项指定。如果 on 选项未设置,则此规则会应用于所有场景。例如:
public function rules()
{
return array(
array('username, password', 'required'),
array('password_repeat', 'required', 'on'=>'register'),
array('password', 'compare', 'on'=>'register'),
);
}
第一个规则将应用于所有场景,而第二个将只会应用于 register 场景。
5、提取验证错误
验 证完成后,任何可能产生的错误将被存储在模型对象中。我们可以通过调用 CModel::getErrors() 和CModel::getError() 提取这些错误信息。这两个方法的不同点在于第一个方法将返回 所有 模型特性的错误信息,而第二个将只返回 第一个 错误信息。
6、特性标签
当设计表单时,我们通常需要为每个表单域显示一个标签。标签告诉用户他应该在此表单域中填写什么样的信息。虽然我们可以在视图中硬编码一个标签,但如果我们在相应的模型中指定(标签),则会更加灵活方便。
默认情况下 CModel 将简单的返回特性的名字作为其标签。这可以通过覆盖 attributeLabels() 方法自定义。正如在接下来的小节中我们将看到的,在模型中指定标签会使我们能够更快的创建出更强大的表单。
二、创建动作
有了模型,我们就可以开始编写用于操作此模型的逻辑了。我们将此逻辑放在一个控制器的动作中。对登录表单的例子来讲,相应的代码就是:
public function actionLogin()
{
$model=new LoginForm;
if(isset($_POST['LoginForm']))
{
// 收集用户输入的数据
$model->attributes=$_POST['LoginForm'];
// 验证用户输入,并在判断输入正确后重定向到前一页
if($model->validate())
$this->redirect(Yii::app()->user->returnUrl); //重定向到之前需要身份验证的页面URL
}
// 显示登录表单
$this->render('login',array('model'=>$model));
}
如 上所示,我们首先创建了一个 LoginForm 模型示例;如果请求是一个 POST 请求(意味着这个登录表单被提交了),我们则使用提交的数据 $_POST['LoginForm'] 填充 $model ;然后我们验证此输入,如果验证成功,重定向用户浏览器到之前需要身份验证的页面。如果验证失败,或者此动作被初次访问,我们则渲染 login 视图,此视图的内容我们在下一节中讲解。
提示: 在 login 动作中,我们使用 Yii::app()->user->returnUrl 获取之前需要身份验证的页面URL。 组件 Yii::app()->user 是一种 CWebUser (或其子类) ,它表示用户会话信息(例如 用户名,状态)。
让我们特别留意一下 login 动作中出现的下面的 PHP 语句:
$model->attributes=$_POST['LoginForm'];
正 如我们在 安全的特性赋值 中所讲的,这行代码使用用户提交的数据填充模型。 attributes 属性由 CModel 定义,它接受一个名值对数组并将其中的每个值赋给相应的模型特性。因此如果 $_POST['LoginForm'] 给了我们这样的一个数组,上面的那段代码也就等同于下面冗长的这段 (假设数组中存在所有所需的特性):
$model->username=$_POST['LoginForm']['username'];
$model->password=$_POST['LoginForm']['password'];
$model->rememberMe=$_POST['LoginForm']['rememberMe'];
注 意: 为了使 $_POST['LoginForm'] 传递给我们的是一个数组而不是字符串, 我们需要在命名表单域时遵守一个规范。具体的,对应于模型类 C 中的特性 a 的表单域,我们将其命名为 C[a] 。例如,我们可使用 LoginForm[username] 命名 username 特性相应的表单域。
现在剩下的工作就是创建 login 视图了,它应该包含一个带有所需输入项的 HTML 表单。
三、创建表单
编 写 login 视图是很简单的,我们以一个 form 标记开始,它的 action 属性应该是前面讲述的 login 动作的URL。然后我们需要为 LoginForm 类中声明的属性插入标签和表单域。最后,我们插入一个可由用户点击提交此表单的提交按钮。所有这些都可以用纯HTML代码完成。
Yii 提供了几个助手(helper)类简化视图编写。例如,要创建一个文本输入域,我们可以调用 CHtml::textField();要创建一个下拉列表,则调用 CHtml::dropDownList()。
例如, 如下代码将生成一个文本输入域,它可以在用户修改了其值时触发表单提交动作。
CHtml::textField($name,$value,array('submit'=>''));
下面,我们使用 CHtml 创建一个登录表单。我们假设变量 $model 是 LoginForm 的实例。
上 述代码生成了一个更加动态的表单,例如, CHtml::activeLabel() 生成一个与指定模型的特性相关的标签。如果此特性有一个输入错误,此标签的CSS class 将变为 error,通过 CSS 样式改变了标签的外观。相似的, CHtml::activeTextField() 为指定模型的特性生成一个文本输入域,并会在错误发生时改变它的 CSS class。
我们还可以使用一个新的小物件 CActiveForm 以简化表单创建。这个小物件可同时提供客户端及服务器端无缝的、一致的验证。使用 CActiveForm, 上面的代码可重写为:
beginWidget('CActiveForm'); ?>
errorSummary($model); ?>
label($model,'username'); ?>
textField($model,'username') ?>
label($model,'password'); ?>
passwordField($model,'password') ?>
checkBox($model,'rememberMe'); ?>
label($model,'rememberMe'); ?>
endWidget(); ?>
四、收集表格输入
有时我们想通过批量模式收集用户输入。也就是说,用户可以为多个模型实例输入信息并将它们一次性提交。我们将此称为 表格输入(tabular input) ,因为这些输入项通常以 HTML 表格的形式呈现。
要 使用表格输入,我们首先需要创建或填充一个模型实例数组,取决于我们是想插入还是更新数据。然后我们从 $_POST 变量中提取用户输入的数据并将其赋值到每个模型。和单模型输入稍有不同的一点就是:我们要使用 $_POST['ModelClass'][$i] 提取输入的数据而不是使用 $_POST['ModelClass']。
public function actionBatchUpdate()
{
// 假设每一项(item)是一个 'Item' 类的实例,
// 提取要通过批量模式更新的项
$items=$this->getItemsToUpdate();
if(isset($_POST['Item']))
{
$valid=true;
foreach($items as $i=>$item)
{
if(isset($_POST['Item'][$i]))
$item->attributes=$_POST['Item'][$i];
$valid=$valid && $item->validate();
}
if($valid) // 如果所有项目有效
// ...则在此处做一些操作
}
// 显示视图收集表格输入
$this->render('batchUpdate',array('items'=>$items));
}
准备好了这个动作,我们需要继续 batchUpdate 视图的工作以在一个 HTML 表格中显示输入项。
NamePriceCount
Description
$item): ?>
注意,在上面的代码中我们使用了 "[$i]name" 而不是 "name" 作为调用 CHtml::activeTextField 时的第二个参数。
如果有任何验证错误,相应的输入项将会自动高亮显示,就像前面我们讲解的单模型输入一样。
Ⅲ、数据库操作
Yii 提供了强大的数据库编程支持。Yii数据访问对象(DAO)建立在PHP的数据对象(PDO)extension上,使得在一个单一的统一的接口可以访问 不同的数据库管理系统(DBMS)。使用Yii的DAO开发的应用程序可以很容易地切换使用不同的数据库管理系统,而不需要修改数据访问代码。Yii 的Active Record( AR ),实现了被广泛采用的对象关系映射(ORM)办法,进一步简化数据库编程。按照约定,一个类代表一个表,一个实例代表一行数据。Yii AR消除了大部分用于处理CRUD(创建,读取,更新和删除)数据操作的sql语句的重复任务。
尽管Yii的DAO和AR能够处理几乎所有数据库相关的任务,您仍然可以在Yii application中使用自己的数据库。事实上,Yii框架精心设计使得可以与其他第三方库同时使用。
一、数据访问对象 (DAO)
Yii DAO 基于 PHP Data Objects (PDO) 构建。它是一个为众多流行的DBMS提供统一数据访问的扩展,这些 DBMS 包括 MySQL, PostgreSQL 等等。因此,要使用 Yii DAO,PDO 扩展和特定的 PDO 数据库驱动(例如 PDO_MYSQL) 必须安装。
Yii DAO 主要包含如下四个类:
CDbConnection: 代表一个数据库连接。
CDbCommand: 代表一条通过数据库执行的 SQL 语句。
CDbDataReader: 代表一个只向前移动的,来自一个查询结果集中的行的流。
CDbTransaction: 代表一个数据库事务。
1、建立数据库连接
要 建立一个数据库连接,创建一个 CDbConnection 实例并将其激活。连接到数据库需要一个数据源的名字(DSN)以指定连接信息。用户名和密码也可能会用到。当连接到数据库的过程中发生错误时 (例如,错误的 DSN 或无效的用户名/密码),将会抛出一个异常。
$connection=new CDbConnection($dsn,$username,$password);
// 建立连接。你可以使用 try...catch 捕获可能抛出的异常
$connection->active=true;
......
$connection->active=false; // 关闭连接
DSN 的格式取决于所使用的 PDO 数据库驱动。总体来说, DSN 要含有 PDO 驱动的名字,跟上一个冒号,再跟上驱动特定的连接语法。可查阅 PDO 文档 获取更多信息。下面是一个常用DSN格式的列表。
* SQLite: sqlite:/path/to/dbfile
* MySQL: mysql:host=localhost;dbname=testdb
* PostgreSQL: pgsql:host=localhost;port=5432;dbname=testdb
* SQL Server: mssql:host=localhost;dbname=testdb
* Oracle: oci:dbname=//localhost:1521/testdb
由于 CDbConnection 继承自 CApplicationComponent,我们也可以将其作为一个 应用组件 使用。要这样做的话,请在 应用配置 中配置一个 db (或其他名字)应用组件如下:
array(
......
'components'=>array(
......
'db'=>array(
'class'=>'CDbConnection',
'connectionString'=>'mysql:host=localhost;dbname=testdb',
'username'=>'root',
'password'=>'password',
'emulatePrepare'=>true, // needed by some MySQL installations
),
),
)
然后我们就可以通过 Yii::app()->db 访问数据库连接了。它已经被自动激活了,除非我们特意配置了 CDbConnection::autoConnect 为 false。通过这种方式,这个单独的DB连接就可以在我们代码中的很多地方共享。
2、执行SQL语句
数据库连接建立后,SQL 语句就可以通过使用 CDbCommand 执行了。你可以通过使用指定的SQL语句作为参数调用 CDbConnection::createCommand() 创建一个 CDbCommand 实例。
$connection=Yii::app()->db; // 假设你已经建立了一个 "db" 连接
// 如果没有,你可能需要显式建立一个连接:
// $connection=new CDbConnection($dsn,$username,$password);
$command=$connection->createCommand($sql);
// 如果需要,此 SQL 语句可通过如下方式修改:
// $command->text=$newSQL;
一条 SQL 语句会通过 CDbCommand 以如下两种方式被执行:
execute(): 执行一个无查询 (non-query)SQL语句,例如 INSERT, UPDATE 和 DELETE 。如果成功,它将返回此执行所影响的行数。
query(): 执行一条会返回若干行数据的 SQL 语句,例如 SELECT。如果成功,它将返回一个 CDbDataReader 实例,通过此实例可以遍历数据的结果行。为简便起见,(Yii)还实现了一系列 queryXXX() 方法以直接返回查询结果。
执行 SQL 语句时如果发生错误,将会抛出一个异常。
$rowCount=$command->execute(); // 执行无查询SQL
$dataReader=$command->query(); // 执行一个SQL查询
$rows=$command->queryAll(); // 查询并返回结果中的所有行
$row=$command->queryRow(); // 查询并返回结果中的第一行
$column=$command->queryColumn(); // 查询并返回结果中的第一列
$value=$command->queryScalar(); // 查询并返回结果中第一行的第一个字段
3、获取查询结果
在 CDbCommand::query() 生成 CDbDataReader 实例之后,你可以通过重复调用 CDbDataReader::read() 获取结果中的行。你也可以在 PHP 的 foreach 语言结构中使用 CDbDataReader 一行行检索数据。
$dataReader=$command->query();
// 重复调用 read() 直到它返回 false
while(($row=$dataReader->read())!==false) { ... }
// 使用 foreach 遍历数据中的每一行
foreach($dataReader as $row) { ... }
// 一次性提取所有行到一个数组
$rows=$dataReader->readAll();
注意: 不同于query(), 所有的queryXXX()方法会直接返回数据。例如,queryRow()会返回代表查询结果第一行的一个数组。
4、使用事务
事务,在 Yii 中表现为 CDbTransaction 实例,可能会在下面的情况中启动:
* 开始事务.
* 一个个执行查询。任何对数据库的更新对外界不可见。
* 提交事务。如果事务成功,更新变为可见。
* 如果查询中的一个失败,整个事务回滚。
上述工作流可以通过如下代码实现:
$transaction=$connection->beginTransaction();
try
{
$connection->createCommand($sql1)->execute();
$connection->createCommand($sql2)->execute();
//.... other SQL executions
$transaction->commit();
}
catch(Exception $e) // 如果有一条查询失败,则会抛出异常
{
$transaction->rollBack();
}
5、绑定参数
要避免 SQL 注入攻击 并提高重复执行的 SQL 语句的效率,你可以 "准备(prepare)"一条含有可选参数占位符的 SQL 语句,在参数绑定时,这些占位符将被替换为实际的参数。
参 数占位符可以是命名的 (表现为一个唯一的标记) 或未命名的 (表现为一个问号)。调用 CDbCommand::bindParam() 或 CDbCommand::bindValue() 以使用实际参数替换这些占位符。这些参数不需要使用引号引起来:底层的数据库驱动会为你搞定这个。参数绑定必须在 SQL 语句执行之前完成。
// 一条带有两
入口文件内容:一般格式如下:
$yii=dirname(__FILE__).'/../../framework/yii.php';//Yii框架位置
$config=dirname(__FILE__).'/protected/config/main.php';//当前应用程序的主配置文件位置
// 部署正式环境时,去掉下面这行
// defined('YII_DEBUG') or define('YII_DEBUG',true);//是否运行在调试模式下
require_once($yii);//包含Yii框架
Yii::createWebApplication($config)->run();//根据主配置文件建立应用实例,并运行。你可以在当前应用的任何位置通过Yii::app()来访问这个实例。
二、主配置文件
保存位置:你的应用/protected/config/main.php
文件内容:一般格式如下:
return array(
'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..', //当前应用根目录的绝对物理路径
'name'=>'Yii Blog Demo', //当前应用的名称
// 预载入log(记录)应用组件,这表示该应用组件无论它们是否被访问都要被创建。该应用的参数配置在下面以“components”为关键字的数组中设置。
'preload'=>array('log'), //log为组件ID
// 自动载入的模型和组件类
'import'=>array(
'application.models.*', //载入“application/models/”文件夹下的所有模型类
'application.components.*', //载入“application/components/”文件夹下的所有应用组件类
),
'defaultController'=>'post', //设置默认控制器类
// 当前应用的组件配置。更多可供配置的组件详见下面的“核心应用组件”
'components'=>array(
'user'=>array( //user(用户)组件配置,“user”为组件ID
// 可以使用基于cookie的认证
'allowAutoLogin'=>true, //允许自动登录
),
'cache'=>array( //缓存组件
'class'=>'CMemCache', //缓存组件类
'servers'=>array( //MemCache缓存服务器配置
array('host'=>'server1', 'port'=>11211, 'weight'=>60), //缓存服务器1
array('host'=>'server2', 'port'=>11211, 'weight'=>40), //缓存服务器2
),
),
'db'=>array( //db(数据库)组件配置,“db”为组件ID
'connectionString' => 'sqlite:protected/data/blog.db', //连接数据库的DSN字符串
'tablePrefix' => 'tbl_', //数据表前缀
),
// 如果要使用一个MySQL数据库,请取消下面的注释
/*
'db'=>array(
'connectionString' => 'mysql:host=localhost;dbname=blog', //连接mysql数据库
'emulatePrepare' => true,
'username' => 'root', //MySQL数据库用户名
'password' => '', //MySQL数据库用户密码
'charset' => 'utf8', //MySQL数据库编码
'tablePrefix' => 'tbl_', //MySQL数据库表前缀
),
*/
'errorHandler'=>array(
// 使用SiteController控制器类中的actionError方法显示错误
'errorAction'=>'site/error', //遇到错误时,运行的操作。控制器名和方法名均小写,并用斜线“/”隔开
),
//URL路由管理器
'urlManager'=>array(
'urlFormat'=>'path', //URL格式。 共支持两种格式:'path'格式(如:/path/to/EntryScript.php/name1/value1/name2 /value2...)和'get'格式(如: /path/to/EntryScript.php?name1=value1&name2=value2...)。当使用'path'格式时, 需要设置如下的规则:
'rules'=>array( //URL规则。语法:<参数名:正则表达式>
'post//'=>'post/view', //将post/12/helloword指向post/view?id=12&title=helloword
'posts/'=>'post/index', //将posts/hahahaha指向post/index?tag=hahahaha
'/'=>'/',
),
),
'log'=>array( //记录
'class'=>'CLogRouter', //处理记录信息的类
'routes'=>array(
array(
'class'=>'CFileLogRoute', //处理错误信息的类
'levels'=>'error, warning', //错误等级
),
// 如要将错误记录消息在网页上显示,取消下面的注释即可
/*
array(
'class'=>'CWebLogRoute',
),
*/
),
),
), //应用组件配置结束
// 使用Yii::app()->params['参数名']可以访问应用层的参数
'params'=>require(dirname(__FILE__).'/params.php'),
);
核心应用组件:
Yii 预定义了一系列核心应用组件,提供常见 Web 应用中所用的功能。例如, request 组件用于解析用户请求并提供例如 URL,cookie 等信息。通过配置这些核心组件的属性,我们可以几乎任意的修改Yii 的默认行为。
下面我们列出了由 CWebApplication 预定义的核心组件。
assetManager: CAssetManager - 管理私有资源文件的发布。
authManager: CAuthManager - 管理基于角色的访问控制 (RBAC).
cache: CCache - 提供数据缓存功能。注意,你必须指定实际的类(例如CMemCache, CDbCache)。否则,当你访问此组件时将返回 NULL。
clientScript: CClientScript - 管理客户端脚本 (java scripts 和 CSS).
coreMessages: CPhpMessageSource - 提供 Yii 框架用到的核心信息的翻译。
db: CDbConnection - 提供数据库连接。注意,使用此组件你必须配置其 connectionString 属性。
errorHandler: CErrorHandler - 处理未捕获的 PHP 错误和异常。
format: CFormatter - 格式化数值显示。此功能从版本 1.1.0 起开始提供。
messages: CPhpMessageSource - 提供Yii应用中使用的信息翻译。
request: CHttpRequest - 提供关于用户请求的信息。
securityManager: CSecurityManager - 提供安全相关的服务,例如散列,加密。
session: CHttpSession - 提供session相关的功能。
statePersister: CStatePersister - 提供全局状态持久方法。
urlManager: CUrlManager - 提供 URL 解析和创建相关功能
user: CWebUser - 提供当前用户的识别信息。
themeManager: CThemeManager - 管理主题。
要访问一个应用组件,使用 Yii::app()->组件的ID
三、控制器(Controller)
控 制器 是 CController 类的子类的实例。它在当用户请求时由应用创建。当一个控制器运行时,它执行所请求的动作(控制器类方法),动作通常会引入所必要的模型并渲染相应的视图。 动作,就是一个名字以 action 开头的控制器类方法(action+大写首字母的动作名)。
控制器类文件保存位置protected/controllers/
控制器和动作以 ID 识别。
控制器ID 是一种 '父目录/子目录/控制器名' 的格式,对应相应的控制器类文件 protected/controllers/父目录/子目录/大写首字母的控制器名Controller.php;
动作ID 是除去 action 前缀的动作方法名。
1、路由
用户以路由的形式请求特定的控制器和动作。路由是由控制器 ID 和动作 ID 连接起来的,两者以斜线分割。
例如,路由 post/edit 代表 PostController 及其 edit 动作。默认情况下,URL http://hostname/index.php?r=post/edit 即请求此控制器和动作。
注 意: 默认情况下,路由是大小写敏感的。可以通过设置应用配置中的 CUrlManager::caseSensitive 为 false 使路由对大小写不敏感。当在大小写不敏感模式中时, 要确保你遵循了相应的规则约定,即:包含控制器类文件的目录名小写,且 控制器映射 和 动作映射 中使用的键为小写。
路由的格式:控制器ID/动作ID 或 模块ID/控制器ID/动作ID(如果是嵌套模块,模块ID 就是 父模块ID/子模块ID)
2、控制器实例化
应用将使用如下规则确定控制器的类以及类文件的位置:
1、如果指定了 CWebApplication::catchAllRequest , 控制器将基于此属性创建,而用户指定的控制器 ID 将被忽略。这通常用于将应用设置为维护状态并显示一个静态提示页面。
2、如果在 CWebApplication::controllerMap 中找到了 ID, 相应的控制器配置将被用于创建控制器实例。
3、 如果 ID 为 'path/to/xyz'的格式,控制器类的名字将判断为 XyzController,相应的类文件则为 protected/controllers/path/to/XyzController.php。如果类文件不存在,将触发一个 404 CHttpException 异常。
在使用了模块的情况下,应用将检查此 ID 是否代表一个模块中的控制器。如果是的话,模块实例将被首先创建,然后创建模块中的控制器实例。
3、动作(action)
动作 就是被定义为一个以 action 单词作为前缀命名的方法。而更高级的方式是定义一个动作类并让控制器在收到请求时将其实例化。这使得动作可以被复用,提高了可复用度。
1、定义一个动作类,基本格式如下:
class UpdateAction extends CAction
{
public function run()
{
// place the action logic here
}
}
2、使用动作类:为了让控制器注意到这个动作,我们要用如下方式覆盖控制器类的actions() 方法:
class PostController extends CController
{
public function actions()
{
return array(
'edit'=>'application.controllers.post.UpdateAction', //使用“应用程序文件夹/controllers/post/UpdateAction.php”文件中的类来处理edit动作
);
}
}
如上所示,我们使用了路径别名“application.controllers.post.UpdateAction”指定动作类文件为“protected/controllers/post/UpdateAction.php”。
通过编写基于类的动作,我们可以将应用组织为模块的风格。例如,如下目录结构可用于组织控制器相关代码:
protected/
controllers/
PostController.php
UserController.php
post/
CreateAction.php
ReadAction.php
UpdateAction.php
user/
CreateAction.php
ListAction.php
ProfileAction.php
UpdateAction.php
4、过滤器(filter)
过滤器是一段代码,可被配置在控制器动作执行之前或之后执行。
一个动作可以有多个过滤器。如有多个过滤器,则按照它们出现在过滤器列表中的顺序依次执行。过滤器可以阻止动作及后面其他过滤器的执行。
过滤器可以定义为一个控制器类的方法。过滤器方法名必须以 filter 开头。例如,现有的 filterAccessControl 方法定义了一个名为 accessControl 的过滤器。过滤器方法必须为如下结构:
public function filterAccessControl($filterChain)
{
// 调用 $filterChain->run() 以继续后续过滤器与动作的执行。
}
$filterChain (过滤器链)是一个 CFilterChain 的实例,代表与所请求动作相关的过滤器列表。在过滤器方法中,我们可以调用 $filterChain->run() 以继续执行后续过滤器和动作。
如 动作 一样,过滤器也可以是一个对象,它是 CFilter 或其子类的实例。如下代码定义了一个新的过滤器类:
class PerformanceFilter extends CFilter
{
protected function preFilter($filterChain)
{
// 动作被执行之前应用的逻辑
return true; // 如果动作不应被执行,此处返回 false
}
protected function postFilter($filterChain)
{
// 动作执行之后应用的逻辑
}
}
要对动作应用过滤器,我们需要覆盖 CController::filters() 方法。此方法应返回一个过滤器配置数组。例如:
class PostController extends CController
{
......
public function filters()
{
return array(
'postOnly + edit, create', //将postOnly过滤器应用于edit和create动作(这是基于方法的过滤器)
array( //使用了数组来配置过滤器
'application.filters.PerformanceFilter - edit, create', //将application.filters.PerformanceFilter过滤器应用于除了edit和create之外的所有动作(这是基于对 象的过滤器)
'unit'=>'second', //初始化过滤器对象中的unit属性值为second
),
);
}
}
上 述代码指定了两个过滤器: postOnly 和 PerformanceFilter。 postOnly 过滤器是基于方法的(相应的过滤器方法已在 CController 中定义);而 performanceFilter 过滤器是基于对象的。路径别名 application.filters.PerformanceFilter 指定过滤器类文件是 protected/filters/PerformanceFilter。我们使用一个数组配置 PerformanceFilter ,这样它就可被用于初始化过滤器对象的属性值。此处 PerformanceFilter 的 unit 属性值将被初始为 second。
使 用加减号,我们可指定哪些动作应该或不应该应用过滤器。上述代码中, postOnly 应只被应用于 edit 和 create 动作,而 PerformanceFilter 应被应用于 除了 edit 和 create 之外的动作。如果过滤器配置中没有使用加减号,则此过滤器将被应用于所有动作。
五、模型(Model)
模型是 CModel 或其子类的实例。模型用于保持数据以及与其相关的业务逻辑。
模型是单独的数据对象。它可以是数据表中的一行,或者一个用户输入的表单。
数据对象的每个字段对应模型中的一个属性。每个属性有一个标签(label),并且可以通过一系列规则进行验证。
Yii 实现了两种类型的模型:表单模型和 Active Record。二者均继承于相同的基类 CModel。
表单模型是 CFormModel 的实例。表单模型用于保持从用户的输入获取的数据。这些数据经常被获取,使用,然后丢弃。例如,在一个登录页面中,我们可以使用表单模型用于表示由最终用户提供的用户名和密码信息。
Active Record (AR) 是一种用于通过面向对象的风格抽象化数据库访问的设计模式。每个 AR 对象是一个 CActiveRecord 或其子类的实例。代表数据表中的一行。行中的字段对应 AR 对象中的属性。
六、视图
视图是一个包含了主要的用户交互元素的PHP脚本.
视 图有一个名字,当渲染(render)时,名字会被用于识别视图脚本文件。视图的名称与其视图脚本名称是一样的。例如:视图 edit 的名称出自一个名为 edit.php 的脚本文件。要渲染时,需通过传递视图的名称调用 CController::render()。这个方法将在“protected/views/控制器ID”目录下寻找对应的视图文件。
在视图脚本内部,我们可以通过 $this 来访问控制器实例。我们可以在视图里以“$this->属性名”的方式获取控制器的任何属性。
我们也可以用以下 推送 的方式传递数据到视图里:
$this->render('edit', array(
'var1'=>$value1,
'var2'=>$value2,
));
在以上的方式中, render() 方法将提取数组的第二个参数到变量里。其产生的结果是,在视图脚本里,我们可以直接访问变量 $var1 和 $var2。
1、布局
布局是一种用来修饰视图的特殊的视图文件。它通常包含了用户界面中通用的一部分视图。例如:布局可以包含header和footer的部分,然后把内容嵌入其间。
......header here......
......footer here......
其中的 $content 则储存了内容视图的渲染结果。
当 使用render()时,布局被隐式应用。视图脚本 protected/views/layouts/main.php 是默认的布局文件。这可以通过改变 CWebApplication::layout 进行自定义。要渲染一个不带布局的视图,则需调用 renderPartial() 。
2、小物件
小物件是 CWidget 或其子类的实例。它是一个主要用于表现数据的组件。小物件通常内嵌于一个视图来产生一些复杂而独立的用户界面。例如,一个日历小物件可用于渲染一个复杂的日历界面。小物件使用户界面更加可复用。
我们可以按如下视图脚本来使用一个小物件:
beginWidget('小物件类的路径别名'[,'包含属性初始化值的数组']); ?>
...可能会由小物件获取的内容主体...
endWidget(); ?>
或者
widget('小物件类的路径别名'[,'包含属性初始化值的数组']); ?>
后者用于不需要任何 body 内容的组件。
小物件可通过配置来定制它的表现。这是通过调用 CBaseController::beginWidget 或 CBaseController::widget 设置其初始化属性值来完成的。
我们通过传递一个携带这些属性初始化值的数组来实现,该数组的键是属性的名称,而数组的值则是小物件属性所对应的值。如下所示 :
$this->widget('CMaskedTextField',array(
'mask'=>'99/99/9999'
));
?>
继承 CWidget 并覆盖其init()和run()方法,可以定义一个新的小物件:
class MyWidget extends CWidget
{
public function init()
{
// 此方法会被 CController::beginWidget() 调用
}
public function run()
{
// 此方法会被 CController::endWidget() 调用
}
}
小物件可以像一个控制器一样拥有它自己的视图。
默 认情况下,小物件的视图文件位于包含了小物件类文件目录的 views 子目录之下(protected/components/views)。这些视图可以通过调用CWidget::render()渲染,这一点和控制器很 相似。唯一不同的是,小物件的视图没有布局文件支持。另外,小物件视图中的$this指向小物件实例而不是控制器实例。
3、系统视图
系统视图的渲染通常用于展示 Yii 的错误和日志信息。
系统视图的命名遵从了一些规则。比如像“errorXXX”这样的名称就是用于渲染展示错误号XXX的 CHttpException 的视图。例如,如果 CHttpException 抛出一个404错误,那么 error404 就会被显示。
在 framework/views 下, Yii 提供了一系列默认的系统视图. 他们可以通过在 protected/views/system 下创建同名视图文件进行自定义。
七、组件
Yii 应用建立于组件之上。组件是 CComponent 或其子类的实例。使用组件主要涉及访问它的属性以及触发或处理它的时间。基类 CComponent 指定了如何定义属性和事件。
1、组件属性
组件的属性就像对象的公共成员变量。它是可读写的。
要定义一个组件属性,我们只需在组件类中定义一个公共成员变量即可。
更灵活的方式是定义其 getter 和 setter 方法,例如:
public function getTextWidth() // 获取 textWidth 属性
{
return $this->_textWidth;
}
public function setTextWidth($value) // 设置 TextWidth 属性
{
$this->_textWidth=$value;
}
上 述代码定义了一个可写的属性名为 textWidth(名字是大小写不敏感的)。当读取属性时,getTextWidth() 就会被调用,其返回值则成为属性值;相似的,当写入属性时,setTextWidth() 被调用。如果 setter 方法没有定义,则属性将是只读的,如果对其写入则会抛出一个异常。使用 getter 和 setter 方法定义一个属性有一个好处:即当读取或写入属性时,可以执行额外的逻辑(例如,执行验证,触发事件)。
注意: 通过 getter/setter 定义的属性和类成员变量之间有一个细微的差异:属性的名字是大小写不敏感的, 而 类成员变量 是大小写敏感的。
2、组件事件
组件事件是一些特殊的属性,它们使用一些称作 事件句柄(event handlers)的方法作为其值。分配一个方法到一个事件将会引起方法在事件被唤起处自动被调用。因此,一个组件的行为可能会被一种在部件开发过程中不可预见的方式修改。
组件事件以 on 开头的命名方式定义。和属性通过 getter/setter 方法来定义的命名方式一样,事件的名称是大小写不敏感的。以下代码定义了一个 onClicked 事件:
public function onClicked($event)
{
$this->raiseEvent('onClicked', $event);
}
这里作为事件参数的 $event 是 CEvent 或其子类的实例。
我们可以分配一个方法到此事件,如下所示:
$component->onClicked=$callback;
这里的 $callback 指向了一个有效的 PHP 回调。它可以是一个全局函数也可以是类中的一个方法。如果是后者,它必须以一个数组的方式提供: array($object,'methodName')。
事件句柄的结构如下:
function 方法名($event)
{
......
}
这里的 $event 即描述事件的参数(它来源于 raiseEvent() 调用)。$event 参数是 CEvent 或其子类的实例。至少,它包含了关于谁触发了此事件的信息。
事件句柄也可以是一个PHP 5.3以后支持的匿名函数。例如:
$component->onClicked=function($event) {
......
}
如果我们现在调用 onClicked(),onClicked 事件将被触发(在 onClicked() 中),附属的事件句柄将被自动调用。
一个事件可以绑定多个句柄。当事件触发时,这些句柄将被按照它们绑定到事件时的顺序依次执行。如果句柄决定组织后续句柄被执行,它会设置 $event->handled 为 true。
3、组件行为
组件已添加了对 mixin 的支持,并可以绑定一个或多个行为。 行为是一个对象,其方法可以被它绑定的部件通过收集功能的方式来实现继承(inherited),而不是专有化继承(即普通的类继承)。一个部件可以以'多重继承'的方式实现多个行为的绑定。
行为类必须实现 IBehavior 接口。 大多数行为可以继承自 CBehavior 。如果一个行为需要绑定到一个模型, 它也可以从专为模型实现绑定特性的 CModelBehavior 或 CActiveRecordBehavior 继承。
要使用一个行为,它必须首先通过调用此行为的 attach() 方法绑定到一个组件。然后我们就可以通过组件调用此行为方法:
// $name 在组件中实现了对行为的唯一识别
$component->attachBehavior($name,$behavior);
// test() 是行为中的方法。
$component->test();
已绑定的行为可以像一个组件中的普通属性一样访问。例如,如果一个名为 tree 的行为绑定到了一个组件,我们就可以通过如下代码获得指向此行为的引用。
$behavior=$component->tree;
// 等于下行代码:
// $behavior=$component->asa('tree');
行为是可以被临时禁止的,此时它的方法就会在组件中失效。例如:
$component->disableBehavior($name);
// 下面的代码将抛出一个异常
$component->test();
$component->enableBehavior($name);
// 现在就可以使用了
$component->test();
两个同名行为绑定到同一个组件下是有可能的。在这种情况下,先绑定的行为则拥有优先权。
当和 events, 一起使用时,行为会更加强大。 当行为被绑定到组件时,行为里的一些方法就可以绑定到组件的一些事件上了。这样一来,行为就有机观察或者改变组件的常规执行流程。
一 个行为的属性也可以通过绑定到的组件来访问。这些属性包含公共成员变量以及通过 getters 和/或 setters 方式设置的属性。例如, 若一个行为有一个 xyz 的属性,此行为被绑定到组件 $a,然后我们可以使用表达式 $a->xyz 访问此行为的属性。
八、模块
模块是一个独立的软件单元,它包含 模型, 视图, 控制器 和其他支持的组件。在许多方面上,模块看起来像一个 应用。主要的区别就是模块不能单独部署,它必须存在于一个应用里。用户可以像他们访问普通应用的控制器那样访问模块中的控制器。
模块在一些场景里很有用。对大型应用来说,我们可能需要把它划分为几个模块,每个模块可以单独维护和部署。一些通用的功能,例如用户管理,评论管理,可以以模块的形式开发,这样他们就可以容易地在以后的项目中被复用。
1、创建模块
模块组织在一个目录中,目录名即为模块的唯一ID。模块目录的结构跟 应用基础目录 很相似。下面列出了一个 fourm 的模块的典型的目录结构:
forum/ 模块文件夹
ForumModule.php 模块类文件
components/ 包含可复用的用户组件
views/ 包含小物件的视图文件
controllers/ 包含控制器类文件
DefaultController.php 默认的控制器类文件
extensions/ 包含第三方扩展
models/ 包含模型类文件
views/ 包含控制器视图和布局文件
layouts/ 包含布局文件
default/ 包含 DefaultController 的视图文件
index.php 首页视图文件
模 块必须有一个继承自 CWebModule 的模块类。类的名字通过表达式 ucfirst($id).'Module' 确定, 其中的 $id 代表模块的 ID (或者说模块的目录名字)。模块类是存储模块代码间可共享信息的中心位置。例如,我们可以使用 CWebModule::params 存储模块参数,使用 CWebModule::components 分享模块级的 应用组件。
2、使用模块
要使用模块,首先将模块目录放在 应用基础目录 的modules文件夹中。然后在应用的modules属性中声明模块ID。例如,为了使用上面的forum模块,我们可以使用如下应用配置:
return array(
......
'modules'=>array('forum',...),
......
);
模块也可以在配置时带有初始属性值。做法和配置 应用组件 很类似。例如, forum 模块可以在其模块类中有一个名为 postPerPage 的属性,它可以在 应用配置 中配置如下:
return array(
......
'modules'=>array(
'forum'=>array(
'postPerPage'=>20,
),
),
......
);
模块的实例可通过当前活动控制器的 module 属性访问。在模块实例中,我们可以访问在模块级中共享的信息。例如,为访问上面的 postPerPage 信息,我们可使用如下表达式:
$postPerPage=Yii::app()->controller->module->postPerPage;
// 如如$this引用的是控制器实例,则可以使用下行语句
// $postPerPage=$this->module->postPerPage;
模 块中的控制器动作可以通过路由“模块ID/控制器ID/动作ID”或“模块ID/存放控制器类文件的子目录名/控制器ID/动作ID”访问。例如,假设上 面的 forum 模块有一个名为 PostController 的控制器,我们就可以通过路由 forum/post/create 访问此控制器中的 create 动作。此路由对应的 URL 即 http://www.example.com/index.php?r=forum/post/create。
3、嵌套的模块
模块可以无限级嵌套。这就是说,一个模块可以包含另一个模块,而这另一个模块又可以包含其他模块。我们称前者为 父模块 ,后者为 子模块。子模块必须定义在其父模块的 modules 属性中,就像我们前面在应用配置中定义模块一样。
要访问子模块中的控制器动作,我们应使用路由 父模块ID/子模块ID/控制器ID/动作ID。
九、路径别名
Yii 中广泛的使用了路径别名。路径别名关联于一个目录或文件的路径。它以点号语法指定,类似于广泛使用的名字空间(namespace)格式:
RootAlias.path.to.target
其中的 RootAlias 是某个现存目录的别名,通过调用 YiiBase::setPathOfAlias(), 我们可以定义新的路径别名。为方便起见,Yii 预定义了以下几个根别名:
system: 表示 Yii 框架目录;
zii: 表示 Zii 库 目录;
application: 表示应用的 基础目录;
webroot: 表示 入口脚本 文件所在的目录。
ext: 表示包含了所有第三方 扩展 的目录。
额外的,如果应用使用了 模块, (Yii) 也为每个模块ID定义了根别名,指向相应模块的跟目录。
通过使用 YiiBase::getPathOfAlias(), 别名可以被翻译为其相应的路径。
使用别名可以很方便的导入类的定义。例如,如果我们想包含 CController 类的定义,我们可以调用如下代码
Yii::import('system.web.CController');
import方法跟 include 和 require 不同,它更加高效。导入(import)的类定义并不会真正被包含进来,直到它第一次被引用。多次导入同样的名字空间也会比 include_once 和 require_once 快得多。
我们还可以使用如下语法导入整个目录,这样此目录下的类文件就会在需要时被自动包含。
Yii::import('system.web.*');
除 import 外, 别名还在其他许多地方指向类。例如,路径别名可以传递给 Yii::createComponent() 以创建相应类的实例。即使类文件在之前从未被包含。
不要将路径别名和名字空间混淆了,名字空间是指对一些类名的一个逻辑组合,这样它们就可以相互区分开,即使有相同的名字。而路径别名是用于指向一个类文件或目录。路径别名与名字空间并不冲突。
十、开发规范
下面我们讲解 Yii 编程中推荐的开发规范。为简单起见,我们假设 WebRoot 是 Yii 应用安装的目录。
1、URL
默认情况下,Yii 识别如下格式的 URL:
http://hostname/index.php?r=ControllerID/ActionID
r 变量意为 路由(route) ,它可以被Yii解析为 控制器和动作。如果 ActionID 被省略,控制器将使用默认的动作(在CController::defaultAction中定义);如果 ControllerID 也被省略(或者 r 变量不存在),应用将使用默认的控制器(在CWebApplication::defaultController中定义)。
通过 CUrlManager 的帮助,可以创建更加可识别,更加 SEO 友好的 URL,例如 http://hostname/ControllerID/ActionID.html。
2、代码
Yii 推荐命名变量、函数和类时使用驼峰风格,即每个单词的首字母大写并连在一起,中间无空格。变量名和函数名应该使它们的第一个单词全部小写,以使其区别于类名。对私有类成员变量来说,我们推荐以下划线作为其名字前缀(例如: $_actionList)。
一 个针对控制器类名的特殊规则是它们必须以单词 Controller 结尾。那么控制器ID就是类名的首字母小写并去掉单词Controller。例如,PageController类的ID就是 page。这个规则使应用更加安全。它还使控制器相关的URL更加简单(例如 /index.php?r=page/index 而不是 /index.php?r=PageController/index)。
3、配置
配置是一个键值对数组。每个键代表了所配置的对象中的属性名,每个值则为相应属性的初始值。
类中任何可写的属性都可以被配置。如果没有配置,属性将使用它们的默认值。当配置一个属性时,最好阅读相应文档以保证初始值正确。
4、文件
命名和使用文件的规范取决于它们的类型。
类文件应以它们包含的公有类命名。例如,CController 类位于 CController.php 文件中。公有类是可以被任何其他类使用的类。每个类文件应包含最多一个公有类。私有类(只能被一个公有类使用的类)可以放在使用此类的公有类所在的文件中。
视图文件应以视图的名字命名。例如, index 视图位于 index.php 文件中。视图文件是一个PHP脚本文件,它包含了用于呈现内容的HTML和PHP代码。
配置文件可以任意命名。配置文件是一个PHP脚本,它的主要目的是返回一个体现配置的关联数组。
5、目录
Yii 假定了一系列默认的目录用于不同的场合。如果需要,每个目录都可以自定义。
WebRoot/protected: 这是 应用基础目录,是放置所有安全敏感的PHP脚本和数据文件的地方。Yii 有一个默认的 application 别名指向此目录。此目录及目录中的文件应该保护起来防止Web用户访问。它可以通过 CWebApplication::basePath 自定义。
WebRoot/protected/runtime: 此目录放置应用在运行时产生的私有临时文件。此目录必须对 Web 服务器进程可写。它可以通过 CApplication::runtimePath自定义。
WebRoot/protected/extensions: 此目录放置所有第三方扩展。它可以通过 CApplication::extensionPath 自定义。
WebRoot/protected/modules: 此目录放置所有的应用 模块,每个模块使用一个子目录。
WebRoot/protected/controllers: 此目录放置所有控制器类文件。它可以通过 CWebApplication::controllerPath 自定义。
WebRoot/protected/views: 此目录放置所有试图文件,包含控制器视图,布局视图和系统视图。它可以通过 CWebApplication::viewPath 自定义。
WebRoot/protected/views/ControllerID: 此目录放置单个控制器类中使用的视图文件。此处的 ControllerID 是指控制器的ID 。它可以通过 CController::viewPath 自定义。
WebRoot/protected/views/layouts: 此目录放置所有布局视图文件。它可以通过 CWebApplication::layoutPath 自定义。
WebRoot/protected/views/system: 此目录放置所有系统视图文件。系统视图文件是用于显示异常和错误的模板。它可以通过CWebApplication::systemViewPath自定义。
WebRoot/assets: 此目录放置公共资源文件。资源文件是可以被发布的,可由Web用户访问的私有文件。此目录必须对 Web 服务器进程可写。它可以通过 CAssetManager::basePath 自定义
WebRoot/themes: 此目录放置应用使用的不同的主题。每个子目录即一个主题,主题的名字即目录的名字。它可以通过 CThemeManager::basePath 自定义。
6、数据库
多数Web 应用是由数据库驱动的。我们推荐在对表和列命名时使用如下命名规范。注意,这些规范并不是 Yii 所必须的。
㈠数据库表名和列名都使用小写命名。
㈡名字中的单词应使用下划线分割 (例如 product_order)。
㈢对于表名,你既可以使用单数也可以使用复数。但不要 同时使用两者。为简单起见,我们推荐使用单数名字。
㈣表名可以使用一个通用前缀,例如 tbl_ 。这样当应用所使用的表和另一个应用说使用的表共存于同一个数据库中时就特别有用。这两个应用的表可以通过使用不同的表前缀很容易地区别开。
Ⅱ、使用表单
在 Yii 中处理表单时,通常需要以下步骤:
1. 创建用于表现所要收集数据字段的模型类。
2. 创建一个控制器动作,响应表单提交。
3. 在视图脚本中创建与控制器动作相关的表单。
一、创建模型
在编写表单所需的 HTML 代码之前,我们应该先确定来自最终用户输入的数据的类型,以及这些数据应符合什么样的规则。模型类可用于记录这些信息。正如模型章节所定义的,模型是保存用户输入和验证这些输入的中心位置。
取 决于使用用户所输入数据的方式,我们可以创建两种类型的模型。如果用户输入被收集、使用然后丢弃,我们应该创建一个表单模型; 如果用户的输入被收集后要保存到数据库,我们应使用一个Active Record。两种类型的模型共享同样的基类 CModel ,它定义了表单所需的通用接口。
1、定义模型类
例如创建为一个表单模型:
class LoginForm extends CFormModel
{
public $username;
public $password;
public $rememberMe=false;
}
LoginForm 中定义了三个属性: $username, $password 和 $rememberMe。他们用于保存用户输入的用户名和密码,还有用户是否想记住他的登录的选项。由于 $rememberMe 有一个默认的值 false,相应的选项在初始化显示在登录表单中时将是未勾选状态。
我们将这些成员变量称为特性(attributes)而不是属性(properties),以区别于普通的属性(properties)。特性(attribute)是一个主要用于存储来自用户输入或数据库数据的属性(propertiy)。
2、声明验证规则
一旦用户提交了他的输入,模型被填充,我们就需要在使用前确保用户的输入是有效的。这是通过将用户的输入和一系列规则执行验证实现的。我们在 rules() 方法中指定这些验证规则,此方法应返回一个规则配置数组。
class LoginForm extends CFormModel
{
public $username;
public $password;
public $rememberMe=false;
private $_identity;
public function rules()
{
return array(
array('username, password', 'required'), //username 和 password 为必填项
array('rememberMe', 'boolean'), //rememberMe 应该是一个布尔值
array('password', 'authenticate'), //password 应被验证(authenticated)
);
}
public function authenticate($attribute,$params)
{
$this->_identity=new UserIdentity($this->username,$this->password);
if(!$this->_identity->authenticate())
$this->addError('password','错误的用户名或密码。');
}
}
rules() 返回的每个规则必须是以下格式:
array('AttributeList', 'Validator', 'on'=>'ScenarioList', ...附加选项)
其中:
AttributeList(特性列表)是需要通过此规则验证的特性列表字符串,每个特性名字由逗号分隔;
Validator(验证器) 指定要执行验证的种类;
on 参数是可选的,它指定此规则应被应用到的场景列表;
附加选项 是一个名值对数组,用于初始化相应验证器的属性值。
有三种方式可在验证规则中指定 Validator:
第一, Validator 可以是模型类中一个方法的名字,就像上面示例中的 authenticate 。验证方法必须是下面的结构:
/**
* @param string 所要验证的特性的名字
* @param array 验证规则中指定的选项
*/
public function 验证器名称($attribute,$params) { ... }
第二,Validator可以是一个验证器类的名字,当此规则被应用时,一个验证器类的实例将被创建以执行实际验证。规则中的附加选项用于初始化实例的属性值。验证器类必须继承自 CValidator。
第三,Validator 可以是一个预定义的验证器类的别名。在上面的例子中,required 名字是 CRequiredValidator 的别名,它用于确保所验证的特性值不为空。下面是预定义的验证器别名的完整列表:
boolean: CBooleanValidator 的别名,确保特性有一个 CBooleanValidator::trueva lue 或 CBooleanValidator::falseva lue 值。
captcha: CCaptchaValidator 的别名,确保特性值等于 CAPTCHA 中显示的验证码。
compare: CCompareva lidator 的别名,确保特性等于另一个特性或常量。
email: CEmailValidator 的别名,确保特性是一个有效的Email地址。
default: CDefaultValueva lidator 的别名,指定特性的默认值。
exist: CExistValidator 的别名,确保特性值可以在指定表的列中可以找到。
file: CFileva lidator 的别名,确保特性含有一个上传文件的名字。
filter: CFilterValidator 的别名,通过一个过滤器改变此特性。
in: CRangeva lidator 的别名,确保数据在一个预先指定的值的范围之内。
length: CStringValidator 的别名,确保数据的长度在一个指定的范围之内。
match: CRegularExpressionValidator 的别名,确保数据可以匹配一个正则表达式。
numerical: CNumberValidator 的别名,确保数据是一个有效的数字。
required: CRequiredValidator 的别名,确保特性不为空。
type: CTypeva lidator 的别名,确保特性是指定的数据类型。
unique: CUniqueva lidator 的别名,确保数据在数据表的列中是唯一的。
url: CUrlValidator 的别名,确保数据是一个有效的 URL。
下面我们列出了几个只用这些预定义验证器的示例:
// 用户名为必填项
array('username', 'required'),
// 用户名必须在 3 到 12 个字符之间
array('username', 'length', 'min'=>3, 'max'=>12),
// 在注册场景中,密码password必须和password2一致。
array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'),
// 在登录场景中,密码必须接受验证。
array('password', 'authenticate', 'on'=>'login'),
3、安全的特性赋值
在一个类的实例被创建后,我们通常需要用最终用户提交的数据填充它的特性。这可以通过如下块赋值(massive assignment)方式轻松实现:
$model=new LoginForm;
if(isset($_POST['LoginForm']))
$model->attributes=$_POST['LoginForm'];
最后的表达式被称作 块赋值(massive assignment) ,它将 $_POST['LoginForm'] 中的每一项复制到相应的模型特性中。这相当于如下赋值方法:
foreach($_POST['LoginForm'] as $name=>$value)
{
if($name 是一个安全的特性)
$model->$name=$value;
}
检测特性的安全非常重要,例如,如果我们以为一个表的主键是安全的而暴露了它,那么攻击者可能就获得了一个修改记录的主键的机会,从而篡改未授权给他的内容。
特性如果出现在相应场景的一个验证规则中,即被认为是安全的。例如:
array('username, password', 'required', 'on'=>'login, register'),
array('email', 'required', 'on'=>'register'),
如 上所示, username 和 password 特性在 login 场景中是必填项。而 username, password 和 email 特性在 register 场景中是必填项。于是,如果我们在 login 场景中执行块赋值,就只有 username 和 password 会被块赋值。因为只有它们出现在 login 的验证规则中。另一方面,如果场景是 register ,这三个特性就都可以被块赋值。
// 在登录场景中
$model=new User('login');
if(isset($_POST['User']))
$model->attributes=$_POST['User'];
// 在注册场景中
$model=new User('register');
if(isset($_POST['User']))
$model->attributes=$_POST['User'];
那么为什么我们使用这样一种策略来检测特性是否安全呢?背后的基本原理就是:如果一个特性已经有了一个或多个可检测有效性的验证规则,那我们还担心什么呢?
请记住,验证规则是用于检查用户输入的数据,而不是检查我们在代码中生成的数据(例如时间戳,自动产生的主键)。因此,不要为那些不接受最终用户输入的特性添加验证规则。
有时候,我们想声明一个特性是安全的,即使我们没有为它指定任何规则。例如,一篇文章的内容可以接受用户的任何输入。我们可以使用特殊的 safe 规则实现此目的:
array('content', 'safe')
还有一个用于声明一个属性为不安全的 unsafe 规则:
array('permission', 'unsafe')
unsafe 规则并不常用,它是我们之前定义的安全特性的一个例外。
4、触发验证
一 旦模型被用户提交的数据填充,我们就可以调用 CModel::validate() 触发数据验证进程。此方法返回一个指示验证是否成功的值。对 CActiveRecord 模型来说,验证也可以在我们调用其 CActiveRecord::save() 方法时自动触发。
我们可以通过设置scenario属性来设置场景属性,这样,相应场景的验证规则就会被应用。
验 证是基于场景执行的。 scenario属性指定了模型当前用于的场景和当前使用的验证规则集。例如,在 login 场景中,我们只想验证用户模型中的 username 和 password 输入;而在 register 场景中,我们需要验证更多的输入,例如 email, address, 等。下面的例子演示了如何在 register 场景中执行验证:
// 在注册场景中创建一个 User 模型。等价于:
// $model=new User;
// $model->scenario='register';
$model=new User('register'); //给模型类添加参数,该参数就是要触发的验证场景
// 将输入的值填充到模型
$model->attributes=$_POST['User'];
// 执行验证
if($model->validate()) // 如果输入有效
...
else
...
规则关联的场景可以通过规则中的 on 选项指定。如果 on 选项未设置,则此规则会应用于所有场景。例如:
public function rules()
{
return array(
array('username, password', 'required'),
array('password_repeat', 'required', 'on'=>'register'),
array('password', 'compare', 'on'=>'register'),
);
}
第一个规则将应用于所有场景,而第二个将只会应用于 register 场景。
5、提取验证错误
验 证完成后,任何可能产生的错误将被存储在模型对象中。我们可以通过调用 CModel::getErrors() 和CModel::getError() 提取这些错误信息。这两个方法的不同点在于第一个方法将返回 所有 模型特性的错误信息,而第二个将只返回 第一个 错误信息。
6、特性标签
当设计表单时,我们通常需要为每个表单域显示一个标签。标签告诉用户他应该在此表单域中填写什么样的信息。虽然我们可以在视图中硬编码一个标签,但如果我们在相应的模型中指定(标签),则会更加灵活方便。
默认情况下 CModel 将简单的返回特性的名字作为其标签。这可以通过覆盖 attributeLabels() 方法自定义。正如在接下来的小节中我们将看到的,在模型中指定标签会使我们能够更快的创建出更强大的表单。
二、创建动作
有了模型,我们就可以开始编写用于操作此模型的逻辑了。我们将此逻辑放在一个控制器的动作中。对登录表单的例子来讲,相应的代码就是:
public function actionLogin()
{
$model=new LoginForm;
if(isset($_POST['LoginForm']))
{
// 收集用户输入的数据
$model->attributes=$_POST['LoginForm'];
// 验证用户输入,并在判断输入正确后重定向到前一页
if($model->validate())
$this->redirect(Yii::app()->user->returnUrl); //重定向到之前需要身份验证的页面URL
}
// 显示登录表单
$this->render('login',array('model'=>$model));
}
如 上所示,我们首先创建了一个 LoginForm 模型示例;如果请求是一个 POST 请求(意味着这个登录表单被提交了),我们则使用提交的数据 $_POST['LoginForm'] 填充 $model ;然后我们验证此输入,如果验证成功,重定向用户浏览器到之前需要身份验证的页面。如果验证失败,或者此动作被初次访问,我们则渲染 login 视图,此视图的内容我们在下一节中讲解。
提示: 在 login 动作中,我们使用 Yii::app()->user->returnUrl 获取之前需要身份验证的页面URL。 组件 Yii::app()->user 是一种 CWebUser (或其子类) ,它表示用户会话信息(例如 用户名,状态)。
让我们特别留意一下 login 动作中出现的下面的 PHP 语句:
$model->attributes=$_POST['LoginForm'];
正 如我们在 安全的特性赋值 中所讲的,这行代码使用用户提交的数据填充模型。 attributes 属性由 CModel 定义,它接受一个名值对数组并将其中的每个值赋给相应的模型特性。因此如果 $_POST['LoginForm'] 给了我们这样的一个数组,上面的那段代码也就等同于下面冗长的这段 (假设数组中存在所有所需的特性):
$model->username=$_POST['LoginForm']['username'];
$model->password=$_POST['LoginForm']['password'];
$model->rememberMe=$_POST['LoginForm']['rememberMe'];
注 意: 为了使 $_POST['LoginForm'] 传递给我们的是一个数组而不是字符串, 我们需要在命名表单域时遵守一个规范。具体的,对应于模型类 C 中的特性 a 的表单域,我们将其命名为 C[a] 。例如,我们可使用 LoginForm[username] 命名 username 特性相应的表单域。
现在剩下的工作就是创建 login 视图了,它应该包含一个带有所需输入项的 HTML 表单。
三、创建表单
编 写 login 视图是很简单的,我们以一个 form 标记开始,它的 action 属性应该是前面讲述的 login 动作的URL。然后我们需要为 LoginForm 类中声明的属性插入标签和表单域。最后,我们插入一个可由用户点击提交此表单的提交按钮。所有这些都可以用纯HTML代码完成。
Yii 提供了几个助手(helper)类简化视图编写。例如,要创建一个文本输入域,我们可以调用 CHtml::textField();要创建一个下拉列表,则调用 CHtml::dropDownList()。
例如, 如下代码将生成一个文本输入域,它可以在用户修改了其值时触发表单提交动作。
CHtml::textField($name,$value,array('submit'=>''));
下面,我们使用 CHtml 创建一个登录表单。我们假设变量 $model 是 LoginForm 的实例。
上 述代码生成了一个更加动态的表单,例如, CHtml::activeLabel() 生成一个与指定模型的特性相关的标签。如果此特性有一个输入错误,此标签的CSS class 将变为 error,通过 CSS 样式改变了标签的外观。相似的, CHtml::activeTextField() 为指定模型的特性生成一个文本输入域,并会在错误发生时改变它的 CSS class。
我们还可以使用一个新的小物件 CActiveForm 以简化表单创建。这个小物件可同时提供客户端及服务器端无缝的、一致的验证。使用 CActiveForm, 上面的代码可重写为:
beginWidget('CActiveForm'); ?>
errorSummary($model); ?>
label($model,'username'); ?>
textField($model,'username') ?>
label($model,'password'); ?>
passwordField($model,'password') ?>
checkBox($model,'rememberMe'); ?>
label($model,'rememberMe'); ?>
endWidget(); ?>
四、收集表格输入
有时我们想通过批量模式收集用户输入。也就是说,用户可以为多个模型实例输入信息并将它们一次性提交。我们将此称为 表格输入(tabular input) ,因为这些输入项通常以 HTML 表格的形式呈现。
要 使用表格输入,我们首先需要创建或填充一个模型实例数组,取决于我们是想插入还是更新数据。然后我们从 $_POST 变量中提取用户输入的数据并将其赋值到每个模型。和单模型输入稍有不同的一点就是:我们要使用 $_POST['ModelClass'][$i] 提取输入的数据而不是使用 $_POST['ModelClass']。
public function actionBatchUpdate()
{
// 假设每一项(item)是一个 'Item' 类的实例,
// 提取要通过批量模式更新的项
$items=$this->getItemsToUpdate();
if(isset($_POST['Item']))
{
$valid=true;
foreach($items as $i=>$item)
{
if(isset($_POST['Item'][$i]))
$item->attributes=$_POST['Item'][$i];
$valid=$valid && $item->validate();
}
if($valid) // 如果所有项目有效
// ...则在此处做一些操作
}
// 显示视图收集表格输入
$this->render('batchUpdate',array('items'=>$items));
}
准备好了这个动作,我们需要继续 batchUpdate 视图的工作以在一个 HTML 表格中显示输入项。
NamePriceCount
$item): ?>
注意,在上面的代码中我们使用了 "[$i]name" 而不是 "name" 作为调用 CHtml::activeTextField 时的第二个参数。
如果有任何验证错误,相应的输入项将会自动高亮显示,就像前面我们讲解的单模型输入一样。
Ⅲ、数据库操作
Yii 提供了强大的数据库编程支持。Yii数据访问对象(DAO)建立在PHP的数据对象(PDO)extension上,使得在一个单一的统一的接口可以访问 不同的数据库管理系统(DBMS)。使用Yii的DAO开发的应用程序可以很容易地切换使用不同的数据库管理系统,而不需要修改数据访问代码。Yii 的Active Record( AR ),实现了被广泛采用的对象关系映射(ORM)办法,进一步简化数据库编程。按照约定,一个类代表一个表,一个实例代表一行数据。Yii AR消除了大部分用于处理CRUD(创建,读取,更新和删除)数据操作的sql语句的重复任务。
尽管Yii的DAO和AR能够处理几乎所有数据库相关的任务,您仍然可以在Yii application中使用自己的数据库。事实上,Yii框架精心设计使得可以与其他第三方库同时使用。
一、数据访问对象 (DAO)
Yii DAO 基于 PHP Data Objects (PDO) 构建。它是一个为众多流行的DBMS提供统一数据访问的扩展,这些 DBMS 包括 MySQL, PostgreSQL 等等。因此,要使用 Yii DAO,PDO 扩展和特定的 PDO 数据库驱动(例如 PDO_MYSQL) 必须安装。
Yii DAO 主要包含如下四个类:
CDbConnection: 代表一个数据库连接。
CDbCommand: 代表一条通过数据库执行的 SQL 语句。
CDbDataReader: 代表一个只向前移动的,来自一个查询结果集中的行的流。
CDbTransaction: 代表一个数据库事务。
1、建立数据库连接
要 建立一个数据库连接,创建一个 CDbConnection 实例并将其激活。连接到数据库需要一个数据源的名字(DSN)以指定连接信息。用户名和密码也可能会用到。当连接到数据库的过程中发生错误时 (例如,错误的 DSN 或无效的用户名/密码),将会抛出一个异常。
$connection=new CDbConnection($dsn,$username,$password);
// 建立连接。你可以使用 try...catch 捕获可能抛出的异常
$connection->active=true;
......
$connection->active=false; // 关闭连接
DSN 的格式取决于所使用的 PDO 数据库驱动。总体来说, DSN 要含有 PDO 驱动的名字,跟上一个冒号,再跟上驱动特定的连接语法。可查阅 PDO 文档 获取更多信息。下面是一个常用DSN格式的列表。
* SQLite: sqlite:/path/to/dbfile
* MySQL: mysql:host=localhost;dbname=testdb
* PostgreSQL: pgsql:host=localhost;port=5432;dbname=testdb
* SQL Server: mssql:host=localhost;dbname=testdb
* Oracle: oci:dbname=//localhost:1521/testdb
由于 CDbConnection 继承自 CApplicationComponent,我们也可以将其作为一个 应用组件 使用。要这样做的话,请在 应用配置 中配置一个 db (或其他名字)应用组件如下:
array(
......
'components'=>array(
......
'db'=>array(
'class'=>'CDbConnection',
'connectionString'=>'mysql:host=localhost;dbname=testdb',
'username'=>'root',
'password'=>'password',
'emulatePrepare'=>true, // needed by some MySQL installations
),
),
)
然后我们就可以通过 Yii::app()->db 访问数据库连接了。它已经被自动激活了,除非我们特意配置了 CDbConnection::autoConnect 为 false。通过这种方式,这个单独的DB连接就可以在我们代码中的很多地方共享。
2、执行SQL语句
数据库连接建立后,SQL 语句就可以通过使用 CDbCommand 执行了。你可以通过使用指定的SQL语句作为参数调用 CDbConnection::createCommand() 创建一个 CDbCommand 实例。
$connection=Yii::app()->db; // 假设你已经建立了一个 "db" 连接
// 如果没有,你可能需要显式建立一个连接:
// $connection=new CDbConnection($dsn,$username,$password);
$command=$connection->createCommand($sql);
// 如果需要,此 SQL 语句可通过如下方式修改:
// $command->text=$newSQL;
一条 SQL 语句会通过 CDbCommand 以如下两种方式被执行:
execute(): 执行一个无查询 (non-query)SQL语句,例如 INSERT, UPDATE 和 DELETE 。如果成功,它将返回此执行所影响的行数。
query(): 执行一条会返回若干行数据的 SQL 语句,例如 SELECT。如果成功,它将返回一个 CDbDataReader 实例,通过此实例可以遍历数据的结果行。为简便起见,(Yii)还实现了一系列 queryXXX() 方法以直接返回查询结果。
执行 SQL 语句时如果发生错误,将会抛出一个异常。
$rowCount=$command->execute(); // 执行无查询SQL
$dataReader=$command->query(); // 执行一个SQL查询
$rows=$command->queryAll(); // 查询并返回结果中的所有行
$row=$command->queryRow(); // 查询并返回结果中的第一行
$column=$command->queryColumn(); // 查询并返回结果中的第一列
$value=$command->queryScalar(); // 查询并返回结果中第一行的第一个字段
3、获取查询结果
在 CDbCommand::query() 生成 CDbDataReader 实例之后,你可以通过重复调用 CDbDataReader::read() 获取结果中的行。你也可以在 PHP 的 foreach 语言结构中使用 CDbDataReader 一行行检索数据。
$dataReader=$command->query();
// 重复调用 read() 直到它返回 false
while(($row=$dataReader->read())!==false) { ... }
// 使用 foreach 遍历数据中的每一行
foreach($dataReader as $row) { ... }
// 一次性提取所有行到一个数组
$rows=$dataReader->readAll();
注意: 不同于query(), 所有的queryXXX()方法会直接返回数据。例如,queryRow()会返回代表查询结果第一行的一个数组。
4、使用事务
事务,在 Yii 中表现为 CDbTransaction 实例,可能会在下面的情况中启动:
* 开始事务.
* 一个个执行查询。任何对数据库的更新对外界不可见。
* 提交事务。如果事务成功,更新变为可见。
* 如果查询中的一个失败,整个事务回滚。
上述工作流可以通过如下代码实现:
$transaction=$connection->beginTransaction();
try
{
$connection->createCommand($sql1)->execute();
$connection->createCommand($sql2)->execute();
//.... other SQL executions
$transaction->commit();
}
catch(Exception $e) // 如果有一条查询失败,则会抛出异常
{
$transaction->rollBack();
}
5、绑定参数
要避免 SQL 注入攻击 并提高重复执行的 SQL 语句的效率,你可以 "准备(prepare)"一条含有可选参数占位符的 SQL 语句,在参数绑定时,这些占位符将被替换为实际的参数。
参 数占位符可以是命名的 (表现为一个唯一的标记) 或未命名的 (表现为一个问号)。调用 CDbCommand::bindParam() 或 CDbCommand::bindValue() 以使用实际参数替换这些占位符。这些参数不需要使用引号引起来:底层的数据库驱动会为你搞定这个。参数绑定必须在 SQL 语句执行之前完成。
// 一条带有两