开源PHP开发框架Yii全方位教程(1)应用(Yii::app)...............................................................................2开源PHP开发框架Yii全方位教程(2)控制器CController.......................................................................5开源PHP开发框架Yii全方位教程(3)模型CModel.................................................................................9开源PHP开发框架Yii全方位教程(4)视图View....................................................................................10开源PHP开发框架Yii全方位教程(5)组件CComponent.......................................................................13开源PHP开发框架Yii全方位教程(6)模块...............................................................................................16开源PHP开发框架Yii全方位教程(7)路径别名和命名空间..................................................................19开源PHP开发框架Yii全方位教程(8)惯例...............................................................................................21开源PHP开发框架Yii全方位教程(9)开发流程.......................................................................................23开源PHP开发框架Yii全方位教程(11)ActiveRecord(AR).....................................................................29开源PHP开发框架Yii全方位教程(12)片段缓存.....................................................................................41开源PHP开发框架Yii全方位教程(13)页面缓存.....................................................................................44开源PHP开发框架Yii全方位教程(14)动态内容.....................................................................................45开源PHP开发框架Yii全方位教程(15)使用扩展.....................................................................................46开源PHP开发框架Yii全方位教程(16)创建扩展.....................................................................................52开源PHP开发框架Yii全方位教程(17)使用第三方库............................................................................57开源PHP开发框架Yii全方位教程(18)定义fixture................................................................................58开源PHP开发框架Yii全方位教程(19)单元测试.....................................................................................60开源PHP开发框架Yii全方位教程(20)功能测试.....................................................................................62开源PHP开发框架Yii全方位教程(21)自动生成代码............................................................................开源PHP开发框架Yii全方位教程(22)URL管理.....................................................................................71
开源PHP开发框架Yii全方位教程(1)应用(Yii::app)
应用代表了整个请求的运行过程。其主要任务是解析用户请求,并将其分配给相应的控制器以进行进一步的处理。它同时也是保存应用级配置的核心。因此,应用一般被称为“前端控制器”。
在入口脚本中,应用被创建为一个单例。它可以在任何位置通过Yii::app()来被访问。应用配置
默认情况下,应用是CWebApplication类的一个实例。要对其进行定制,通常是在应用实例被创建的时候提供一个配置文件(或数组)来初始化其属性值。另一个定制应用的方法就是扩展CWebApplication类。
配置是一个键值对的数组。每个键名都对应应用实例的一个属性,相应的值为属性的初始值。举例来说,下面的代码设定了应用的name和defaultController属性。
1234
)array(
'name'=>'YiiFramework','defaultController'=>'site',
复制代码
我们一般将配置保存在一个单独的PHP脚本中(如protected/config/main.php)。在这个脚本中,我们按如下方式返回配置数组,
5
returnarray(...);
复制代码
为应用这些配置,我们一般将这个文件的文件名作为一个参数,传递给应用的构造器。或者像下述例子这样传递给Yii::createWebApplication(),就像我们经常在入口脚本里做的那样:
6
$app=Yii::createWebApplication($configFile);
复制代码
如果应用配置非常复杂,我们可以将这分成几个文件,每个文件返回一部分配置参数。接下来,我们在主配置文件里用PHP的include()把其它配置文件加载进来并合并成一个配置数组。
应用的主目录
应用的主目录是指包含所有安全系数比较高的PHP代码和数据的根目录。在默认情况下,这个目录一般和入口脚本所在目录同级的一个子目录:protected。这个路径可以通过在应用配置里设置basePath属性来改变.
不应该让WEB用户访问应用文件夹里的内容。在ApacheHTTP服务器里,我们可以在这
个文件夹里放一个.htaccess文件来实现。.htaccess的文件内容是这样的:
7
denyfromall
复制代码
应用组件
我们可以很容易的通过组件(component)设置和丰富一个应用(Application)的功能。一个应用可以有很多应用组件,每个组件都执行一些特定的功能。比如说,一个应用可能通过CUrlManager和CHttpRequest组件来解析用户的访问请求。
通过配置应用的components属性,我们可以为应用中的每个应用组件,配置类名及其参数。例如,我们可以配置CMemCache组件以便用服务器的内存当缓存:
1011121314151617181920)
),
),
),
array(
......
'components'=>array(
......
'cache'=>array(
'class'=>'CMemCache','servers'=>array(
array('host'=>'server1','port'=>11211,'weight'=>60),array('host'=>'server2','port'=>11211,'weight'=>40),
复制代码
•在上述例子中,我们将cache元素加在components数组里.这个cache元素告
诉我们这个组件的类是CMemCache,以及其servers属性应该如何初始化。要调用组件,可以使用:Yii::app()->ComponentID,其中ComponentID是指这个组件的ID。(比如Yii::app()->cache).
我们可以在应用配置里,将enabled设置为false来关闭一个组件。当我们访问一个被禁止的组件时,系统会返回一个NULL值。
默认情况下,应用组件是根据需要而创建的。这意味着一个组件只有在被访问的情况下才会创建。因此,系统的整体性能不会因为配置了很多组件而下降。有些组件,(比如CLogRouter)是不管用不用都要创建的。在这种情况下,我们在应用的配置文件里将这些组件的ID加入到应用的preload属性中。应用的核心组件
Yii预定义了一套核心应用组件提供Web应用程序的常见功能。例如,request组件用于解析用户请求和提供网址、cookie等信息。几乎在每一个方面,我们都可以通过配置这
些核心组件的属性,来更改Yii的默认行为。
下面我们列出CWebApplication预先声明的核心组件。
assetManager:CAssetManager-管理发布私有asset文件。
•authManager:CAuthManager-管理基于角色控制(RBAC)。
•cache:CCache-提供数据缓存功能。请注意,您必须指定实际的类(例如CMemCache,CDbCache)。否则,将返回空当访问此元件。
•clientScript:CClientScript-管理客户端脚本(javascriptsandCSS)。
•coreMessages:CPhpMessageSource-提供翻译Yii框架使用的核心消息。•db:CDbConnection-提供数据库连接。请注意,你必须配置它的connectionString属性才能使用此元件。
•errorHandler:CErrorHandler-处理没有捕获的PHP错误和例外。•••
format:CFormatter-为显示目的格式化数据值。已自版本1.1.0可用。messages:CPhpMessageSource-提供翻译Yii应用程序使用的消息。request:CHttpRequest-提供和用户请求相关的信息。
•securityManager:CSecurityManager-提供安全相关的服务,例如散列(hashing),加密(encryption)。
•session:CHttpSession-提供会话(session)相关功能。
•statePersister:CStatePersister-提供全局持久方法(globalstatepersistencemethod)。
•urlManager:CUrlManager-提供网址解析和某些函数。••
user:CWebUser-代表当前用户的身份信息。
themeManager:CThemeManager-管理主题(themes)。
•
应用的生命周期
当处理一个用户请求时,一个应用程序将经历如下生命周期:使用CApplication::preinit()预初始化应用。•建立类自动加载器和错误处理;•注册核心应用组件;•
载入应用配置;
•用CApplication::init()初始化应用程序。注册应用行为;•载入静态应用组件;触发onBeginRequest事件;
•处理用户请求:
解析用户请求;•创建控制器;•执行控制器;
•
触发onEndRequest事件;
•
开源PHP开发框架Yii全方位教程(2)控制器CController
1
控制器是CController或者其子类的实例。控制器在用户请求应用时创建。控制器执行所请
求的action,action通常加载必要的模型并渲染恰当的视图。最简单的action仅仅是一个控制器类方法,此方法的名字以action开始。
控制器有默认的action。用户请求不能指定哪一个action执行时,将执行默认的action。缺省情况下,默认的action名为index。可以通过设置CController::defaultAction改变默认的action。
下边是最小的控制器类。因此控制器未定义任何action,请求时会抛出异常。classSiteController
extendsCController23
{}
复制代码
路由
控制器和actions通过ID进行标识的。控制器ID的格式:path/to/xyz对应的类文件protected/controllers/path/to/XyzController.php,相应的xyz应该用实际的控制器名替换(例如post对应protected/controllers/PostController.php).ActionID与是没有action前缀的action方法名字。例如,控制器类包含一个actionEdit方法,对应的actionID就是edit。
注意:在1.0.3版本之前,控制器ID的格式是path.to.xyz而不是path/to/xyz。
用户请求一个特定的controller和action用术语即为路由.路由一个controllerID和一个actionID连结而成,二者中间以斜线分隔.例如,routepost/edit引用的是PostController和它的editaction.默认情况下,URLhttp://hostname/index.php?r=post/edit将请求此controller和action.
注意:默认地情况下,路由是大小写敏感的.从版本1.0.1开始,可以让其大小写不敏感,通过在应用配置中设置CUrlManager::caseSensitive为false.当在大小写不敏感模式下,确保你遵照约定:包含controller类文件的目录是小写的,controllermap和actionmap都使用小写的keys.
自版本1.0.3,一个应用可以包含模块(module).一个module中的controller的route格式是moduleID/controllerID/actionID.更多细节,查阅sectionaboutmodules.控制器实例化
CWebApplication在处理一个新请求时,实例化一个控制器。程序通过控制器的ID,并按如下规则确定控制器类及控制器类所在位置
•
若设置了CWebApplication::catchAllRequest,一个基于此属性的controller将被创建,同时用户指定的controllerID将被忽略.这主要用来将application置于维护模式,并显示一个静态的提醒页面.
•若此ID出现在CWebApplication::controllerMap,对应的controller配置将被用来创建此
controller实例.
•若此ID的格式是'path/to/xyz',controller类名字被假定为XyzController而相应的类文件是protected/controllers/path/to/XyzController.php.例如,一个controllerIDadmin/user将被
解
析
为
controller
类
UserController
,class
文
件
是
protected/controllers/admin/UserController.php.若此class文件不存在,会触发一个404CHttpException
4
一旦使用了modules(自版本1.0.3可用),上面的过程有少许不同.特别的,application将检查此ID是否引用的是一个module中的controller,如果是,此module实例首先被创建,然后创建controller实例.
Action
如之前所述,一个action可以被定义为一个方法,其名字以单词action开头.一个更高级的方式是定义一个action类,当它被请求的时候让controller实例化它.这将允许action可被重用,因此更加具有可重用性.
要定义一个新action类,这样做:classUpdateActionextendsCAction
56710}
}{
publicfunctionrun(){
//placetheactionlogichere
复制代码
11要让controller知道此action的存在,我们重写controller类的actions()方法:classPostControllerextendsCController12{13141516171819}
}
);
publicfunctionactions(){
returnarray(
'edit'=>'application.controllers.post.UpdateAction',
复制代码
20如上所示,使用路径别名application.controllers.post.UpdateAction确定action类文件为
protected/controllers/post/UpdateAction.php.
编写基于类的(class-based)action,我们可以以模块化的方式组织程序。例如,可以使用下边的目录结构组织控制器代码:protected/
212223242526272829303132
controllers/
PostController.phpUserController.phppost/
CreateAction.phpReadAction.phpUpdateAction.phpuser/
CreateAction.phpListAction.phpProfileAction.phpUpdateAction.php
复制代码
33过滤器(Filter)
Filter是一个代码片段,被配置用来在一个控制器的动作执行之前/后执行.例如,anaccesscontrolfilter可被执行以确保在执行请求的action之前已经过验证;一个performancefilter可被用来衡量此action执行花费的时间.
一个action可有多个filter.filter以出现在filter列表中的顺序来执行.一个filter可以阻止当前action及剩余未执行的filter的执行.
一个filter可被定义为一个controller类的方法.此方法的名字必须以filter开始.例如,方法filterAccessControl的存在定义了一个名为accessControl的filter.此filter方法必须如下:publicfunctionfilterAccessControl($filterChain)
34{3536}
//call$filterChain->run()tocontinuefilteringandactionexecution
复制代码
37$filterChain是CFilterChain的一个实例,CFilterChain代表了与被请求的action相关的
filter列表.在此filter方法内部,我们可以调用$filterChain->run()以继续执行其他过滤器以及action的执行.
一个filter也可以是CFilter或其子类的一个实例.下面的代码定义了一个新的filter类:class
PerformanceFilterextendsCFilter
38{394041424344454748}
}}
protectedfunctionpostFilter($filterChain){
//logicbeingappliedaftertheactionisexecutedprotectedfunctionpreFilter($filterChain){
//logicbeingappliedbeforetheactionisexecutedreturntrue;//falseiftheactionshouldnotbeexecuted
复制代码
49要应用filter到action,我们需要重写CController::filters()方法.此方法应当返回一个
filter配置数组.例如,classPostControllerextendsCController
50{515253545556575859606162}
}
);
),
......
publicfunctionfilters(){
returnarray(
'postOnly+edit,create',array(
'application.filters.PerformanceFilter-edit,create','unit'=>'second',
复制代码
上面的代码指定了两个filter:postOnly和PerformanceFilter.postOnlyfilter是基于方法的(对应的filter方法已被定义在CController中);而PerformanceFilterfilter是基于对象的(object-based).路径别名application.filters.PerformanceFilter指定filter类文件是protected/filters/PerformanceFilter.我们使用一个数组来配置PerformanceFilter以便它可被用来初始化此filter对象的属性值.在这里PerformanceFilter的unit属性被将初始化为'second'.使用+和-操作符,我么可以指定哪个action此filter应当和不应当被应用.在上面的例子中,postOnly被应用到edit和createaction,而PerformanceFilter被应用到所有的actions除了edit和create.若+或-均未出现在filter配置中,此filter将被用到所有action.
开源PHP开发框架Yii全方位教程(3)模型CModel
模型是CModel或其子类的实例。模型用于保持数据以及和数据相关的业务规则。
模型描述了一个单独的数据对象。它可以是数据表中的一行数据或者用户输入的一个表单。数据中的各个字段都描述了模型的一个属性。这些属性都有一个标签,都可以被一套可靠的规则验证。Yii实现了表单模型和activerecord两种模型,它们都继承自基类CModel。
表单模型是CFormModel的实例。表单模型用于保存通过收集用户输入得来的数据。这样的数据通常被收集,使用,然后被抛弃。例如,在一个登录页面上,我们可以使用一个表单模型来描述诸如用户名,密码这样的由最终用户提供的信息。若想了解更多,请参阅WorkingwithForm。ActiveRecord(AR)是一种面向对象风格的,用于抽象数据库访问的设计模式。任何一个AR对象都是CActiveRecord或其子类的实例,它描述的数据表中的单独一行数据。这行数据中的字段被描述成AR对象的一个属性。关于AR的更多信息可以在ActiveRecord中找到。
开源PHP开发框架Yii全方位教程(4)视图View
1
视图是一个包含了主要的用户交互元素的PHP脚本。他可以包含PHP语句,但是我们建议
这些语句不要去改变数据模型,且最好能够保持其单纯性(单纯作为视图)!为了实现逻辑和界面分离,大部分的逻辑应该被放置于控制器或模型里,而不是视图里。
一个view有一个当渲染(render)时用来识别view脚本的名字。view名字和它的view脚本文件的名字相同。例如:视图edit的名称出自一个名为edit.php的脚本文件。通过CController::render()调用视图的名称可以渲染一个视图。这个方法将在protected/views/ControllerID目录下寻找对应的视图文件。
在视图脚本内部,我们可以通过$this来访问控制器实例.我们可以在视图里以$this->propertyName的方式获取(pull)控制器的任何属性.
我们也可以用以下push的方式传递数据到视图里:$this->render('edit',array(
234
));
'var1'=>$value1,'var2'=>$value2,
复制代码
5
在以上的方式中,render()方法将提取数组的第二个参数到变量里。其结果是,在视图脚本
里,我们可以直接访问变量$var1和$var2。
布局
布局是一种特殊的视图文件,用来修饰视图。它通常包含了用户交互过程中常用到的一部分视图。例如:视图可以包含header和footer的部分,然后把内容嵌入其间。...
67
...
复制代码
8
而$content则储存了内容视图的渲染结果。
当使用render()时,布局被隐含的应用。视图脚本protected/views/layouts/main.php是默认的布局文件。它可以通过改变CWebApplication::layout或者CController::layout来实现定制。要渲染(render)一个view而不应用任何布局,换用renderPartial()。
部件
部件是CWidget或其子类的实例。它是一个主要用于呈现目的的组件。部件通常内嵌于一个视图来产生一些复杂却的用户界面。例如,一个日历部件可以用于渲染一个复杂的日历界面。部件可以在用户界面上更好的实现重用。
要使用一个部件在一个view脚本中这样做:beginWidget('path.to.WidgetClass');?>
9
...bodycontentthatmaybecapturedbythewidget...
10endWidget();?>
复制代码
11或者widget('path.to.WidgetClass');?>
复制代码
12后者用于不需要任何body内容的widget。
widget可以通过配置来定制它的行为。这些是通过调用CBaseController::beginWidget或者CBaseController::widget设置它们的初始化属性值来完成的。例如,当使用CMaskedTextField组件时,我们想指定被使用的mask。我们通过传递一个携带这些属性初始化值的数组来实现。这里的数组的键是属性的名称,而数组的值则是组件属性所对应的值。正如以下所示:13$this->widget('CMaskedTextField',array(1415));16?>
'mask'=>'99/99/9999'
复制代码
17继承部件以及重载它的init()和run()方法,可以定义一个新的组件:classMyWidgetextendsCWidget18{192021222324252627}
}}
publicfunctionrun(){
//thismethodiscalledbyCController::endWidget()publicfunctioninit(){
//thismethodiscalledbyCController::beginWidget()
复制代码
部件可以像一个控制器一样拥有它自己的视图。默认的,widget的视图文件位于包含了widget文件的views子目录之下。这些视图可以通过调用CWidget::render()渲染,这一点和控制器很相似。唯一不同的是,widget的视图没有布局文件支持。同时,view文件中的$this指的是widget实例而不是controller实例。系统视图(SystemView)
系统视图的渲染通常用于展示Yii的错误和日志信息。例如,当用户请求来一个不存在的控制器或动作时,Yii会抛出一个异常来解释这个错误。这时,Yii就会使用一个特殊的系统视图来展示这个错误。
系统视图的命名遵从了一些规则。比如像errorXXX这样的名称就是用于渲染展示错误号XXX的CHttpException的视图。例如,如果CHttpException抛出来一个404错误,那么error404就会被展示出来。
在framework/views下,Yii提供了一系列默认的系统视图。他们可以通过在protected/views/system下创建同名视图文件来实现定制。
开源PHP开发框架Yii全方位教程(5)组件CComponent
1
Yii应用构建于组件之上。组件是CComponent或其子类的实例。使用组件主要就是涉及
访问其属性和处理/处理它的事件。基类CComponent指定了如何定义属性和事件。
组件属性
组件的属性就像对象的公开成员变量。我们可以读取或设置组件属性的值。例如:$width=$component->textWidth;
2
$component->enableCaching=true;
//获取textWidth属性//设置enableCaching属性
复制代码
3
要定义组件属性,我们可以简单的在组件类里声明一个公共成员变量。更灵活的方法就是,
如下所示的,定义getter和setter方法:publicfunctiongetTextWidth()
456710}
}
publicfunctionsetTextWidth($value){
$this->_textWidth=$value;{
return$this->_textWidth;
复制代码
11以上的代码定义了一个名称为textWidth(大小写不敏感)的可写属性。当读取此属性时,
getTextWidth()被调用,然后它的返回值成为属性的值;同样的,当写入属性时,setTextWidth()被调用。如果setter方法没有定义,属性就是只读的,如果向其写入将抛出一个异常。使用getter和setter方法来定义属性有这样一个好处:当属性被读取或者写入的时候,附加的逻辑(例如执行校验,唤起事件)可以被执行。
通过getter/setter方法来定义一个属性和通过定义类的一个成员变量定义一个属性,有一个微小的差异.前者大小写不敏感而后者大小写敏感.
组件事件
组件事件是一种特殊的属性,它可以将方法(称之为事件句柄(eventhandlers))作为它的值。附加(分配)一个方法到一个事件将会引起方法在事件被触发时自动被调用。因此,一个组件的行为可能会被一种在组件开发过程中不可预见的方式修改。
组件事件以on开头的命名方式定义。和属性通过getter/setter方法来定义的命名方式一样,事件的名称是大小写不敏感的。以下代码定义了一个onClicked事件:publicfunction
onClicked($event)12{1314}
$this->raiseEvent('onClicked',$event);
复制代码
15这里作为事件参数的$event是CEvent或其子类的实例。
我们可以附加一个方法到此event,如下所示:$component->onClicked=$callback;
复制代码
16这里的$callback指向了一个有效的PHP回调。它可以是一个全局函数也可以是类中的一
个方法。如果是后者,它的提供方式必须是一个数组array($object,'methodName')。
事件句柄(eventhandler)必须按照如下来签署:functionmethodName($event)
17{1819}
......
复制代码
20这里的$event是描述事件(源于raiseEvent()调用的)的参数。$event参数是CEvent或
其子类的实例。它至少包含了\"是谁唤起这个事件\"的信息。
从版本1.0.10开始,一个eventhandler也可以是一个PHP5.3以后支持的匿名函数。例如,
$component->onClicked=function($event){......}
复制代码
21如果我们现在调用了onClicked(),onClicked事件将被触发(insideonClicked()),然后被绑
定的事件句柄(eventhandler)将被自动调用.
一个事件可以绑定多个句柄.当事件被唤起时,句柄将会以他们被绑定到事件的先后顺序调用.如果句柄决定在调用期间防止其他句柄的调用,它可以设置$event->handled为true.
组件行为
自1.0.2版起,部件开始支持mixin从而可以绑定一个或者多个行为.一个行为(behavior)就是一个对象,其方法可以被它绑定的部件通过收集功能的方式来实现'继承(inherited)',而不是专有
化继承(即普通的类继承).简单的来说,就是一个部件可以以'多重继承'的方式实现多个行为的绑定.
行为类必须实现IBehavior接口.大多数行为可以从CBehavior基类扩展而来.如果一个行为需要绑定到一个模型,它也可以从专为模型实现绑定特性的CModelBehavior或者CActiveRecordBehavior继承.
使用一个行为,必须首先通过调用行为的attach()方法绑定到一个组件.然后我们就可以通过组件调用行为了://$name是行为在部件中唯一的身份标识.
22$behavior->attach($name,$component);23//test()是一个方法或者行为24$component->test();
复制代码
25一个已绑定的行为是可以被当作组件的一个属性一样来访问的.例如,如果一个名为tree的
行为被绑定到组件,我们可以获得行为对象的引用:$behavior=$component->tree;
26//相当于以下:
27//$behavior=$component->asa('tree');
复制代码
28行为是可以被临时禁止的,此时它的方法开就在组件中失效.例
如:$component->disableBehavior($name);
29//以下语句将抛出一个异常30$component->test();
31$component->enableBehavior($name);32//当前可用
33$component->test();
复制代码
两个同名行为绑定到同一个组件下是很有可能的.在这种情况下,先绑定的行为则拥有优先权.当和events一起使用时,行为会更加强大.当行为被绑定到组件时,行为里的一些方法就可以绑定到组件的一些事件上了.这样一来,行为就有机观察或者改变组件的常规执行流程.
自版本1.1.0开始,一个行为的属性也可以通过绑定到的组件来访问。这些属性包含公共成员变量以及通过getters和/或setters方式设置的属性。例如,若一个行为有一个xyz的属性,此行为被绑定到组件$a,然后我们可以使用表达式$a->xyz访问此行为的属性。
开源PHP开发框架Yii全方位教程(6)模块
1
一个模块是一个自我包含的软件单元,它由模型,视图,控制器和另外组件组成。在很多方
面,一个模块类似于一个应用。主要的不同是一个模块不能单独部署,它必须位于一个应用的内部。用户可以访问一个模块中的控制器,就像访问一个普通的应用的控制器。
模块在一些情况下是有用的。对于一个大型应用,我们可以将它分离为几个模块。每个被单独的开发和维护。一些常用的特征,例如用户管理,评论管理,可以以模块的方式开发以便它们在未来的项目容易的重用。
创建模块
一个模块被组织为一个和它ID名字相同的目录。模块目录的结构类似于应用的目录。下面展示一个名为forum的典型目录结构:forum/
234567101112
ForumModule.phpcomponents/
views/controllers/
themoduleclassfile
containingreusableusercomponentscontainingviewfilesforwidgetscontainingcontrollerclassfiles
DefaultController.phpthedefaultcontrollerclassfileextensions/models/views/
layouts/default/index.php
containingthird-partyextensionscontainingmodelclassfiles
containingcontrollerviewandlayoutfilescontaininglayoutviewfiles
containingviewfilesforDefaultControllertheindexviewfile
复制代码
13一个模块必须有一个扩展自
CWebModule的模块类。类的名字是表达式
ucfirst($id).’Module’,$id是模块ID(或模块目录名)。模块类是在模块代码中存储信息以及分享的核心。例如,我们可以使用
CWebModule::params来存储模块参数,使用
CWebModule::components在模块级分享应用组件。
我们可以使用yiic工具来创建一个新模块的框架。例如,要创建上面的forum模块,我们可以在命令行窗口中执行下面的命令:
%cdWebRoot/testdrive%protected/yiicshellYiiInteractiveToolv1.0
Pleasetype’help’forhelp.Type’exit’toquit.
>>moduleforum
使用模块
要使用一个模块,首先放置模块目录到应用基本目录下。然后在应用的modules属性中声明模块ID。例如,为了使用上面的forum模块,我们可使用下面的应用配置:returnarray(
14151617);
......
'modules'=>array('forum',...),......
复制代码
18一个模块也可以使用初始值来配置。其用法非常类似于配置应用组件。例如,模块forum在
它的模块类中可以有一个属性postPerPage,在应用配置中可以如下配置:returnarray(
1920212223242526);
),......
),......
’modules’=>array(
’forum’=>array(
’postPerPage’=>20,
复制代码
27模块实例可以通过当前活动控制器的module属性来访问。通过模块实例,我们可以访问在
模块级分享的信息。例如,为了访问上面的postPerPage信息,我们可以使用下面的表达式:
$postPerPage=Yii::app()->controller->module->postPerPage;28//orthefollowingif$thisreferstothecontrollerinstance29//$postPerPage=$this->module->postPerPage;
复制代码
一个模块中的控制器动作可以使用路由moduleID/controllerID/actionID来访问。例如,假设上面的forum模块有一个名为PostController的控制器,我们可以使用路由forum/post/create来指向此控制器的create动作。相应http://www.example.com/index.php?r=forum/post/create。
的
路
由
的
URL
是
若一个控制器是一个控制器的子目录,我们仍然可以使用上面的路由格式。例如,假设PostController位于forum/controllers/admin目录中,我们可以指向create动作使用forum/admin/post/create。嵌套模块
模块可以嵌套,一个模块可以包含另外的模块。我们称前者为parentmodule(父模块)后者为childmodule(子模块)。子模块必须放置在父模块的modules目录下。要访问一个子模块中的控制器动作,我们应当使用路由parentModuleID/childModuleID/controllerID/actionID。
开源PHP开发框架Yii全方位教程(7)路径别名和命名空间
•Yii广泛的使用了路径别名.路径别名是和目录或者文件相关联的.它是通过使用点号(\".\")语法指定的,类似于以下这种被广泛使用的命名空间的格式:RootAlias.path.to.target
RootAlias则是一些已经存在目录的别名.通过调用YiiBase::setPathOfAlias()我们可以定义新的路径别名.
为了方便起见,Yii预定义了以下根目录别名:
system:指向Yii框架目录;•zii:指向ziilibrary目录;
•application:指向应用程序基本目录(basedirectory);••
webroot:指向包含里入口脚本文件的目录.此别名自1.0.3版起生效.ext:指向包含所有第三方扩展的目录,从版本1.0.8可用;
另外,若应用使用了module,一个根别名也被定义为每个moduleID并指向相应module的basepath.此特征从版本1.0.3可用.
通过使用YiiBase::getPathOfAlias(),一个别名可以被转换成它的对应的路径.例如,system.web.CController可以被转换成yii/framework/web/CController.
使用别名,来导入已定义的类是非常方便的.例如,如果我们想要包含CController类的定义,我们可以通过以下方式调用:
1
Yii::import('system.web.CController');
复制代码
import方法不同于include和require,它是更加高效的.实际上被导入(import)的类定义直到它第一次被调用之前都是不会被包含的.同样的,多次导入同一个命名空间要比include_once和require_once快很多.
当调用一个通过Yii框架定义的类时,我们不必导入或者加载它.所有的Yii核心类都是被预加载的.
我们也可以按照以下的语法导入整个目录,以便目录下所有的类文件都可以在需要时被包含.
2
Yii::import('system.web.*');
复制代码
除了import外,别名同样被用在其他很多地方来调用类.例如,别名可以被传递到Yii::createComponent()以创建对应类的一个实例,即使这个类文件没有被预先包含.不要把别名和命名空间混淆了.命名空间调用了一些类名的逻辑分组以便他们可以同其他类名区分开,即使他们的名称是一样的,而别名则是用来引用类文件或者目录的.所以路径别名和命名空间并不冲突.
因为PHP5.3.0以前的版本并不内置支持名命名空间,所以你并不能创建两个有着同样名称但是不同定义的类的实例.为此,所有Yii框架类都以字母'C'(代表'class')为前缀,以便避免与用户自定义类产生冲突.在这里我们推荐为Yii框架保留'C'字母前缀的唯一使用权,用户自定义类则可以使用其他字母作为前缀.
开源PHP开发框架Yii全方位教程(8)惯例
Yiifavorsconventionsoverconfigurations。遵循约定您可以不需编写和管理复杂的配置,就可以创建复杂的Yii应用。当然,当需要时Yii可对几乎所有方面进行定制配置。
下面我们描述Yii开发推荐的约定。为了方便起见,我们假设WebRoot是Yii应用安装目录。网址(URL)
URL默认地,Yii识别以下格式URL:
http://hostname/index.php?r=ControllerID/ActionID
Get变量r被Yii路由解释为控制器与动作。如果省略ActionId,控制器会使用默认动作。(通过CController::defaultAction定义);如果ControllerId也省略(或r变量没有值)应用程序会使用默认控制器(通过CWebAppication::defaultController定义)。在CUrlManager的帮助下,有可能生成和识别许多对搜索引擎优化友好的URL,如http://hostname/ControllerID/ActionID.html。此功能详细情况在URLManagement。代码(Code)
Yii建议变量,函数和类类型使用骆驼方式命名,就是大写名字中的每个单词并不用空格连接起来。变量和函数名首字母小写,为了区分于类名称(如:$basePath,runController(),LinkPager)。对于私有的类成员变量,建议将他们的名字前缀加下划线字符(例如:$_actionList)。
因为在PHP5.3.0之前不支持命名空间,建议以一些独特的方式命名这些类,以避免和第三方类名称冲突。出于这个原因,所有Yii框架类以字母\"C\"开头。
控制器类名的特别规则是,他们必须附上Controller后缀。类名的首字母小写,然后切掉结尾的Controller便是控制器的ID。例如,PageController类将有IDpage。这条规则使得应用更加安全。这也使得controller相关的URL更加简洁(例如/index.php?r=page/index替代/index.php?r=PageController/index)。配置(Configuration)
配置是键-值对组成的数组。每个键代表要被配置的对象的一个属性,每个值是相应属性的初始值。例如,array('name'=>'Myapplication','basePath'=>'./protected')初始name和basePath属性为其相应的数组值。
一个对象任何可写的属性均可以配置。如果未被配置,属性将使用它们的默认值。当设定属性,应该阅读相应的文档,以便使初始值设定正确。文件(File)
文件命名和使用的约定取决于其类型。
类文件应命名应使用包含的公共类名字。例如,CController类是在CController.php文件中。公共类是一个可用于任何其他类的类。每个类文件应包含最多一个公共类。私有类(只被单独一个公共类使用)可以和该公共类存放在同一个文件里。
视图文件应使用视图名称命名。例如,index视图在index.php文件里。视图文件是一个PHP脚本文件包含HTML和PHP代码,主要用来显示的。
配置文件可任意命名。配置文件是一个PHP脚本,其唯一目的就是要返回一个代表配置的关联数组。
目录(Directory)
Yii默认设定了被用于各种目的的一个目录集合.需要时,它们每个均可被自定义。
•
WebRoot/protected:这是applicationbasedirectory包括所有安全敏感的PHP脚本和数据文件。Yii有一个默认的别名为application代表此路径。这个目录和下面的一切文件目录,将得到保护不被网络用户访问。它可通过CWebApplication::basePath自定义。
•WebRoot/protected/runtime:此目录拥有应用程序在运行时生成的私有临时文件。这个目录必须可被Web服务器进程写。它可通过CApplication::runtimePath定制。•
WebRoot/protected/extensions:此目录拥有所有第三方扩展。它可通过CApplication::extensionPath定制。
•WebRoot/protected/modules:此目录拥有所有应用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:此目录包括发布的asset文件。一个asset文件是一个私有文件,可被发布来被Web用户访问。此目录必须Web服务进程可写。可通过CAssetManager::basePath定制。•
WebRoot/themes:此目录包括各种适用于应用程序的各种主题。每个子目录代表一个主题,名字为子目录名字。可通过CThemeManager::basePath定制。数据库(Database)
大多数Webapplication由数据库支撑的.Forbestpractice,我们建议数据库的表和列遵循如下命名约定.注意它们不是Yii必需的.
••
数据库的表和列均以小写格式命名.
名字中的单词应以下划线分隔(例如product_order).
•对于表的名字,可以使用单数或复数名字,但不要两者均采用.简化起见,我们推荐使用单数名字.•
表名字可以使用前缀,如tbl_.当一个application的表和其他application的表共存在同一个数据库时非常有用.使用不同的表前缀可以容易地将它们分开.
开源PHP开发框架Yii全方位教程(9)开发流程
•已经描述了Yii的基本概念,现在我们看看用Yii开发一个web程序的基本流程。前提是我这个程序我们已经做了需求分析和必要的设计分析。创建目录结构。参看建立第一个Yii应用写的yiic工具可以帮助我们快速完成这步。•配置应用。就是修改应用的配置文件。这步有可能会写一些application组件(例如:用户组件)•每种类型的数据都创建一个model类来管理。同样,yiic可以为我们需要的数据库表自动生成activerecord类。•每种类型的用户请求都创建一个controller类。依据实际的需求对用户请求进行分类。一般来说,如果一个model类需要用户访问,就应该对应一个controller类。yiic工具也能自动完成这步。•实现actions和相应的views。这是真正需要我们编写的工作。•在controller类里配置需要的actionfilters。••••如果需要主题功能,编写themes。如果需要internationalization国际化功能,编写翻译语句。使用caching技术缓存数据和页面。最后tuneup调整程序和发布。以上每个步骤,有可能需要编写测试案例来测试。DataAccessObjects(DAO)提供了一个通用的API以访问存储在不同DBMS中的数据.这样,数据库改变时可以无需修改访问数据库的代码.
YiiDAO建立于PHPDataObjects(PDO),它是一个为很多DBMS提供统一数据访问的扩展,支持MySQL,PostgreSQL等.因此,要使用YiiDAO,PDO扩展和指定的PDO数据库驱动(例如PDO_MYSQL)需要被安装.YiiDAO主要由下面四个类组成:
••••
1
CDbConnection:代表一个数据库连接.
CDbCommand:代表一个执行到数据库的SQL语句.
CDbDataReader:representsaforward-onlystreamofrowsfromaqueryresultset.CDbTransaction:representsaDB事务处理.
下面我们介绍在不同场景中YiiDAO的用法.
建立数据库连接
要建立一个数据库连接,需要创建一个CDbConnection实例并激活它.一个数据源名字(DSN)被用来指定数据库连接信息.可能也会需要用户名和密码来建立连接.若在连接数据库时出现错误,将会触发一个异常(例如.错误的DSN或无效的用户名/密码).$connection=new
CDbConnection($dsn,$username,$password);2345
//establishconnection.Youmaytry...catchpossibleexceptions$connection->active=true;......
$connection->active=false;//closeconnection
复制代码
DSN的格式取决于使用的PDO数据库驱动.通常一个DSN由PDO驱动名字,跟上一个冒号,以及驱动专有的连接句法组成.PDOdocumentation可以查看到完整信息.下面是一个常用的DSN格式列表:
•••••
6
SQLite:sqlite:/path/to/dbfile
MySQL:mysql:host=localhost;dbname=testdb
PostgreSQL:pgsql:host=localhost;port=5432;dbname=testdbSQLServer:mssql:host=localhost;dbname=testdbOracle:oci:dbname=//localhost:1521/testdb
因为CDbConnection扩展自CApplicationComponent,我们也可以使用它作为一个应用组件.我们可以在应用配置中如下配置db(或其他名字)应用组件,来实现此目的array(
7101112
......
'components'=>array(
......
'db'=>array(
'class'=>'CDbConnection',
'connectionString'=>'mysql:host=localhost;dbname=testdb',
131415161718)
),
),
'username'=>'root','password'=>'password',
'emulatePrepare'=>true,//neededbysomeMySQLinstallations
复制代码
19除非我们明确配置CDbConnection::autoConnect为false,否则我们就可以通过已自动被
激活的Yii::app()->db来访问此DB连接,通过这个方法,这个单一的DB连接可以在代码中的多处位置共享.
执行SQL语句
一旦一个数据库连接建立,就可以使用CDbCommand来执行SQL语句.可以通过调用CDbConnection::createCommand()来创建一个CDbCommand实例,参数是一个SQL语句:$command=$connection->createCommand($sql);
20//若需要,SQL语句可以被如下更新:21//$command->text=$newSQL;
复制代码
一个SQL语句被执行通过CDbCommand以下面两种方式:
•
execute():执行一个非查询的SQL语句,例如INSERT,UPDATE和DELETE.若成功执行,返回影响的记录数目.
•query():执行一条返回数据记录的SQL语句,例如SELECT.若成功,返回一个CDbDataReader实例.方便起见,一些queryXXX()方法也可以执行直接以返回查询结果.
22
若在SQL语句查询过程中出现错误,会触发一个异常.$rowCount=$command->execute();
executethenon-querySQL
23$dataReader=$command->query();24$rows=$command->queryAll();25$row=$command->queryRow();
//executeaquerySQL
//queryandreturnallrowsofresult//queryandreturnthefirstrowofresult
//
26$column=$command->queryColumn();//queryandreturnthefirstcolumnofresult27$value=$command->queryScalar();//queryandreturnthefirstfieldinthefirstrow
复制代码
28读取查询结果
在CDbCommand::query()产生CDbDataReader实例后,可以通过反复调用
CDbDataReader::read()来取得结果集的记录.也可以在PHP的foreach语言结构中使用
CDbDataReader以逐行检索记录.$dataReader=$command->query();
29//callingread()repeatedlyuntilitreturnsfalse30while(($row=$dataReader->read())!==false){...}31//usingforeachtotraversethrougheveryrowofdata32foreach($dataReaderas$row){...}
33//retrievingallrowsatonceinasinglearray34$rows=$dataReader->readAll();
复制代码
不同于query(),所有queryXXX()方法直接返回数据.例如,queryRow()返回一个数组,它代表着查询结果中的第一行记录.使用事务处理
当在一个应用执行一些查询时,每次读取和/或写入数据库中的信息,确保数据库不是只执行了一部分查询,这一点非常重要.一个事务处理,在Yii的代表是一个CDbTransaction实例,maybeinitiatedinthiscase:
••••
35
开始事务处理.
逐个执行查询.任何对数据库的更新对于外部都是不可见的.提交(Commit)事务.若事务成功执行,对数据库的更改变得可见.若其中一个查询失败,整个事务被回滚(rolleback).
上面的流程可以使用下面的代码来执行:$transaction=$connection->beginTransaction();
36try37{3839404142}
43catch(Exception$e)//anexceptionisraisedifaqueryfails44{4546}
$transaction->rollBack();
$connection->createCommand($sql1)->execute();$connection->createCommand($sql2)->execute();//....otherSQLexecutions$transaction->commit();
复制代码
47绑定参数
为了避免SQL注入攻击和改善执行反复的SQL语句的性能,你可以\"prepare\"一个SQL语句,其中的可选参数占位符在参数绑定过程中被实际的数据代替.
参数占位符可以是命名的(representedasuniquetokens)或未命名的(representedas
questionmarks).调用CDbCommand::bindParam()或CDbCommand::bindValue()以替换这些占位符为实际的参数.参数无需以引号环绕:底层数据库驱动为你完成.参数绑定必须在SQL语句被执行前完成.//anSQLwithtwoplaceholders\":username\"and\":email\"
48$sql=\"INSERTINTOtbl_user(username,email)VALUES(:username,:email)\";49$command=$connection->createCommand($sql);
50//replacetheplaceholder\":username\"withtheactualusernamevalue51$command->bindParam(\":username\52//replacetheplaceholder\":email\"withtheactualemailvalue53$command->bindParam(\":email\54$command->execute();
55//insertanotherrowwithanewsetofparameters
56$command->bindParam(\":username\57$command->bindParam(\":email\58$command->execute();
复制代码
59方法bindParam()和bindValue()非常类似.唯一不同点是前者以一个PHP变量引用
(reference)绑定一个参数,而后者以一个值绑定一个参数.对于大量参数(Forparametersthatrepresentlargeblockofdatamemory),为了性能考虑应当使用前者.
关于绑定参数的更详细信息,查看相关PHP文档.
绑定字段(BindingColumns)
当取出查询结果时,你也可以绑定字段为PHP变量以便它们被每次取出的相应值自动填充.$sql=\"SELECTusername,emailFROMtbl_user\";
60$dataReader=$connection->createCommand($sql)->query();61//bindthe1stcolumn(username)withthe$usernamevariable62$dataReader->bindColumn(1,$username);
63//bindthe2ndcolumn(email)withthe$emailvariable$dataReader->bindColumn(2,$email);65while($dataReader->read()!==false)66{6768}
//$usernameand$emailcontaintheusernameandemailinthecurrentrow
复制代码
69使用表前缀
从版本1.1.0开始,Yii为使用数据表前缀提供了完整的支持.表前缀是一个字符串,放置在数据
表名字的前面.主要用于共享主机环境,多个应用分享一个数据库,使用不同的表前缀以相互区分.例如,一个可以使用tbl_作为表前缀而另一个使用yii_.
要使用表前缀,配置CDbConnection::tablePrefix属性为你的表前缀.然后,在SQL语句中使用{{TableName}}指向表的名字,TableName指的是不加前缀的表名字.例如,若数据库中有一个名为tbl_user的表,同时tbl_被配置为表前缀,然后我们可以使用下面的代码查询用户:$sql='SELECT*FROM{{user}}';
70$users=$connection->createCommand($sql)->queryAll();
复制代码
开源PHP开发框架Yii全方位教程(11)ActiveRecord(AR)
1
虽然YiiDAO可以处理事实上任何数据库相关的任务,但编写一些通用的SQL语句来执行
CRUD操作(创建,读取,更新和删除)往往会让我们花费掉90%的时间。同时我们也很难维护这些PHP和SQL语句混合的代码。要解决这些问题,我们可以使用ActiveRecord。
ActiveRecord(AR)是一种流行的对象关系映射(ORM)技术。每个AR类代表一个数据表(或视图),数据表或者视图的字段作为AR类的属性,一个AR实例代表在表中的一行。常见的CRUD操作被作为AR类的方法执行。于是,我们可以使用更面向对象的方法处理我们的数据。例如,我们可以使用下面的代码在tbl_post表中插入一个新行:$post=newPost;
234
$post->title='samplepost';
$post->content='postbodycontent';$post->save();
复制代码
5
在下面我们将介绍如何设置AR和用它来执行CRUD操作。在下一小节我们将展示如何使
用AR处理数据库中的关联表。为了简单起见,在本节中,我们使用下面的数据库表作为例子。请注意,如果你使用MySQL数据库,您应该把下面SQL语句中的AUTOINCREMENT替换为AUTO_INCREMENT。CREATETABLEtbl_post(
6710);
idINTEGERNOTNULLPRIMARYKEYAUTOINCREMENT,titleVARCHAR(128)NOTNULL,contentTEXTNOTNULL,create_timeINTEGERNOTNULL
复制代码
11AR不是要解决所有与数据库相关的任务。它最好用于在PHP结构中模型化数据表和执行不
复杂的SQL语句.而YiiDAO应该用于复杂的情况下。
建立数据库连接
AR需要一个数据库连接以执行数据库相关的操作。默认情况下,应用中的db组件提供了CDbConnection实例作为我们需要的数据库连接。可参看如下配置:returnarray(
121314151617
'components'=>array(
'db'=>array(
'class'=>'system.db.CDbConnection','connectionString'=>'sqlite:path/to/dbfile',
//turnonschemacachingtoimproveperformance//'schemaCachingDuration'=>3600,
181920);
),
),
复制代码
由于ActiveRecord需要表的元数据来确定数据表的字段信息,这需要时间来读取和分析元数据。如果您的数据库结构是比较固定的,你应该打开缓存。CDbConnection::schemaCachingDuration属性为一个大于0的值。AR的支持受限于数据库管理系统。目前,只有以下数据库管理系统支持:
•••••
21
打开方法是配置
MySQL4.1或以后版本PostgreSQL7.3或以后版本SQLite2和3
MicrosoftSQLServer2000或以后版本Oracle
MicrosoftSQLServer自1.0.4版本提供支持;而对Oracle自1.0.5版本即提供支持。
如果你想使用其他组件而不是db,或者你使用AR访问多个数据库,你应该重写CActiveRecord::getDbConnection()。CActiveRecord类是所有AR类的基类。
有两种方法可以在AR模式下使用多种数据库系统。如果数据库的模式不同,您可以对getDbConnection()进行不同的实现,来创建不同的AR基类。否则,动态改变静态变量CActiveRecord::db是一个更好的主意。
定义AR类
为了使用一个数据表,我们首先需要扩展CActiveRecord来定义一个AR类。每个AR类代表一个数据库表,每个AR实例代表数据表中的一行。下面的代码介绍了要创建一个对应tbl_post表的AR类所需要的最少的代码。classPostextendsCActiveRecord
22{232425262728293031}
publicstaticfunctionmodel($className=CLASS){
returnparent::model($className);}
publicfunctiontableName(){
return’tbl_post’;}
复制代码
32因为AR类在很多地方被引用,我们可以导入包含AR类的整个目录,而不是逐个引入它们.
例如,若我们所有的AR类文件位于protected/models,我们可以如下配置:returnarray(
33343536);
),
’import’=>array(
’application.models.*’,
复制代码
37默认的,AR类的名字和数据表的名字相同.若它们不同需要重写tableName()方法.每个
AR类的model()方法都如此声明(会在稍后介绍).
要使用版本1.1.0引入的表前缀特征,AR的方法tableName()可以被如下重写,public
functiontableName()38{3940}
return’{{post}}’;
复制代码
41在这里,不再返回一个完整的表名,而是返回去掉了前缀的表名,并把它环绕在双弯曲括号中.
数据库表中一条数据的字段可以作为相应AR实例的属性被访问.例如,下面的代码设置了title字段(属性):$post=newPost;
42$post->title='asamplepost';
复制代码
43虽然我们没有在Post类中明确声明title属性,我们仍然可以在上面的代码中访问它.这
是因为title是表tbl_post中的字段,在PHP__get()魔术方法的帮助下,CActiveRecord可以将其作为一个属性来访问.若以同样方式尝试访问不存在的字段,一个异常将被抛出.
在此指南中,我们为所有的数据表和字段采取小写格式.这是因为在不同的DBMS中,对于大小写的敏感是不同的.例如,PostgreSQL默认对字段名字是大小写不敏感的,如果一个列名是大小写混合的,在查询条件中我们就必须把它引用起来.使用小写格式可以避免此问题.
AR依赖于数据表良好定义的主键.若一个表没有一个主键,需要相应的AR类通过重写primaryKey()方法来指定哪些字段应当为主键,publicfunctionprimaryKey()
44{4547
return'id';
//Forcompositeprimarykey,returnanarraylikethefollowing//returnarray(’pk1’,’pk2’);
48}
复制代码
49创建记录
要插入新的一行记录到数据表中,我们创建一个新的对应的AR类实例,设置和字段对应的属性的值,并调用save()方法来完成插入.$post=newPost;
50$post->title='samplepost';
51$post->content='contentforthesamplepost';52$post->createtime=time();53$post->save();
复制代码
54若表的主键是自增的,在插入后AR实例将包含一个更新后的主键.在上面的例子中,属性
id将映射为新插入的主键值,即使我们没有明确更改它.
若在表模式中,一个字段被定义为一些静态默认值(staticdefaultvalue)(例如一个字符串,一个数字),在这个AR实例被创建后,实例中相应的属性将自动有相应的默认值.改变此默认值的一个方式是在AR类中明确声明此属性:classPostextendsCActiveRecord
55{565758}
59$post=newPost;
60echo$post->title;//thiswoulddisplay:pleaseenteratitle
public$title=’pleaseenteratitle’;......
复制代码
61从版本1.0.2开始,在记录被保存前(插入或更新)一个属性可以赋值为CDbExpression类
型的值.例如,为了保存由MySQLNOW()函数返回的时间戳,我们可以使用下面的代码:$post=newPost;
62$post->createtime=newCDbExpression('NOW()');63//$post->createtime='NOW()';willnotworkbecause//’NOW()’willbetreatedasastring65$post->save();
复制代码
66AR允许我们执行数据库操作而无需编写麻烦的SQL语句,我们常常想要知道什么SQL
语句被AR在下面执行了.这可以通过打开Yii的记录(logging)特征来实现.例如,我们可以在应用配置中打开CWebLogRoute,我们将看到被执行的SQL语句被显示在每个页面的底
部.自版本1.0.5开始,我们可以在应用配置设置CDbConnection::enableParamLogging为true以便绑定到SQL语句的参数值也被记录.
读取记录
要读取数据表中的数据,我们可以调用下面其中一个find方法://findthefirstrowsatisfyingthe
specifiedcondition
67$post=Post::model()->find($condition,$params);68//findtherowwiththespecifiedprimarykey
69$post=Post::model()->findByPk($postID,$condition,$params);70//findtherowwiththespecifiedattributevalues
71$post=Post::model()->findByAttributes($attributes,$condition,$params);72//findthefirstrowusingthespecifiedSQLstatement73$post=Post::model()->findBySql($sql,$params);
复制代码
74在上面,我们使用Post::model()调用find方法.记得静态方法model()是每个AR类
所必需的.此方法返回一个AR实例,此实例被用来访问类级的方法(类似于静态的类方法).
若find方法找到一行记录满足查询条件,它将返回一个Post实例,此实例的属性包含表记录对应的字段值.然后我们可以读取被载入的值如同我们访问普通对象的属性,例如,echo$post->title;.
find方法将返回null若在数据库中没有找到满足条件的记录.
当调用find,我们使用$condition和$params来指定查询条件.这里$condition可以是字符串代表一个SQL语句中的WHERE子语句,$params是一个参数数组,其中的值应被绑定到$condition的占位符.例如,//findtherowwithpostID=10
75$post=Post::model()->find(’postID=:postID’,array(’:postID’=>10));
复制代码
76注意:在上面的例子中,对于某些DBMS我们可能需要转义对postID字段的引用.例如,
若我们使用PostgreSQL,我们需要写condition为\"postID\"=:postID,因为PostgreSQL默认情况下对待字段名字为大小写不敏感的.
我们也可以使用$condition来指定更复杂的查询条件.不使用字符串,我们让$condition为一个CDbCriteria实例,可以让我们指定条件而不限于WHERE子语句。例如,$criteria=new
CDbCriteria;
77$criteria->select='title';//onlyselectthe'title'column78$criteria->condition='postID=:postID';
79$criteria->params=array(':postID'=>10);
80$post=Post::model()->find($criteria);//$paramsisnotneeded
复制代码
81注意,当使用CDbCriteria作为查询条件,不再需要参数$params因为它可以在
CDbCriteria中被指定,如上所示.
一个可选的CDbCriteria方式是传递一个数组到find方法.数组的键和值分别对应于criteria的属性名字和值.上面的例子可以被如下重写,$post=Post::model()->find(array(
82838485));
'select'=>'title',
'condition'=>'postID=:postID','params'=>array(':postID'=>10),
复制代码
86当一个查询条件是关于匹配一些字段用指定的值,我们可以使用findByAttributes()。我们
让参数$attributes为一个数组,数组的值由字段名字索引.在一些框架中,此任务可以通过调用类似于findByNameAndTitle的方法来实现.虽然这个方法看起来很有吸引力,但它常常引起混淆和冲突,例如字段名字的大小写敏感性问题.
当多行记录满足指定的查询条件,我们可以使用下面的findAll方法将它们聚合在一起,每个都有它们自己的副本find方法。//findallrowssatisfyingthespecifiedcondition
87$posts=Post::model()->findAll($condition,$params);88//findallrowswiththespecifiedprimarykeys
$posts=Post::model()->findAllByPk($postIDs,$condition,$params);90//findallrowswiththespecifiedattributevalues
91$posts=Post::model()->findAllByAttributes($attributes,$condition,$params);92//findallrowsusingthespecifiedSQLstatement93$posts=Post::model()->findAllBySql($sql,$params);
复制代码
94若没有符合条件的记录,findAll返回一个空数组.不同于find方法,find方法会返回null.
除了上面所说的find和findAll方法,为了方便,下面的方法也可以使用://getthenumberof
rowssatisfyingthespecifiedcondition
95$n=Post::model()->count($condition,$params);
96//getthenumberofrowsusingthespecifiedSQLstatement97$n=Post::model()->countBySql($sql,$params);
98//checkifthereisatleastarowsatisfyingthespecifiedcondition99$exists=Post::model()->exists($condition,$params);
复制代码
100更新记录
一个AR实例被字段值填充后,我们可以改变它们并保存回它们到数据表
中.$post=Post::model()->findByPk(10);
101$post->title='newposttitle';
102$post->save();//savethechangetodatabase
复制代码
103如我们所见,我们使用相同的save()方法来执行插入和更新操作.若一个AR实例被使用
new操作符创建,调用save()将插入一行新记录到数据表中;如果AR实例是一些find或findAll方法调用的结果,调用save()将更新表中已存在的记录.事实上,我们可以使用CActiveRecord::isNewRecord来检查一个AR实例是否是新建的.
更新一行或多行表中的记录而不预先载入它们也是可能的.AR提供如下方便的类级的方法来实现它://updatetherowsmatchingthespecifiedcondition
104Post::model()->updateAll($attributes,$condition,$params);
105//updatetherowsmatchingthespecifiedconditionandprimarykey(s)106Post::model()->updateByPk($pk,$attributes,$condition,$params);107//updatecountercolumnsintherowssatisfyingthespecifiedconditions108Post::model()->updateCounters($counters,$condition,$params);
复制代码
109在上面,$attributes是一个值由字段名索引的数组;$counters是一个增加值由字段名索引
的数组;$condition和$params已在之前被描述.
删除记录
我们也可以删除一行记录若一个AR实例已被此行记录填充.$post=Post::model()->findByPk(10);
//assumingthereisapostwhoseIDis10
110$post->delete();//deletetherowfromthedatabasetable
复制代码
111注意,在删除后,此AR实例仍然未改变,但相应的表记录已经不存在了.
下面类级别的(class-level)方法被用来删除记录,而无需预先载入它们://deletetherowsmatching
thespecifiedcondition
112Post::model()->deleteAll($condition,$params);
113//deletetherowsmatchingthespecifiedconditionandprimarykey(s)114Post::model()->deleteByPk($pk,$condition,$params);
复制代码
115数据验证
当插入或更新一行记录,我们常常需要检查字段的值是否符合指定的规则.若字段值来自用户时这一点特别重要.通常我们永远不要信任用户提交的数据.
AR自动执行数据验证在save()被调用时.验证基于在AR类中的rules()方法中指定的规则。如何指定验证规则的更多信息,参考声明验证规则部分.下面是保存一条记录典型的工作流程:if($post->save())
116{117118}119else120{121122}
//dataisinvalid.callgetErrors()toretrieveerrormessages//dataisvalidandissuccessfullyinserted/updated
复制代码
123当插入或更新的数据被用户在HTML表单中提交,我们需要赋值它们到对象的AR属性.
我们可以这样做:$post->title=$_POST[’title’];
124$post->content=$_POST[’content’];125$post->save();
复制代码
126若有很多字段,我们可以看到一个很长的赋值列表.可以使用下面的attributes属性来缓解.
更多细节可以在安全属性设置部分和创建动作部分找到.//assume$_POST[’Post’]isanarrayof
columnvaluesindexedbycolumnnames127$post->attributes=$_POST[’Post’];128$post->save();
复制代码
对比记录
类似于表记录,AR实例由它们的主键值来被识别.因此,要对比两个AR实例,我们只需要对比它们的主键值,假设它们属于相同的CActiveRecord::equals().
AR类.然而,一个更简单的方式是调用
提示:不同于AR在其他框架的执行,Yii在其AR中支持多个主键.一个复合主键由两个或更多字段构成.对应的,主键值在Yii中表示为一个数组.TheprimaryKey属性给出一个AR实例的主键值。定制
CActiveRecord提供了一些占位符(placeholder)方法可被用来在子类中重写以自定义它的工作流程.
••••
beforeValidate和afterValidate:它们在验证执行之前/后被调用.beforeSave和afterSave:它们在保存一个AR实例之前/后被调用.beforeDelete和afterDelete:它们在一个AR实例被删除之前/后被调用.afterConstruct:这将在每个AR实例被使用new操作符创建之后被调用.
•beforeFind:它在一个ARfinder被用来执行一个查询之前被调用(例如find(),findAll()).从版本1.0.9可用.•
129
afterFind:它在每个AR实例被创建作为一个查询结果后被调用.
在AR中使用事务处理
每个AR实例包含一个名为dbConnection的属性,它是一个CDbConnection实例.这样我们在使用AR时就可以使用YiiDAO提供的事务处理特征:$model=Post::model();
130$transaction=$model->dbConnection->beginTransaction();131try132{133134135136137138139}
140catch(Exception$e)141{142143}
$transaction->rollBack();
//findandsavearetwostepswhichmaybeintervenedbyanotherrequest//wethereforeuseatransactiontoensureconsistencyandintegrity$post=$model->findByPk(10);$post->title=’newposttitle’;$post->save();
$transaction->commit();
复制代码
144命名空间
命名空间是一个命名的查询约束,可以和其他的命名空间组合,并可以用于数据库查询(anamedscoperepresentsanamedquerycriteriathatcanbecombinedwithothernamedscopesandappliedtoanactiverecordquery.)
命名空间被主要在CActiveRecord::scopes()方法中声明,格式是name-criteria对。下面的代
码在Post模型类中声明了两个命名空间,published和
CActiveRecord145{146147148149150151152153154155156157158159}
}
);
),),
'recently'=>array(
'order'=>'createtimeDESC','limit'=>5,
......
publicfunctionscopes(){
returnarray(
'published'=>array(
'condition'=>'status=1',
recently:classPostextends
复制代码
160每个命名空间被声明为一个被用来初始化一个CDbCriteria实例的数组。例如,命名空间
recently指定了order属性为create_timeDESC,limit属性为5,转换成的查询条件就是应当返回最近发表的5篇帖子。
命名空间大多数作为find方法的修改(modifier)来使用。几个命名空间可以连接在一起,则样可以得到一个更加有性的查询结果集。例如,要找到最近发表的帖子,我们可以使用下面的代码:$posts=Post::model()->published()->recently()->findAll();
复制代码
161通常命名空间必须出现在一个find方法的左边。其中的每一个提供了一个查询约束,和其
他约束相结合,includingtheonepassedtothefindmethodcall.这种网络结构很像在查询上加了一些过滤.
从版本1.0.6开始,命名空间也可以使用update和delete方法。例如,下面的代码将删除所有最近发表的帖子:Post::model()->published()->recently()->delete();
复制代码
162注意:命名空间只可以被作为类级别的方法使用。也就是说,此方法必须使用
ClassName::model()来调用它。
参数化命名空间(ParameterizedNamedScopes)
命名空间可以被参数化。例如,我们想要定制命名空间recently指定的帖子数目。要这样做,不是在CActiveRecord::scopes方法中声明命名空间,我们需要定义一个新的方法,它的名字和空间的名字相同:publicfunctionrecently($limit=5)
163{1165166167168169}
));
return$this;
$this->getDbCriteria()->mergeWith(array(
’order’=>’createtimeDESC’,’limit’=>$limit,
复制代码
170然后,我们可以使用下面的语句来检索
3个最近发表的帖
子:http://wenku.baidu.com/view/9748b29951e79b6802263b.html
复制代码
171若我们不使用上面的参数3,默认情况下我们将检索5个最近发表的内容。
默认命名空间
一个模型类可以有一个默认命名空间,它被应用于此模型所有的查询(包括relationalones)。例如,一个支持多种语言的网站只是以当前用户指定的语言来显示内容。因为有很多关于站点内容的查询,我们可以定义一个默认命名空间来解决这个问题。要这样做,我们重写CActiveRecord::defaultScope方法如下,classContentextendsCActiveRecord
172{173174175176177178179}
}
);
publicfunctiondefaultScope(){
returnarray(
’condition’=>\"language=’\".Yii::app()->language.\"’\
复制代码
180现在,若调用下面的方法将自动使用上面定义的查询条
件:$contents=Content::model()->findAll();
复制代码
注意默认命名空间只应用于SELECT查询。它忽视INSERT,UPDATE和DELETE查询。
开源PHP开发框架Yii全方位教程(12)片段缓存
1
片段缓存指缓存网页某片段。例如,如果一个页面在表中显示每年的销售摘要,我们可以存
储此表在缓存中,减少每次请求需要重新产生的时间。
要使用片段缓存,在控制器视图脚本中调用CController::beginCache()和CController::endCache()。这两个方法标识了被缓存的页面内容的开始和结束。类似datacaching,我们需要一个编号,识别被缓存的片段。...别的HTML内容...
2345
beginCache($id)){?>...被缓存的内容...
endCache();}?>...别的HTML内容...
复制代码
6
在上面的,如果beginCache()返回false,缓存的内容将此地方自动插入;否则,在if语句
内的内容将被执行并在endCache()触发时缓存。
缓存选项(CachingOptions)
当调用beginCache(),可以提供一个由缓存选项组成的数组作为第二个参数,以自定义片段缓存。事实上为了方便,beginCache()和endCache()方法是COutputCachewidget的包装。因此COutputCache的所有属性都可以在缓存选项中初始化。
有效期(Duration)
也许是最常见的选项是duration,指定了内容在缓存中多久有效。和CCache::set()过期参数有点类似。下面的代码缓存内容片段最多一小时:...其他HTML内容...
7
beginCache($id,array('duration'=>3600))){?>...被缓存的内容...
endCache();}?>
10...其他HTML内容...
复制代码
11如果我们不设定期限,它将默认为60,这意味着60秒后缓存内容将无效。
依赖(Dependency)
像datacaching,被缓存的内容片段也可以有依赖。例如,文章的内容被显示取决于文章是否
被修改。
要指定一个依赖,我们设置了dependency选项,可以是一个实现ICacheDependency的对象或可用于生成依赖对象的配置数组。下面的代码指定片段内容取决于lastModified列的值是否变化:...其他HTML内容...
12beginCache($id,array('dependency'=>array(1314
'class'=>'system.caching.dependencies.CDbCacheDependency','sql'=>'SELECTMAX(lastModified)FROMPost')))){?>
15...被缓存的内容...
16endCache();}?>17...其他HTML内容...
复制代码
变化(Variation)
缓存的内容可根据一些参数变化。例如,每个人的档案都不一样。缓存的档案内容将根据每个人ID变化。这意味着,当调用beginCache()时将用不同的ID。
COutputCache内置了这一特征,程序员不需要编写根据ID变动内容的模式。以下是摘要。
•varyByRoute:设置此选项为true,缓存的内容将根据route变化。因此,每个控制器和行动的组合将有一个单独的缓存内容。•
varyBySession:设置此选项为true,缓存的内容将根据sessionID变化。因此,每个用户会话可能会看到由缓存提供的不同内容。
•varyByParam:设置此选项的数组里的名字,缓存的内容将根据GET参数的值变动。例如,如果一个页面显示文章的内容根据id的GET参数,我们可以指定varyByParam为array('id'),以使我们能够缓存每篇文章内容。如果没有这样的变化,我们只能能够缓存某一文章。•
18
varyByExpression:设置此选项为一个PHP表达式,我们可以让缓存的内容根据此PHP
表达式的结果而变化。此选项自版本1.0.4可用。
请求类型(RequestTypes)
有时候,我们希望片段缓存只对某些类型的请求启用。例如,对于某张网页上显示表单,我们只想要缓存表单当其被初次访问时(通过GET请求)。任何随后显示(通过POST请求)的表单将不被缓存,因为表单可能包含用户输入。要做到这一点,我们可以指定requestTypes选项:...
其他HTML内容...
19beginCache($id,array('requestTypes'=>array('GET')))){?>20...被缓存的内容...
21endCache();}?>22...其他HTML内容...
复制代码
23嵌套缓存(NestedCaching)
片段缓存可以嵌套。就是说一个缓存片段附在一个更大的片段缓存里。例如,评论缓存在内部片段缓存,而且它们一起在外部缓存中在文章内容里缓存。...其他HTML内容...
24beginCache($id1)){?>25...外部被缓存内容...262728
beginCache($id2)){?>...内部被缓存内容...
endCache();}?>
29...外部被缓存内容...
30endCache();}?>31...其他HTML内容...
复制代码
嵌套缓存可以设定不同的缓存选项。例如,在上面的例子中内部缓存和外部缓存可以设置时间长短不同的持续值。当数据存储在外部缓存无效,内部缓存仍然可以提供有效的内部片段。然而,反之就不行了。如果外部缓存包含有效的数据,它会永远保持缓存副本,即使内部缓存中的内容已经过期。
开源PHP开发框架Yii全方位教程(13)页面缓存
页面缓存指的是缓存整个页面的内容。页面缓存可以发生在不同的地方。例如,通过选择适当的页面头,客户端的浏览器可能会缓存网页浏览有限时间。Web应用程序本身也可以在缓存中存储网页内容。在本节中,我们侧重于后一种办法。
页面缓存可以被看作是片段缓存的一个特例。由于网页内容是往往通过应用一个布局到一个视图来生成,如果我们只是简单的在布局中调用beginCache()和endCache(),将无法正常工作。这是因为布局在CController::render()方法里的加载是在页面内容产生之后。缓存整个页面,我们应该跳过产生网页内容的动作执行。我们可以使用COutputCache作为动作过滤器来完成这一任务。下面的代码演示如何配置缓存过滤器:
123456710}
);
),
publicfunctionfilters(){
returnarray(
array(
'COutputCache','duration'=>100,
'varyByParam'=>array('id'),
复制代码
上述过滤器配置会使过滤器应用于控制器中的所有行动。通过使用+操作符,我们可能会它应用于一个或几个action。更多的细节中可以看过滤器。
开源PHP开发框架Yii全方位教程(14)动态内容
1
当使用片段缓存或页面缓存时,我们常常遇到的这样的情况:整个部分的输出除了个别地方
都是相对静态的。例如,帮助页可能会显示静态的帮助信息,而将当前登录的用户的名称显示在页面顶部。
解决这个问题,我们可以根据用户名变化缓存内容,但是这将是我们宝贵缓存空间一个巨大的浪费,因为缓存除了用户名其他大部分内容是相同的。我们还可以把网页切成几个片段并分别缓存,但这种情况会使页面和代码变得非常复杂。更好的方法是使用由CController提供的动态内容(dynamiccontent)特征。
动态内容是指片段输出即使是在片段缓存包括的内容中也不会被缓存。即使是包括的内容是从缓存中取出,为了使动态内容在所有时间是动态的,每次都得重新生成。出于这个原因,我们要求动态内容通过一些方法或函数生成。
调用CController::renderDynamic()在你想的地方插入动态内容。...别的HTML内容...
234567
beginCache($id)){?>...被缓存的片段内容...
renderDynamic($callback);?>...被缓存的片段内容...
endCache();}?>...别的HTML内容...
复制代码
在上面的,$callback指的是有效的PHP回调。它可以是指向当前控制器类的方法或者全局函数的字符串名。它也可以是一个数组名指向一个类的方法。任何传递到renderDynamic()方法中的额外参数将被传递给回调(callback)。回调将返回动态内容而不是显示它。
开源PHP开发框架Yii全方位教程(15)使用扩展
1
适用扩展通常涉及以下三个步骤:
1.从Yii的扩展库下载扩展.
2.解压到应用程序的基目录的子目录extensions/xyz下,这里的xyz是扩展的名称.3.导入,配置和使用扩展.
每个扩展都有一个唯一的标识的名字.把一个扩展命名为xyz,我们可以使用路径别名ext.xyz定位到包含了xyz所有文件的基目录.
不同的扩展有着不同的导入,配置,使用要求.以下是我们通常会用到扩展的场景,按照他们在概述中的描述分类.
Zii扩展
在开始介绍第三方扩展的用法之前,我们首先介绍Zii扩展库,它是由Yii开发团队开发的扩展集合,自Yii版本1.1.0开始包含在发布中。Zii库在Google代码上的名字是zii。
当使用一个Zii扩展时,必须以格式zii.path.to.ClassName指向对应的类。这里的根别名zii被预定义在Yii中。它指向Zii库的根目录。例如,要使用CGridView,当引用此扩展时,我们应当在一个视图中使用如下代码:$this->widget(’zii.widgets.grid.CGridView’,array(
23
));
’dataProvider’=>$dataProvider,
复制代码
4
应用组件
要使用应用组件,首先我们需要添加一个新条目到应用配置的components属性,如下所示:returnarray(
56710111213
),
),
//其他部件配置//'preload'=>array('xyz',...),'components'=>array(
'xyz'=>array(
'class'=>'ext.xyz.XyzClass','property1'=>'value1','property2'=>'value2',
14);
复制代码
15然后,我们可以在任何地方通过使用Yii::app()->xyz来访问组件.部件将会被惰性创建(就是,
仅当它第一次被访问时创建.),除非我们把它配置到preload属性里.
行为(Behavior)
Behavior可以使用在各种组件中。它的用法涉及两个步骤。第一部,一个行为被附加到一个目标组件。第二步,通过目标组件调用行为方法。例如://$nameuniquelyidentifiesthebehaviorin
thecomponent
16$component->attachBehavior($name,$behavior);17//test()isamethodof$behavior18$component->test();
复制代码
19经常,一个行为被附加到一个组件,使用一个配置化的方式而不是调用attachBehavior方
法。例如,要附加一个行为到一个应用组件,我们可以使用下面的应用配置:returnarray(
2021222324252627282930313233);
),
),//....
),
),
’components’=>array(’db’=>array(
’class’=>’CDbConnection’,’behaviors’=>array(
’xyz’=>array(
’class’=>’ext.xyz.XyzBehavior’,’property1’=>’value1’,’property2’=>’value2’,
复制代码
34上面的代码附加
xyz行为到db应用组件。我们可以这样做,因为
CApplicationComponent定义了一个名为behaviors的属性。通过使用一个行为配置列表设置此属性,组件当它被初始化时将附加对应的行为。
对于CController,CFormModel和CActiveRecord这些经常被扩展的类,可以重写它们的behaviors()方法来附加行为。当这些类初始化时,自动将在此方法中声明的行为附加到类中。
例如,publicfunctionbehaviors()
35{3637383940414243}
);
),returnarray(
’xyz’=>array(
’class’=>’ext.xyz.XyzBehavior’,’property1’=>’value1’,’property2’=>’value2’,
复制代码
44部件
部件主要用在视图里.假设部件类XyzClass属于xyz扩展,我们可以如下在视图中使用它://
组件不需要主体内容
45widget('ext.xyz.XyzClass',array(47
'property1'=>'value1','property2'=>'value2'));?>
48//组件可以包含主体内容
49beginWidget('ext.xyz.XyzClass',array(5051
'property1'=>'value1','property2'=>'value2'));?>
52...组件的主体内容...
53endWidget();?>
复制代码
54动作
动作被控制器用于响应指定的用户请求.假设动作的类XyzClass属于xyz扩展,我们可以在我们的控制器类里重写CController::actions方法来使用它:classTestControllerextends
CController55{56575859606162
publicfunctionactions(){
returnarray(
'xyz'=>array(
'class'=>'ext.xyz.XyzClass','property1'=>'value1','property2'=>'value2',
63656667}
}
);
),
//其他动作
复制代码
68然后,我们可以通过路由test/xyz来访问.
过滤器
过滤器也被控制器使用。过滤器主要用于当其被动作操纵时预处理,后期处理(pre-andpost-process)用户的请求。假设过滤器的类XyzClass属于xyz扩展,我们可以在我们的控制器类里重写CController::filters方法来使用它:classTestControllerextendsCController
69{707172737475767778798081}
}
);
),
//其他过滤器
publicfunctionfilters(){
returnarray(
array(
'ext.xyz.XyzClass','property1'=>'value1','property2'=>'value2',
复制代码
82在上述代码中,我们可以在数组的第一个元素离使用+或者-操作符来限定过滤器只在那
些动作中生效.更多信息,请参照文档的CController.
控制器
控制器提供了一套可以被用户请求的动作。为了使用一个控制器扩展,我们需要在应用配置里设置CWebApplication::controllerMap属性:returnarray(
83848586
'controllerMap'=>array(
'xyz'=>array(
'class'=>'ext.xyz.XyzClass','property1'=>'value1',
878091);
),
),
'property2'=>'value2',
//其他控制器
复制代码
92然后,一个在控制里的a行为就可以通过路由xyz/a来访问了.
校验器
校验器主要用在模型类(继承自CFormModel或者CActiveRecord)中.假设校验器类XyzClass属于xyz扩展,我们可以在我们的模型类中通过CModel::rules重写CModel::rules来使用它:classMyModelextendsCActiveRecord//orCFormModel
93{9495969799100101102103104105106}
}
);
),
//其他校验规则
publicfunctionrules(){
returnarray(
array(
'attr1,attr2','ext.xyz.XyzClass','property1'=>'value1','property2'=>'value2',
复制代码
107控制台命令
控制台命令扩展通常使用一个额外的命令来增强yiic的功能.假设一个控制台命令XyzClass属于xyz扩展,我们可以通过设定控制台应用的配置来使用它:returnarray(
108109110111112113
),
'commandMap'=>array(
'xyz'=>array(
'class'=>'ext.xyz.XyzClass','property1'=>'value1','property2'=>'value2',
114115116);
),
//其他命令
复制代码
然后,我们就能使用配备了额外命令xyz的yiic工具了.
注意:控制台应用通常使用了一个不同于Web应用的配置文件.如果使用了yiicwebapp命令创建了一个应用,这样的话,控制台应用的protected/yiic的配置文件就是protected/config/console.php了,而Web应用的配置文件则是protected/config/main.php.一般组件
使用一个一般组件,我们首先需要通过使用Yii::import('ext.xyz.XyzClass');
来包含它的类文件.然后,我们既可以创建一个类的实例,配置它的属性,也可以调用它的方法.我们还可以创建一个新的子类来扩展它。
开源PHP开发框架Yii全方位教程(16)创建扩展
由于扩展意味着是第三方开发者使用,需要一些额外的努力去创建它。以下是一些一般性的指导原则:
•扩展最好是自给自足。也就是说,其外部的依赖应是最少的。如果用户的扩展需要安装额外的软件包,类或资源档案,这将是一个头疼的问题。••••
1
文件属于同一个扩展的,应组织在同一目录下,目录名用扩展名称。扩展里面的类应使用一些单词字母前缀,以避免与其他扩展命名冲突。
扩展应该提供详细的安装和API文档。这将减少其他开发员使用扩展时花费的时间和精力。扩展应该用适当的许可。如果您想您的扩展能在开源和闭源项目中使用,你可以考虑使用许
可证,如BSD的,麻省理工学院等,但不是GPL的,因为它要求其衍生的代码是开源的。
在下面,我们根据overview中所描述的分类,描述如何创建一个新的扩展。当您要创建一个主要用于在您自己项目的组件,这些描述也适用。
ApplicationComponent(应用组件)
一个applicationcomponent应实现接口IApplicationComponent或继承自
CApplicationComponent。主要需要实现的方法是IApplicationComponent::init,部件在此执行一些初始化工作。此方法在部件创建和属性值(在applicationconfiguration里指定的)被赋值后调用。
默认情况下,一个应用程序部件创建和初始化,只有当它首次访问期间要求处理。如果一个应用程序部件需要在应用程序实例被创建后创建,它应要求用户在CApplication::preload的属性中列出他的编号。
行为(Behavior)
要创建一个behavior,必须实现IBehavior接口。方便起见,Yii提供了一个基础类CBehavior,它已经实现了这个接口并提供了一些额外的方便方法。子类主要需要实现为要附加的组件可用的额外方法。
当为CModel和CActiveRecord开发行为时,可以分别扩展CModelBehavior和
CActiveRecordBehavior。这些基础类专为CModel和CActiveRecord提供了额外的特征。例如,CActiveRecordBehavior类实现一些方法集合来响应ActiveRecord对象里唤起的生命周期事件。子类可以重写这些方法,让自定义的代码参与到AR的生命周期里。
下面的代码展示了一个ActiveRecord行为的例子。当这个行为被附加到一个AR对象,当AR对象通过调用save()被保存时,它将自动以当前时间戳设置create_time和update_time属性。classTimestampBehaviorextendsCActiveRecordBehavior
2
{
3456710}
publicfunctionbeforeSave($event){
if($this->owner->isNewRecord)
$this->owner->createtime=time();else
$this->owner->updatetime=time();
}
复制代码
11部件
widget应继承CWidget或其子类。
最简单的方式建立一个新的widget是扩展一个现成的widget和重载它的方法或改变其默认的属性值。例如,如果您想为CTabView使用更好的CSS样式,当使用widget时,您可以配置其CTabView::cssFile属性。您还可以扩展CTabView如下,让您在使用widget时,不再需要配置属性。classMyTabViewextendsCTabView
12{13141516171819202122}
}
}
parent::init();publicfunctioninit(){
if($this->cssFile===null){
$file=dirname(FILE).DIRECTORY_SEPARATOR.'tabview.css';$this->cssFile=Yii::app()->getAssetManager()->publish($file);
复制代码
23在上面,我们重载CWidget::init方法和指定CTabView::cssFile的URL到我们的新的默认
CSS样式如果此属性未设置时。我们把新的CSS样式文件和MyTabView类文件放在相同的目录下,以便它们能够封装成扩展。由于CSS样式文件不是通过Web访问,我们需要发布作为一项asset资源。
要从零开始创建一个新的widget,我们主要是需要实现两个方法:CWidget::init和CWidget::run。第一种方法是当我们在视图中使用$this->beginWidget插入一个widget时被调用,第二种方法在$this->endWidget被调用时调用。如果我们想在这两个方法调用之间捕捉和处理显示的内容,我们可以在CWidget::init开始outputbuffering并在CWidget::run中检索
缓冲后的输出以作进一步处理。
在网页中使用的widget需要引入CSS,Javascript或其他资源文件。我们称这些文件为assets,因为它们和widget类文件在一起,而且通常Web用户无法访问。为了使这些档案通过Web访问,我们需要用CWebApplication::assetManager发布它们,例如上述代码段所示。此外,如果我们想包括CSS或JavaScript文件在当前的网页,我们需要使用CClientScript注册它:classMyWidgetextendsCWidget
24{2526272829303132}
}
protectedfunctionregisterClientScript(){
//...publishCSSorJavaScriptfilehere...$cs=Yii::app()->clientScript;$cs->registerCssFile($cssFile);$cs->registerScriptFile($jsFile);
复制代码
33一个widget也可能有自己的视图文件。如果是这样,在包含widget类文件的目录下创建
一个目录views,并把所有的视图文件放里面。在widget类中使用$this->render('ViewName')来渲染widget视图,类似于我们在控制器里做的。
Action(动作)
action应扩展自CAction或者其子类。action要实现的主要方法是IAction::run。
Filter(过滤器)
filter应扩展自CFilter或者其子类。filter要实现的主要方法是CFilter::preFilter和CFilter::postFilter。前者是在action之前被执行,而后者是在之后。classMyFilterextendsCFilter
34{35363738394041424344}
}}
protectedfunctionpostFilter($filterChain){
//logicbeingappliedaftertheactionisexecutedprotectedfunctionpreFilter($filterChain){
//logicbeingappliedbeforetheactionisexecutedreturntrue;//falseiftheactionshouldnotbeexecuted
复制代码
45参数$filterChain的类型是CFilterChain,它包含了当前被过滤的action的相关信息。
Controller(控制器)
controller要作为扩展需扩展自CExtController,而不是CController。主要的原因是因为CController假设控制器视图文件位于application.views.ControllerID下,而CExtController认定视图文件在views目录下,也是包含控制器类目录的一个子目录。因此,很容易重新分配控制器,因为它的视图文件和控制类是在一起的。
Validator(验证)
Validator需继承CValidator和实现CValidator::validateAttribute方法。classMyValidatorextends
CValidator46{47484950515253}
}
protectedfunctionvalidateAttribute($model,$attribute){
$value=$model->$attribute;if($valuehaserror)
$model->addError($attribute,$errorMessage);
复制代码
54ConsoleCommand(控制台命令)
consolecommand应继承CConsoleCommand和实现CConsoleCommand::run方法。或者,我们可以重载CConsoleCommand::getHelp来提供一些更好的有关帮助命令。classMyCommand
extendsCConsoleCommand55{5657585960616263}
}}
publicfunctiongetHelp(){
return'Usage:howtousethiscommand';publicfunctionrun($args){
//$argsgivesanarrayofthecommand-lineargumentsforthiscommand
复制代码
Module(模块)
请参阅modules一节了解如何创建一个模块。
一般准则制订一个模块,它应该是的。模块所使用的资源文件(如CSS,JavaScript,图片),应该和模块一起分发。还有模块应发布它们,以便可以Web访问它们。GenericComponent(通用组件)
开发一个通用组件扩展类似写一个类。再次强调,该组件还应该自足,以便它可以很容易地被其他开发者使用。
开源PHP开发框架Yii全方位教程(17)使用第三方库
Yii是精心设计,使第三方库可易于集成,进一步增强Yii的功能。当在一个项目中使用第三方库,程序员往往遇到关于类命名和文件包含的问题。因为所有Yii类以C字母开头,这就减少可能会出现的类命名问题;而且因为Yii依赖SPLautoload执行类文件包含,如果他们使用相同的自动加载功能或PHP包含路径包含类文件,它可以很好地结合。下面我们用一个例子来说明如何在一个Yii应用中使用Zend_Search_Lucene组件。
首先,假设protected是applicationbasedirectory,我们提取ZendFramework的发布文件到protected/vendors目录。假设protected是应用的基础目录。确认protected/vendors/Zend/Search/Lucene.php文件存在。第二,在一个controller类文件的开始,加入以下行:
12
Yii::import('application.vendors.*');require_once('Zend/Search/Lucene.php');
Zendframework的
复制代码
上述代码包含类文件Lucene.php。因为我们使用的是相对路径,我们需要改变PHP的包含路径,以使文件可以正确定位。这是通过在require_once之前调用Yii::import完成的。一旦上述设立准备就绪后,我们可以在controlleraction里使用Lucene类,类似如下:
34
$lucene=newZend_Search_Lucene($pathOfIndex);$hits=$lucene->find(strtolower($keyword));
复制代码
开源PHP开发框架Yii全方位教程(18)定义fixture
自动化测试需要被执行很多次。要确保测试过程是重复的,我们想要运行测试以一些已知的状态,称为fixture。例如,要测试blog应用的文章创建功能,每次我们运行这个测试,存储文章相关数据的表(例如Post表,Comment表)应被还原为一些固定的状态。PHPUnit文档已经对通用fixture建立做了很好的描述。在这一小节中,我们主要描述如何建立数据库fixtures,如我们在例子中讲到的。
在测试数据库驱动的Web应用时,建立数据库fixtures可能是最耗时的部分之一。Yii引入了CDbFixtureManager应用组件来缓解这个问题。当运行一个测试集合时,它基本上执行如下工作:
•••
1
在所有测试运行之前,它重设所有与测试相关的表为一些已知的状态。在一个单独的测试方法运行之前,它重设指定的表为一些已知的状态。
在一个测试方法执行过程中,itprovidesaccesstotherowsofthedatathatcontributetothe
fixture.
为了使用CDbFixtureManager,我们在应用配置中如下设置它,returnarray(
234567
);
),
),
’components’=>array(
’fixture’=>array(
’class’=>’system.test.CDbFixtureManager’,
复制代码
8
然后我们提供fixture数据,位于目录protected/tests/fixtures中。通过在应用配置中设置
CDbFixtureManager::basePath,可以将这个目录设置成别的目录。fixture数据被组织为一个PHP文件集合,称为fixture文件。Eachfixturefilereturnsanarrayrepresentingtheinitialrowsofdataforaparticulartable.文件的名字和表的名字相同。下面是一个例子,Post表的fixture数据存储在一个名为Post.php的文件中:91011121314151617181920
),
’sample2’=>array(
’title’=>’testpost2’,
’content’=>’testpostcontent2’,’createTime’=>1230952287,’authorId’=>1,
returnarray(
’sample1’=>array(
’title’=>’testpost1’,
’content’=>’testpostcontent1’,’createTime’=>1230952187,’authorId’=>1,
2122);
),
复制代码
如我们看到的,在上面两行数据被返回。每行记录表示为一个关联数组,它的键是字段名,它的值是对应的字段值。此外,每行记录由一个字符串(例如sample1,sample2)索引,这个字符串称为rowalias。稍后当我们编写测试脚本时,我们可以方便的根据它的别名指向一行记录。在下个小节,我们将详细描述。
你可能注意到在上面的fixture中我们不指定id字段值。这是因为id字段被定义为一个自增的主键,当我们插入新记录时,它的值被填充。
当CDbFixtureManager被首次引用时,它将检查每个fixture文件并使用它来重设对应的表。它通过截取表,重设表的自增主键的顺序值来重设表,然后插入fixture文件中的记录到表中。有时,wemaynotwanttoreseteverytablewhichhasafixturefilebeforewerunasetoftests,因为重设太多的fixture文件将花费很长时间。这时,我们可以编写一个PHP脚本以一个定制的方式来做初始化的工作。脚本应当被保存为init.php,放置在含有其他fixture文件的相同目录。当CDbFixtureManager探测到这个文件存在,它将执行这个脚本而不是重设每个表。
若你不喜欢重设一个表的默认方式,例如截取它并插入fixture文件中的数据,改变它也是可能的。这时,我们可以为指定的fixture文件编写一个初始化脚本。这个脚本必须命名为表名字跟上.init.php。例如,Post表的初始化脚本将是Post.init.php。当CDbFixtureManager看到这个脚本,它将执行这个脚本而不是使用默认的方式来重设数据表。
太多fixture文件将极大增加测试时间。由于这个原因,你应当只为那些内容在测试中改变的表提供fixture文件。那些用于查询的表不发生改变,因此无需fixture文件。
在下面两个小节,我们将描述如何在单元测试和功能测试中使用由CDbFixtureManager管理的fixtures。
开源PHP开发框架Yii全方位教程(19)单元测试
•因为Yii测试框架基于PHPUnit,推荐你首先阅览PHPUnit文档,对如何编写一个单元测试有一个基本理解。我们总结在Yii中编写一个单元测试的基本原则如下:
一个单元测试编写为一个类XyzTest,它扩展自CTestCase或CDbTestCase,Xyz代表被测试的类。例如,要测试Post类,根据约定我们命名对应的测试单元为PostTest。基础类CTestCase是为通用单元测试准备的,而CDbTestCase适合测试activerecord模型类。因为PHPUnit_Framework_TestCase是这两个类的根类,我们可以使用从这个类继承而来的所有方法。
•单元测试类被保存为一个名为XyzTest.php的PHP文件。根据约定,单元测试文件必须被保存到目录protected/tests/unit中。
•测试类主要包含名为testAbc的测试方法集合,Abc通常是被测试的类方法的名字。•一个测试方法经常包含asequenceofassertionstatements(e.g.assertTrue,assertEquals)whichserveascheckpointsonvalidatingthebehaviorofthetargetclass.下面我们主要描述如何为activerecord模型类编写单元测试。我们将扩展CDbTestCase类编写测试类,因为它提供了之前我们介绍的数据库fixture支持。
假设我们想要测试blog演示中的Comment模型类,我们首先创建CommentTest类并保存为protected/tests/unit/CommentTest.php:
12345678
}
);......
classCommentTestextendsCDbTestCase{
public$fixtures=array(
’posts’=>’Post’,
’comments’=>’Comment’,
复制代码
在这个类中,我们指定成员变量fixtures是一个数组,指示在测试中使用那些fixtures。数组代表了一个从fixture名字到模型类名字或fixture表名字(例如从fixture名字posts到模型类Post)之间的映射。注意当映射到fixture表名字时,我们应当在表名字前加前缀冒号(例如:Post)来和模型类名字相区别。当使用模型类名字时,对应的表被看作fixture表。我们之前讲过,当每次一个测试方法被执行时,fixture表将被重设到一些已知的状态。fixture名字允许我们以一个方便的方式来访问测试方法中的fixture数据。如下代码展示了它的典型用法:
9
//returnallrowsinthe’Comment’fixturetable
10$comments=$this->comments;
11//returntherowwhosealiasis’sample1’inthe‘Post‘fixturetable12$post=$this->posts[’sample1’];
13//returntheARinstancerepresentingthe’sample1’fixturedatarow
14$post=$this->posts(’sample1’);
复制代码
若一个fixture被声明为使用它的表名字(例如’posts’=>’:Post’),然后上面第三个用法是无效的,因为我们没有这个表关联的模型类的信息。
接下来,我们编写testApprove方法来测试Comment模型类中的approve方法。代码非常明了:我们首先插入一条评论,状态是等待审核;然后我们通过在数据库中检索它来验证这条评论处于等待审核状态;组后我们调用approve方法并验证状态如预期的那样改变。
15publicfunctiontestApprove()16{171819202122232425262728
//insertacommentinpendingstatus$comment=newComment;$comment->setAttributes(array(
’content’=>’comment1’,
’status’=>Comment::STATUSPENDING,’createTime’=>time(),’author’=>’me’,
’email’=>’me@example.com’,
’postId’=>$this->posts[’sample1’][’id’],),false);
$this->assertTrue($comment->save(false));//
verify
the
comment
is
in
pending
status
$comment=Comment::model()->findByPk($comment->id);2930313233343536}
$this->assertTrue($commentinstanceofComment);
$this->assertEquals(Comment::STATUSPENDING,$comment->status);//callapprove()andverifythecommentisinapprovedstatus$comment->approve();
$this->assertEquals(Comment::STATUSAPPROVED,$comment->status);$comment=Comment::model()->findByPk($comment->id);
$this->assertEquals(Comment::STATUSAPPROVED,$comment->status);
复制代码
开源PHP开发框架Yii全方位教程(20)功能测试
在阅读这一小节之前,推荐你首先阅读Selenium文档和PHPUnit文档。我们总结在Yii中编写一个功能测试的基本原则如下:
•类似于单元测试,一个功能测试编写为一个类XyzTest,它扩展自CWebTestCase,Xyz代表被测试的类。因为PHPUnit_Extensions_SeleniumTestCase是CWebTestCase的根类,我们调用从根类继承而来的所有方法。•
功能测试类被保存到名为XyzTest.php的PHP文件中。根据约定,功能测试文件必须保存到目录protected/tests/functional中。
•测试类主要包含名为testAbc的测试方法集合,Abc同时是被测试的功能的名字。例如,要测试用户登录特征,我们可以有一个测试方法名为testLogin。•
AtestmethodusuallycontainsasequenceofstatementsthatwouldissuecommandstoSeleniumRCtointeractwiththeWebapplicationbeingtested.ItalsocontainsassertionstatementstoverifythattheWebapplicationrespondsasexpected.
1
在描述如何使用功能测试之前,让我们看一下由yiicwebapp命令产生的WebTestCase.php文件。这个文件定义了WebTestCase,它充当所有功能测试类的基础类。define(’TESTBASE
URL’,’http://localhost/yii/demos/blog/index-test.php/’);2345671011121314}
}......
classWebTestCaseextendsCWebTestCase{
/**
*Setsupbeforeeachtestmethodruns.
*ThismainlysetsthebaseURLforthetestapplication.*/
protectedfunctionsetUp(){
parent::setUp();
$this->setBrowserUrl(TEST_BASE_URL);
复制代码
15类WebTestCase主要设置被测试页面的基础URL。稍后在测试方法中,我们可以使用相
对URL来指定哪些页面被测试。
我们也应当注意在基础测试URL中,我们使用index-test.php而不是index.php作为入口文件。index-test.php和index.php唯一的不同是前者使用test.php作为应用配置文件,而后者使用main.php作为配置文件。
现在我们描述在blog演示中如何测试展示一篇文章的功能。我们首先编写测试类如下。
notingthatthetestclassextendsfromthebaseclasswejustdescribed:classPostTestextends
WebTestCase16{17181920212223242526272829}
}......{
$this->open(’post/1’);
//verifythesampleposttitleexists
$this->assertTextPresent($this->posts[’sample1’][’title’]);//verifycommentformexists
$this->assertTextPresent(’LeaveaComment’););
publicfunctiontestShow()public$fixtures=array(
’posts’=>’Post’,
复制代码
类似于编写一个单元测试,我们声明这个测试使用的fixture,这里我们指示使用Postfixture。在testShow测试方法中,我们首先指示SeleniumRC打开URLpost/1,注意这是一个相对URL,完整的URL应当是我们在基础中设置的基础URL再加上相对URL(例如http://localhost/yii/demos/blog/index-test.php/post/1)。然后我们验证我们可以找到sample1文章的标题出现在当前网页。我们也可以验证网页包含了文本LeaveaComment。
在运行功能测试之前,Selenium-RC服务器必须开启。可以通过在Selenium服务器安装目录中执行命令java-jarselenium-server.jar实现。
开源PHP开发框架Yii全方位教程(21)自动生成代码
1
自动生成代码从版本1.1.2开始,Yii装备了一个基于web的代码生成工具,叫做Gii。它
替代之前的yiicshell生成工具(它运行在命令行)。在这一小节中,我们将描述如何使用Gii以及如何扩展Gii来增加我们的开发生产力。使用Gii
Gii以一个模块的方式运行,必须在一个已存在的Yii应用内部使用。要使用Gii,我们首先改变应用配置如下:returnarray(
234567101112);
),
),......
'modules'=>array(
'gii'=>array(
'class'=>'system.gii.GiiModule','password'=>'pickupapasswordhere',//'ipFilters'=>array(...alistofIPs...),//'newFileMode'=>0666,//'newDirMode'=>0777,
复制代码
13在上面,我们声明了一个模块名为gii,它的类是GiiModule。我们也为这个模块指定了一个
密码,当访问Gii时需要输入。
默认的,处于安全考虑,Gii被配置为只允许在本地访问。若我们想要在另外信任的机器上访问,可以在如上代码中配置GiiModule::ipFilters属性。
因为Gii可以产生并保存新代码文件到已存在的应用中,我们需要确保web服务器进程有权限这样做。在上面的GiiModule::newFileMode和GiiModule::newDirMode属性控制这些新文件和目录应当如何被产生。
现在我们可以通过URLhttp://hostname/path/to/index.php?r=gii访问Gii,这里我们假设http://hostname/path/to/index.php是访问已存在Yii应用的URL。
若已存在的Yii应用使用path格式的URL,我们可以通过URL
http://hostnamepath/to/index.php/gii访问Gii。我们也需要增加如下URL规则到已存在URL规则的前面:'components'=>array(
1415
......
'urlManager'=>array(
161718192021222324)
),
'urlFormat'=>'path','rules'=>array(
'gii'=>'gii',
'gii/ 'gii/ 复制代码 25Gii有一个新的默认代码生成器。每个代码生成器负责生成一个特定类型的代码。例如, controller生成器生成一个控制器类以及一些动作视图脚本;model生成器为指定的数据表生成一个ActiveRecord类。 使用一个生成器的基本工作流如下: 1.进入生成器页面; 2.填写字段以指定代码生成器的参数。例如,要使用module生成器创建一个新模块,你需要指定模块ID; 3.点击Preview按钮预览生成的代码。你将看到一个表展示了将被生成的一个代码文件列表。你可以点击其中的任何文件以预览代码。 4.点击Generate按钮以生成代码文件;5.查看代码生成日志。 扩展Gii 尽管默认的代码生成器已经可以产生很强大的代码,我也经常希望能定制他们或者创建个新的来满足我们的需求,例如,我们希望生成的代码能符合我们的编码风格,或者我们希望代码能支持国际化,这些都可以轻松的通过Gii实现Gii可以通过两种模式进行扩展:定制代码模板来扩展已有的代码生成器,和编写新的代码生成器 创建代码生成器 代码生成器所在的目录名被视为代码生成器的名字。该目录往往包含以下内容“model/ themodelgeneratorrootfolder 2627282930 ModelCode.phpModelGenerator.phpviews/ index.phptemplates/ thecodemodelusedtogeneratecodethecodegenerationcontrollercontainingviewscriptsforthegeneratorthedefaultviewscriptcontainingcodetemplatesets 3132 default/model.php the'default'codetemplateset thecodetemplateforgeneratingmodelclasscode 复制代码 33代码生成器的检索路径 Gii通过GiiModule::generatorPaths属性设置的路径搜索可用的代码生成器。如果需要定制,我们可以在应用的配置中如下配置这个属性returnarray( 343536373839404142); ), ), ), 'modules'=>array( 'gii'=>array( 'class'=>'system.gii.GiiModule','generatorPaths'=>array( 'application.gii', //apathalias 复制代码 43上面的配置表明Gii除了在默认的system.gii.generators之外,还在路径别名为 application.gii的目录下检索可用的代码生成器, 有可能会在不同的目录底下有相同名字的代码生成器,这种情况下在GiiModule::generatorPaths中最先指定的,具有优先权。 自定义代码模板 这是最简单的也是最常用的扩展Gii的方法。我们使用一个例子来解释如何抵制代码模板。假设我们想要自定义model代码生成器生成的代码。 我们首先创建一个名为protected/gii/model/templates/compact的路径。这里的model意味着我们要重写摩尔恩的model代码生成器。templates/compact表示我们要添加一个新的代码模板,名字为compact 然后我们修改应用的配置,把application.gii加入到GiiModule::generatorPaths中,如同上一节中所叙 现在打开model代码生成器页面。点击代码模板字段,在出现的下拉框里我们能看到我们新加的模板路径compact,如果我们选择这个模板来生成代码,我们会看到一个错误,那是因为我们还没有在compact底下放入任何的实际代码模板。 下面开始我们真正的自定义的工作。打开文件 protected/gii/model/templates/compact/model.php进行编辑。记住这个文件会像被作为视图文件使用,这意味着它可以包含PHP语句和声明。让我们修改模板,以便生成器产生的attributeLabels()方法,使用Yii::t()来对标签支持国际化publicfunctionattributeLabels() 44{45 returnarray( 46$label):?>47 Yii::t('application','$label'),\\n\";?> 484950} ); 复制代码 在每一个代码模板,我们可以访问一些预定义的变量,例如上面例子中的$labels。这些变量由相应的代码生成器产生。不同的代码生成器在他们各自的模板中可能产生不同的变量。请仔细阅读默认代码模板的中的描述创建新的代码生成器 在本节中,我们讲解了如何创建一个代码生成器,该生成器的作用是生成了一个生成一个部件类。我们首先创建路径protected/gii/widget。在这个路径下,我们创建以下文件 •••• 51 WidgetGenerator.php:containstheWidgetGeneratorcontrollerclass.这是部件生成器的WidgetCode.php:containstheWidgetCodemodelclass.代码生成的主要逻辑.views/index.php:代码生成器输入表单的视图脚本templates/default/widget.php:生成部件类的默认代码模板 入口. CreatingWidgetGenerator.php WidgetGenerator.php文件非常简单,仅包含以下代码classWidgetGeneratorextends CCodeGenerator52{5354} public$codeModel='application.gii.widget.WidgetCode'; 复制代码 55在上面的代码中,我们指定生成器使用的model类的路径别名是 application.gii.widget.WidgetCode。WidgetGenerator扩展自CCodeGenerator, CCodeGenerator实现了许多功能,includingthecontrolleractionsneededtocoordinatethecodegenerationprocess.CreatingWidgetCode.php WidgetCode.php文件了WidgetCode模型类,它包含了根据用户输入生成部件的类的主要逻辑。在本例中,我们假设用户仅仅输入了部件的类名,参考如下:classWidgetCodeextends CCodeModel56{5758596061626365666768697071727374757677} }} publicfunctionprepare(){ $path=Yii::getPathOfAlias('application.components.'.$this->className).'.php';$code=$this->render($this->templatepath.'/widget.php');$this->files[]=newCCodeFile($path,$code);));} publicfunctionattributeLabels(){ returnarray_merge(parent::attributeLabels(),array( 'className'=>'WidgetClassName',)); public$className;publicfunctionrules(){ returnarray_merge(parent::rules(),array( array('className','required'), array('className','match','pattern'=>'/^\\w+$/'), 复制代码 78WidgetCode类继承于CCodeModel。想普通的模型类一样,在本例中我们生成了rules()和 attributeLabels()分别来验证用户输入和显示属性标签。注意因为CCodeModel类已经定义了一些rule和属性标签,我们需要把已经定义的和我们的ruler和标签进行合并 prepare()方法准备要被产生的代码。它的主要任务是准备CCodeFile对象列表,其中的每一个都被渲染了一个代码文件。在我们的例子中,我们只需要产生一个CCodeFile对象来渲染要生成的部件类文件。生成的部件类被保存在protected/components目录底下,我们调用CCodeFile::render来生成实际的代码,该方法把代码模板当做php脚本加载,并返回输出内容作为生成的代码。 Creatingviews/index.php 完成了控制器(WidgetGenerator)和模型(WidgetCode)之后,现在让我们来创建视图文件 views/index.php 79beginWidget('CCodeForm',array('model'=>$model));?>8081828384858687 labelEx($model,'className');?> textField($model,'className',array('size'=>65));?> Widgetclassnamemustonlycontainwordcharacters.WidgetGenerator
error($model,'className');?>
88endWidget();?>
复制代码
上面的代码中,我们主要通过CCodeForm部件显示了一个表单。在这个表单中,我们显示
了一个字段用于输入类的名字。(Inthisform,wedisplaythefieldtocollecttheinputfortheclassNameattributeinWidgetCode)
创建这个表单的时候,我们可以用CCodeForm部件的两个不错的功能。一个是inputtooltips,另一个是是stickyinputs.
如果你使用过任何的默认代码生成器,你将会注意到,当焦点放到输入框上的时候,一个友好的tooltip会显示在一侧,这可以通过编写挨着的CSSclass名为tooltip的div来很容易的实现。(ThiscaneasilyachievedherebywritingnexttotheinputfieldadivwhoseCSSclassistooltip.)
对一些输入框来说,我们需要记住他们最后一次的有效输入,这样用户使用生成器生成代码的时候不用重新输入。例如,默认的控制器生成器重控制器基类名字的输入框,粘性字段最初用高亮的静态文本显示,当我们点击的时候,会变成输入框让用户进行输入。
声明一个字段有粘性,我们需要做两件事
首先我们需要为相应的模型属性声明一个粘性验证规则。例如默认的控制器生成器有如下的规则来声明baseClass和attributes具有粘性publicfunctionrules()
90{9192939495}
));
returnarray_merge(parent::rules(),array(
......
array('baseClass,actions','sticky'),
复制代码
96然后,我们需要为视图中的输入框所在的div添加名为sticky的cssclass,如下: ...inputfieldhere... 98
复制代码
99创建templates/default/widget.php
最后我们创建代码模板templates/default/widget.php,我们已经提到过,这个最为视图脚本,可以包含PHP声明和表达式。在模板中,我们经常使用的$this是模型示例的引用。在本例中$this是WidgetModel对象的一个引用。我们可以这样读取用户输入的类名:$this->classNameecho'
100classclassName;?>extendsCWidget101{102103104105}
publicfunctionrun(){}
复制代码
这便是代码生成器的创建,我们可以通过地址http://hostname/path/to/index.php?r=gii/widget来访问这个代码生成器。
开源PHP开发框架Yii全方位教程(22)URL管理
1
Web应用程序完整的URL管理包括两个方面。首先,当用户请求约定的URL,应用程序
需要解析它变成可以理解的参数。第二,应用程序需要提供一种创造URL的方法,可以让创建的URL应用程序正确理解。对于Yii应用程序,这些通过CUrlManager辅助完成。
CreatingURLs(创建网址)
虽然URL可被硬编码在控制器的视图(view)文件,但动态的创建他们会更加灵活:
$url=$this->createUrl($route,$params);
复制代码
2
$this指的是控制器实例;$route指定请求的route的要求;$params列出了附加在网址中的
GET参数。
默认情况下,URL以get格式使用createUrl创建。例如,提供$route='post/read'和$params=array('id'=>100),我们将获得以下网址:/index.php?r=post/read&id=100
复制代码
3
参数以一系列Name=Value通过符号串联起来出现在请求字符串,r参数指的是请求的
route。这种URL格式用户友好性不是很好,因为它需要一些非单词字符。
我们可以使上述网址看起来更简洁,更不言自明,通过采用所谓的path格式,省去查询字符串并把GET参数加到路径信息,作为网址的一部分:/index.php/post/read/id/100
复制代码
4
要更改URL格式,我们应该配置urlManager应用组件,以便createUrl可以自动切换到新
格式,并可以让应用程序可以正确理解新的网址:array(
567101112);
),
),......
'components'=>array(
......
'urlManager'=>array(
'urlFormat'=>'path',
复制代码
13请注意,我们不需要指定的urlManager元件的类,因为它在CWebApplication预声明为
CUrlManager。
此网址通过createurl方法所产生的是一个相对地址。为了得到一个绝对的url,我们可以用前缀yii::app()->hostInfo,或调用createAbsoluteUrl。
User-friendlyURLs(用户友好的URL)
当用path作为URL格式,我们可以指定某些URL规则使我们的网址对用户更加友好性。例如,我们可以产生一个短短的URL/post/100,而不是冗长/index.php/post/read/id/100。网址创建和解析都是通过CUrlManager指定网址规则。
要指定的URL规则,我们必须设定urlManager应用组件的属性rules:array(
14151617181920212223242526);
),
),
),
......
'components'=>array(
......
'urlManager'=>array(
'urlFormat'=>'path','rules'=>array(
'pattern1'=>'route1','pattern2'=>'route2','pattern3'=>'route3',
复制代码
27规则被指定为一个由pattern-routepairs组成的数组,每一个对应一个规则。一个规则的
pattern是一个字符串,用来匹配URL的路径信息部分(thepathinfopartofURLs)。一个规则的route应当指向一个有效的控制器路由。
除了上面的pattern-route格式,一个规则也可以以自定义选项来指定,如
下:’pattern1’=>array(’route1’,’urlSuffix’=>’.xml’,’caseSensitive’=>false)
复制代码
在上面,数组包含一个自定义选项列表。从版本1.1.0起,下面的选项可用:
•urlSuffix:此规则使用的URL后缀。默认是null,意味着使用CUrlManager::urlSuffix的值。•
caseSensitive:
是否规则区分大小写。默认是
null,意味着使用
CUrlManager::caseSensitive的值。
•defaultParams:此规则提供的默认GET参数(name=>value)。Whenthisruleisusedto
parsetheincomingrequest,thevaluesdeclaredinthispropertywillbeinjectedintoGET•matchValue:whethertheGETparametervaluesshouldmatchthecorrespondingsubpatternsintherulewhencreatingaURL.Defaultstonull,meaningusingthevalueofCUrlManager::matchValue.Ifthispropertyisfalse,itmeansarulewillbeusedforcreatingaURLifitsrouteandparamepatterns.Notethatsettingthispropertytotruewilldegradeperformance.
28
使用命名参数(UsingNamedParameters)
规则可以绑定少量的GET参数。这些出现在规则格式的GET参数,按照如下的特殊格式:
ParamName表示GET参数名字,可选项ParamPattern表示将用于匹配GET参数值的正则表达式。如果省略了ParamPattern,表示该参数匹配除了/.之外的任何字符。当生成一个网址(URL)时,这些参数将被相应的参数值替换;当解析一个网址时,相应的GET参数将通过解析结果来生成。
我们使用一些例子来解释URL规则如何工作。我们假设我们的规则包括如下三个:array(
29303132)
'posts'=>'post/list',
'post/ 'post/ 复制代码 •调用$this->createUrl('post/list')生成/index.php/posts。第一个规则适用。 •调用$this->createUrl('post/read',array('id'=>100))生成/index.php/post/100。第二个规则适用。•• 33 调用$this->createUrl('post/read',array('year'=>2008,'title'=>'asamplepost'))生成调用$this->createUrl('post/read')产生/index.php/post/read。请注意,没有规则适用。 /index.php/post/2008/a%20sample%20post。第三个规则适用。 也就是说,当使用createUrl生成URL,路由(route)和传递给该方法的GET参数被用来决定哪些URL规则适用。如果规则中的每个相关参数可以在被传递给createUrl的GET参数中找到的,并且路由的规则也匹配路由参数,规则将用来生成网址。 如果传递到createUrl的GET参数比规则这种给出的参数要多,俺么其他参数将出现在查询字符串。例如,如果我们调用$this->createUrl('post/read',array('id'=>100,'year'=>2008)),我们将获得/index.php/post/100?year=2008。为了使这些额外参数出现在路径信息的一部分,我们应该给规则附加/*。 因此,使用规则post/ /index.php/post/100/year/2008。 正如我们提到的,URL规则的另一个用途是解析请求网址。当然,这是URL生成的一个逆过程。例如,当用户请求/index.php/post/100,上面例子的第二个规则将适用来解析路由post/read 和GET参数array('id'=>100)(可通过$_GET获得)。 使用URLrule会降低系统性能,这是因为解析一个URL请求的时候,CUrlManager会尝试用每一个rule进行匹配,直到有一个符合,ruler的数量越多,性能的开销越大,因此高流量的网站应该尽量减少URLruler的应用。 参数化路由 从1.0.5版本开始,我们可以在ruler中对路由路径参数化。这允许一条ruler可以应用到多条相匹配路由。这也有利于减少应用ruler的数量,提高整体性能。 我们使用下面的例子来解释如何使用命名参数对路由进行参数化array( 34353637) '<_c:(post|comment)>/ 复制代码 38在上面,我们在ruler的路由部分使用了两个命名参数_c和_a,前者匹配一个控制器id是 post或者comment,后者匹配一个动作id是create、update或者delete。你可以把参数的名字起的尽可能长,以避免和出现在GET中的参数冲突。 使用上面的规则URL/index.php/post/123/create,会被解释为post/create,并具有GET参数id=123。给出路由comment/list和GET参数page=2,我们可以创建URL/index.php/comments?page=2参数化主机名 从1.0.11版本开始,分析和创建URLs的rule中可以支持主机名。一方面,可以读取路径中的主机名作为GET变量,例如:地址http://admin.example.com/en/profile,可以被解析为GET变量user=admin和lang=en。另一方面,开主机名的ruler可以创建参数化主机名的URLs 为了使用参数化的主机名,我们可以用主机信息声明URLrules,例如:array( 3940) 'http:// 复制代码 41上面的例子表示,主机名的第一部分被当做user参数,而第二部分被当做lang参数,相符 合的路由是user/profile路由。 请注意当URL是由参数化的主机来创建时,CUrlManager::showScriptName不会起作用。 同样需要注意如果应用是在WEB根目录的子文件夹底下,参数化主机名的ruler不能包含子文件夹,例如,如果应用的路径是在http://www.example.com/sandbox/blog下面,我们可以仍然使用和上面描述相同的URLruler:sandbox/blog而不需要子文件夹 隐藏index.php 还有一点,我们可以进一步清理我们的网址,即在URL中藏匿index.php入口脚本。这就要求我们配置Web服务器,以及urlManager应用程序元件。 我们首先需要配置Web服务器,这样一个URL没有入口脚本仍然可以由入口脚本来处理。如果是ApacheHTTPserver,可以通过打开网址重写引擎和指定一些重写规则。这两个操作可以在包含入口脚本的目录下的.htaccess文件里实现。下面是一个示例:Options+FollowSymLinks 42IndexIgnore/43RewriteEngineon 44#ifadirectoryorafileexists,useitdirectly45RewriteCond%{REQUEST_FILENAME}!-f46RewriteCond%{REQUEST_FILENAME}!-d47#otherwiseforwardittoindex.php48RewriteRule.index.php 复制代码 然后,我们设定urlManager元件的showScriptName属性为false。 现在,如果我们调用$this->createUrl('post/read',array('id'=>100)),我们将获取网址/post/100。更重要的是,这个URL可以被我们的Web应用程序正确解析。FakingURLSuffix(伪造URL后缀) 我们还可以添加一些网址的后缀。例如,我们可以用/post/100.html来替代/post/100。这使得它看起来更像一个静态网页URL。为了做到这一点,只需配置urlManager组件的urlSuffix属性为你所喜欢的后缀。
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- axer.cn 版权所有 湘ICP备2023022495号-12
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务