三、控制器(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。如果类文件不存在,将触发一个 404CHttpException 异常。
在使用了模块的情况下,应用将检查此 ID是否代表一个模块中的控制器。如果是的话,模块实例将被首先创建,然后创建模块中的控制器实例。
3、动作(action)
动作 就是被定义为一个以 action单词作为前缀命名的方法。而更高级的方式是定义一个动作类并让控制器在收到请求时将其实例化。这使得动作可以被复用,提高了可复用度。
1、定义一个动作类,基本格式如下:
class UpdateAction extends CAction
{
publicfunction run()
{
// place the action logic here
}
}
2、使用动作类:为了让控制器注意到这个动作,我们要用如下方式覆盖控制器类的actions() 方法:
class PostController extends CController
{
publicfunction 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
四、过滤器(filter)
过滤器是一段代码,可被配置在控制器动作执行之前或之后执行。
一个动作可以有多个过滤器。如有多个过滤器,则按照它们出现在过滤器列表中的顺序依次执行。过滤器可以阻止动作及后面其他过滤器的执行。
过滤器可以定义为一个控制器类的方法。过滤器方法名必须以 filter 开头。例如,现有的 filterAccessControl方法定义了一个名为 accessControl 的过滤器。过滤器方法必须为如下结构:
public function filterAccessControl($filterChain)
{
// 调用$filterChain->run() 以继续后续过滤器与动作的执行。
}
$filterChain (过滤器链)是一个 CFilterChain 的实例,代表与所请求动作相关的过滤器列表。在过滤器方法中,我们可以调用$filterChain->run() 以继续执行后续过滤器和动作。
如 动作 一样,过滤器也可以是一个对象,它是 CFilter 或其子类的实例。如下代码定义了一个新的过滤器类:
class PerformanceFilter extends CFilter
{
protectedfunction preFilter($filterChain)
{
// 动作被执行之前应用的逻辑
return true; // 如果动作不应被执行,此处返回 false
}
protectedfunction postFilter($filterChain)
{
// 动作执行之后应用的逻辑
}
}
要对动作应用过滤器,我们需要覆盖 CController::filters()方法。此方法应返回一个过滤器配置数组。例如:
class PostController extends CController
{
......
publicfunction 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
{
publicfunction init()
{
// 此方法会被 CController::beginWidget() 调用
}
publicfunction 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 functiongetTextWidth() // 获取 textWidth 属性
{
return$this->_textWidth;
}
public functionsetTextWidth($value) // 设置 TextWidth 属性
{
$this->_textWidth=$value;
}
上述代码定义了一个可写的属性名为 textWidth(名字是大小写不敏感的)。当读取属性时,getTextWidth()就会被调用,其返回值则成为属性值;相似的,当写入属性时,setTextWidth() 被调用。如果 setter方法没有定义,则属性将是只读的,如果对其写入则会抛出一个异常。使用 getter 和 setter方法定义一个属性有一个好处:即当读取或写入属性时,可以执行额外的逻辑(例如,执行验证,触发事件)。
注意: 通过 getter/setter 定义的属性和类成员变量之间有一个细微的差异:属性的名字是大小写不敏感的, 而类成员变量是大小写敏感的。
2、组件事件
组件事件是一些特殊的属性,它们使用一些称作 事件句柄(eventhandlers)的方法作为其值。分配一个方法到一个事件将会引起方法在事件被唤起处自动被调用。因此,一个组件的行为可能会被一种在部件开发过程中不可预见的方式修改。
组件事件以 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()以创建相应类的实例。即使类文件在之前从未被包含。
不要将路径别名和名字空间混淆了,名字空间是指对一些类名的一个逻辑组合,这样它们就可以相互区分开,即使有相同的名字。而路径别名是用于指向一个类文件或目录。路径别名与名字空间并不冲突。