YII读书笔记
来源:互联网 发布:光翼学园网络班有用吗 编辑:程序博客网 时间:2024/06/04 01:11
=========================================================================================================================
模型-视图-控制器 (MVC)
一个典型的工作流
- 用户发出了访问 URL
http://www.example.com/index.php?r=post/show&id=1
的请求, Web 服务器通过执行入口脚本index.php
处理此请求。 - 入口脚本创建了一个 应用 实例并执行。
- 应用从一个叫做
request
的 应用组件 中获得了用户请求的详细信息。 - 应用在一个名叫
urlManager
的应用组件的帮助下,决定请求的 控制器 和 动作 。在这个例子中,控制器是post
,它代表PostController
类; 动作是show
,其实际含义由控制器决定。 - 应用创建了一个所请求控制器的实例以进一步处理用户请求。控制器决定了动作
show
指向控制器类中的一个名为actionShow
的方法。然后它创建并持行了与动作关联的过滤器(例如访问控制,基准测试)。 如果过滤器允许,动作将被执行。 - 动作从数据库中读取一个 ID 为
1
的Post
模型。 - 动作通过
Post
模型渲染一个名为show
的 视图。 - 视图读取并显示
Post
模型的属性。 - 视图执行一些 小物件。
- 视图的渲染结果被插入一个 布局。
- 动作完成视图渲染并将其呈现给用户。
入口脚本
入口脚本是处理用户的初始引导PHP脚本。它是唯一一个最终用户可直接请求执行的PHP脚本
// 在生产环境中请删除此行defined('YII_DEBUG') or define('YII_DEBUG',true);// 包含Yii引导文件require_once('path/to/yii/framework/yii.php');// 创建一个应用实例并执行$configFile='path/to/config/file.php';Yii::createWebApplication($configFile)->run();
yii.php
。然后他按指定的配置创建了一个Web 应用实例并执行。调试模式
yii.php
文件之前定义此常量YII_DEBUG 为true应用
前端控制器
。应用配置
配置是一个键值对数组。每个键和应用实例属性一一对应,每个值即相应属性的初始值。 例如,如下的配置设定了CWebApplication应用的name 和 defaultController 属性的值。
array( 'name'=>'Yii Framework', 'defaultController'=>'site',)
protected/config/main.php
)中保存这些配置$app=Yii::createWebApplication($webApplication,$configFile);
应用基础目录
protected
的子目录可以通过在基础目录中放置一个.htaccess
文件很简单的实现。 .htaccess
内容如下:
deny from all
应用组件
通过配置config/main.php的 components 属性, 我们可以自定义应用中用到的任何组件类及其属性值。例如,我们可以配置应用的CMemCache 组件, 这样它就可以使用多个 memcache 服务器实现缓存:
array( 'components'=>array( 'cache'=>array( 'class'=>'CMemCache', 'servers'=>array( array('host'=>'server1', 'port'=>11211, 'weight'=>60), array('host'=>'server2', 'port'=>11211, 'weight'=>40), ), ), ),)
Yii::app()->ComponentID
,其中的 ComponentID
是指组件的ID(例如Yii::app()->db
)核心应用组件
assetManager: CAssetManager - 管理私有资源文件的发布。
authManager: CAuthManager - 管理基于角色的访问控制 (RBAC).
cache: CCache - 提供数据缓存功能。注意,你必须指定实际的类(例如CMemCache, CDbCache)。 否则,当你访问此组件时将返回 NULL。
clientScript: CClientScript - 管理客户端脚本 (javascripts 和 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 - 管理主题。
应用的生命周期
当处理用户请求时,应用将经历如下声明周期:
通过 CApplication::preinit() 预初始化应用;
设置类的自动装载器和错误处理 ;
注册核心类组件;
加载应用配置; (main.php 对应2,3,4步骤)
通过 CApplication::init() 初始化应用:
- 注册应用行为;
- 载入静态应用组件;
触发 onBeginRequest 事件;
处理用户请求: (对应acitonCotroller)
- 解析用户请求;
- 创建控制器;
- 运行控制器;
触发 onEndRequest 事件。
控制器
1.控制器
是 CController 或其子类的实例。它在当用户请求时由应用创建。动作
的最简形式,就是一个名字以 action
开头的控制器类方法。index
。它可以通过设置 CController::defaultAction 修改。路由
控制器和动作以 ID 识别。控制器 ID 是一种 'path/to/xyz' 的格式,对应相应的控制器类文件protected/controllers/path/to/XyzController.php
, 其中的标志 xyz
应被替换为实际的名字 (例如 post
对应 protected/controllers/PostController.php
). 动作 ID 是除去 action
前缀的动作方法名。例如,如果一个控制器类含有一个名为 actionEdit
的方法,则相应的动作 ID 为 edit
post/edit
代表 PostController
及其 edit
动作。默认情况下,URL http://hostname/index.php?r=post/edit
即请求此控制器和动作moduleID/controllerID/actionID
控制器实例化
'path/to/xyz'
的格式,控制器类的名字将判断为 XyzController
, 相应的类文件则为protected/controllers/path/to/XyzController.php
。admin/user
将被解析为控制器类 UserController
,类文件是 protected/controllers/admin/UserController.php
。 如果类文件不存在,将触发一个 404 CHttpException 异常动作
action
单词作为前缀命名的方法。定义一个新动作类,可用如下代码:
class UpdateAction extends CAction{ public function run() { // place the action logic here }}
为了让控制器注意到这个动作,我们要用如下方式覆盖控制器类的actions() 方法:
class PostController extends CController{ public function actions() { return array( 'edit'=>'application.controllers.post.UpdateAction', ); }}
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
动作参数绑定
$_GET
填充。1.1.4之前,使用动作参数功能:从 $_GET
中提取参数时:
class PostController extends CController{ public function actionCreate() { if(isset($_GET['category'])) $category=(int)$_GET['category']; else throw new CHttpException(404,'invalid request'); if(isset($_GET['language'])) $language=$_GET['language']; else $language='en'; // ... fun code starts here ... }}
1.1.4开始,使用动作参数功能:
class PostController extends CController{ public function actionCreate($category, $language='en') { $category=(int)$category; // ... fun code starts here ... }}
class PostController extends CController{ public function actionCreate(array $categories) //参数强制转换为array类型 { // Yii will make sure $categories be an array }}
过滤器
过滤器可以定义为一个控制器类的方法。方法名必须以 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'), array( 'application.filters.PerformanceFilter - edit, create', 'unit'=>'second', ), ); } }
上述代码指定了两个过滤器: postOnly
和 PerformanceFilter
。 postOnly
过滤器是基于方法的(相应的过滤器方法已在 CController 中定义); 而 performanceFilter
过滤器是基于对象的。路径别名application.filters.PerformanceFilter
指定过滤器类文件是protected/filters/PerformanceFilter
。我们使用一个数组配置 PerformanceFilter
,这样它就可被用于初始化过滤器对象的属性值。此处 PerformanceFilter
的 unit
属性值将被初始为 second
。
使用加减号,我们可指定哪些动作应该或不应该应用过滤器。上述代码中, postOnly
应只被应用于 edit
和 create
动作,而 PerformanceFilter
应被应用于 除了 edit
和 create
之外的动作。 如果过滤器配置中没有使用加减号,则此过滤器将被应用于所有动作。
模型
2>Active Record (AR) 是一种用于通过面向对象的风格抽象化数据库访问的设计模式。 每个 AR 对象是一个 CActiveRecord 或其子类的实例。 数据对象的每个字段代表数据表中的一行。 行中的字段对应 AR 对象中的属性。更多关于 AR 的细节请阅读 Active Record.
视图
edit
的名称出自一个名为 edit.php
的脚本文件.要渲染时如,需通过传递视图的名称调用 CController::render()。这个方法将在 protected/views/ControllerID
目录下寻找对应的视图文件.如controller路径为:\protected\controllers\contract\applyController.php 对应 视图目录:protected\views\contract\apply\$this
来访问当前控制器实例用以下 推送
的方式传递数据到视图里:
$this->render('edit', array( 'varName1'=>$value1, 'varName2'=>$value2,));
$
varName1
和 $
varName2
.布局
$content
则储存了内容视图的渲染结果.protected/views/layouts/main.php
是默认的布局文件.这可以通过改变 CWebApplication::layout 或者 CWebApplication::layout 进行自定义。小物件
按如下视图脚本来使用一个小物件:
<?php $this->beginWidget('path.to.WidgetClass'); ?>...可能会由小物件获取的内容主体...<?php $this->endWidget(); ?>
或者
<?php $this->widget('path.to.WidgetClass'); ?>
后者用于不需要任何 body 内容的组件.
小物件可通过配置来定制它的表现.这是通过调用 CBaseController::beginWidget 或 CBaseController::widget 设置其初始化属性值来完成的.例如,当使用 CMaskedTextField 小物件时,我们想指定被使用的 mask (可理解为一种输出格式,译者注).我们通过传递一个携带这些属性初始化值的数组来实现.这里的数组的键是属性的名称,而数组的值则是小物件属性所对应的值.正如以下所示 :
<?php$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
子目录之下.这些视图可以通过调用 CWidget::render() 渲染,这一点和控制器很相似.唯一不同的是,小物件的视图没有布局文件支持。另外,小物件视图中的$this
指向小物件实例而不是控制器实例。
系统视图
framework/views
下, Yii 提供了一系列默认的系统视图. 他们可以通过在 protected/views/system
下创建同名视图文件进行自定义组件
组件属性
组件的属性就像对象的公共成员变量。它是可读写的。例如:
$width=$component->textWidth; // 获取 textWidth 属性$component->enableCaching=true; // 设置 enableCaching 属性
定义一个组件属性: 只需在组件类中定义一个公共成员变量即可。更灵活的方式是定义其 getter 和 setter 方法,例如:
public function getTextWidth(){ return $this->_textWidth;} public function setTextWidth($value){ $this->_textWidth=$value;}
textWidth
(名字是大小写不敏感的)。 当读取属性时,getTextWidth()
就会被调用,其返回值则成为属性值;相似的, 当写入属性时,setTextWidth()
被调用。如果 setter 方法没有定义,则属性将是只读的, 如果对其写入则会抛出一个异常。使用 getter 和 setter 方法定义一个属性有一个好处:即当读取或写入属性时, 可以执行额外的逻辑(例如,执行验证,触发事件)。组件事件是一些特殊的属性,它们使用一些称作 事件句柄 (event handlers)
的方法作为其值。 分配一个方法到一个事件将会引起方法在事件被唤起处自动被调用。因此, 一个组件的行为可能会被一种在部件开发过程中不可预见的方式修改。
组件事件以 on
开头的命名方式定义。和属性通过 getter/setter 方法来定义的命名方式一样, 事件的名称是大小写不敏感的。以下代码定义了一个 onClicked
事件:
public function onClicked($event){ $this->raiseEvent('onClicked', $event);}
这里作为事件参数的 $event
是 CEvent 或其子类的实例。
我们可以附加一个方法到此 event,如下所示:
$component->onClicked=$callback;
这里的 $callback
指向了一个有效的 PHP 回调。它可以是一个全局函数也可以是类中的一个方法。 如果是后者,它必须以一个数组的方式提供: array($object,'methodName')
.
事件句柄的结构如下:
function methodName($event){ ......}
这里的 $event
即描述事件的参数(它来源于 raiseEvent()
调用)。$event
参数是 CEvent 或其子类的实例。 至少,它包含了关于谁触发了此事件的信息。
从版本 1.0.10 开始,事件句柄也可以是一个PHP 5.3以后支持的匿名函数。例如,
$component->onClicked=function($event) { ......}
如果我们现在调用 onClicked()
,onClicked
事件将被触发(在 onClicked()
中), 附属的事件句柄将被自动调用。
一个事件可以绑定多个句柄。当事件触发时, 这些句柄将被按照它们绑定到事件时的顺序依次执行。如果句柄决定组织后续句柄被执行,它可以设置 $event->handled 为 true
组件行为
继承(inherited)
, 而不是专有化继承(即普通的类继承).要使用一个行为,它必须首先通过调用此行为的 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, 一起使用时,行为会更加强大. 当行为被绑定到组件时,行为里的一些方法就可以绑定到组件的一些事件上了. 这样一来,行为就有机观察或者改变组件的常规执行流程.
自版本 1.1.0 开始,一个行为的属性也可以通过绑定到的组件来访问。 这些属性包含公共成员变量以及通过 getters 和/或 setters 方式设置的属性。 例如, 若一个行为有一个 xyz 的属性,此行为被绑定到组件 $a,然后我们可以使用表达式 $a->xyz
访问此行为的属性。
模块
创建模块
模块组织在一个目录中,目录的名字即模块的唯一 ID
模块的典型的目录结构:
forum/ ForumModule.php 模块类文件 components/ 包含可复用的用户组件 views/ 包含小物件的视图文件 controllers/ 包含控制器类文件 DefaultController.php 默认的控制器类文件 extensions/ 包含第三方扩展 models/ 包含模块类文件 views/ 包含控制器视图和布局文件 layouts/ 包含布局文件 default/ 包含 DefaultController 的视图文件 index.php 首页视图文件
路径别名与名字空间
system.web.CController
会被翻译为yii/framework/web/CController
Root Alias
system
: 表示 Yii 框架目录;zii
: 表示 Zii 库 目录;application
: 表示应用的 基础目录;webroot
: 表示 入口脚本 文件所在的目录。此别名从版本 1.0.3 开始有效ext
: 表示包含了所有第三方 扩展 的目录。此别名从版本 1.0.8 开始有效
system.web.CController
会被翻译为yii/framework/web/CController
Importing Classes
使用别名可以很方便的导入类的定义。 例如,如果我们想包含 CController 类的定义,我们可以调用如下代码
Yii::import('system.web.CController');
include
和 require
不同,它更加高效。 导入(import)的类定义并不会真正被包含进来,直到它第一次被引用。 多次导入同样的名字空间也会比 include_once
和 require_once
快得多使用Class Map
使用Class Map
若要使用预导入功能,要在CWebApplication::run()执行前执行下面的代码:
Yii::$classMap=array( 'ClassName1' => 'path/to/ClassName1.php', 'ClassName2' => 'path/to/ClassName2.php', ......);
导入目录
使用如下语法导入整个目录,这样此目录下的类文件就会在需要时被自动包含。
Yii::import('system.web.*');
Namespace
使用命名空间的类
application\components\GoogleMap
所对应的路径必须和别名:application.components.GoogleMap
一致。开发规范
Yii 编程中推荐的开发规范。 为简单起见,我们假设 WebRoot
是 Yii 应用安装的目录。
URL
默认情况下,Yii 识别如下格式的 URL:
http://hostname/index.php?r=ControllerID/ActionID
1.r
GET 变量意为 路由(route) ,它可以被Yii解析为 控制器和动作。 ActionID
被省略,控制器将使用默认的动作(在CController::defaultAction中定义); ControllerID
也被省略(或者 r
变量不存在),应用将使用默认的控制器 (在CWebApplication::defaultController中定义)。http://hostname/ControllerID/ActionID.html
代码
1.命名变量、函数和类时使用 驼峰风格,即每个单词的首字母大写并连在一起,中间无空格。 变量名和函数名应该使它们的第一个单词全部小写,以使其区别于类名(例如:$basePath
, runController()
, LinkPager
)。对私有类成员变量来说,我们推荐以下划线作为其名字前缀(例如: $_actionList
)
Controller
结尾。那么控制器 ID 即类名的首字母小写并去掉单词Controller
。 例如,PageController
类的 ID 就是 page
配置
array('name'=>'My application', 'basePath'=>'./protected')
初始化了 name
和 basePath
属性为它们相应的数组值文件
命名和使用文件的规范取决于它们的类型。
CController.php
文件中index
视图位于 index.php
文件中。 视图文件是一个PHP脚本文件,它包含了用于呈现内容的 HTML和PHP代码目录
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 自定义。
数据库
多数Web 应用是由数据库驱动的。 推荐在对表和列命名时使用如下命名规范。
数据库表名和列名都使用小写命名。
名字中的单词应使用下划线分割 (例如
product_order
)。对于表名,推荐使用单数名字。
表名可以使用一个通用前缀,例如
tbl_
。这样当应用所使用的表和另一个应用说使用的表共存于同一个数据库中时就特别有用。 这两个应用的表可以通过使用不同的表前缀很容易地区别开。
开发流程
创建目录结构骨架。
配置此 应用。通过修改应用配置文件实现的。 此步骤可能也需要编写一些应用组件(例如用户组件)。
为每个类型的数据(表)创建一个 模型 类。
Gii
工具可以用于快速为每个数据表创建 active record 类为每个类型的用户请求 创建一个 控制器 类。 具体如何对用户请求归类要看实际需求。总体来说,如果一个模型类需要被用户访问,就应该有一个相应的控制器类。Gii
工具也可以自动实现这一步骤。实现 动作 和他们相应的 视图。 这是真正所需要做的工作。
在控制器类中配置必要的动作 过滤器。
如果需要主题功能,创建 主题 。
如果需要 国际化(I18N) ,创建翻译信息。
对可缓存的数据点和视图点应用适当的 缓存 技术。
最终 调整 与部署。
上述的每个步骤中,可能需要创建并执行测试用例。
通过HTML表单收集用户数据是Web程序开发的主要工作之一。除了表单设计外, 开发者还需要将现存的或默认的数据填充到表单,验证用户输入, 对无效的输入显示适当的错误信息,保存输入到持久性存储器。通过其 MVC 结构极大地简化了此工作流程。
处理表单时,通常需要以下步骤:
- 创建用于表现所要收集数据字段的模型类。
- 创建一个控制器动作,响应表单提交。
- 在视图脚本中创建与控制器动作相关的表单。
创建模型
定义模型类
创建了一个 LoginForm
模型类用于在一个登录页中收集用户的输入。 由于登录信息只被用于验证用户,并不需要保存,因此我们将 LoginForm
创建为一个 表单模型。
class LoginForm extends CFormModel{ public $username; public $password; public $rememberMe=false;}
LoginForm
中定义了三个属性: $username
, $password
和 $rememberMe
。用于保存用户输入的用户名和密码,还有用户是否想记住他的登录的选项。 由于 $rememberMe
有一个默认的值 false
,相应的选项在初始化显示在登录表单中时将是未勾选状态。
声明验证规则
rules()
方法中指定这些验证规则, 此方法应返回一个规则配置数组。class LoginForm extends CFormModel{ public $username; public $password; public $rememberMe=false; private $_identity; public function rules() { return array( array('username, password', 'required'), array('rememberMe', 'boolean'), array('password', 'authenticate'), ); } public function authenticate($attribute,$params) { $this->_identity=new UserIdentity($this->username,$this->password); if(!$this->_identity->authenticate()) $this->addError('password','错误的用户名或密码。'); }}
上述代码指定:username
和 password
为必填项, password
应被验证(authenticated),rememberMe
应该是一个布尔值。
rules()
返回的每个规则必须是以下格式:
array('AttributeList', 'Validator', 'on'=>'ScenarioList', ...附加选项)
其中 AttributeList(特性列表)
是需要通过此规则验证的特性列表字符串,每个特性名字由逗号分隔;Validator(验证器)
指定要执行验证的种类;on
参数是可选的,它指定此规则应被应用到的场景列表; 附加选项是一个名值对数组,用于初始化相应验证器的属性值。
有三种方式可在验证规则中指定 Validator
。第一, Validator
可以是模型类中一个方法的名字,就像上面示例中的authenticate
。验证方法必须是下面的结构:
/** * @param string 所要验证的特性的名字 * @param array 验证规则中指定的选项 */public function ValidatorName($attribute,$params) { ... }
第二,Validator
可以是一个验证器类的名字,当此规则被应用时, 一个验证器类的实例将被创建以执行实际验证。规则中的附加选项用于初始化实例的属性值。 验证器类必须继承自 CValidator。
第三,Validator
可以是一个预定义的验证器类的别名。在上面的例子中, required
名字是 CRequiredValidator 的别名,它用于确保所验证的特性值不为空。 下面是预定义的验证器别名的完整列表:
==========================================================================================================================
- 概览
1.数据访问对象 (DAO)
Yii DAO 主要包含如下四个类:
- CDbConnection: 代表一个数据库连接。
- CDbCommand: 代表一条执行的 SQL 语句。
- CDbDataReader: 代表一个只向前移动的,来自一个查询结果集中的行的流。
- CDbTransaction: 代表一个数据库事务。
建立数据库连接
3.DSN 的格式取决于所使用的 PDO 数据库驱动。总体来说, DSN 要含有 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
访问数据库连接了。它已经被自动激活了执行 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()
方法以直接返回查询结果。
$rowCount=$command->execute(); // 执行无查询 SQL$dataReader=$command->query(); // 执行一个 SQL 查询$rows=$command->queryAll(); // 查询并返回结果中的所有行$row=$command->queryRow(); // 查询并返回结果中的第一行$column=$command->queryColumn(); // 查询并返回结果中的第一列$value=$command->queryScalar(); // 查询并返回结果中第一行的第一个字段
获取查询结果
在 CDbCommand::query() 生成 CDbDataReader 实例之后,可以通过重复调用 CDbDataReader::read() 获取结果中的行。你也可以在 PHP 的 foreach
语言结构中使用 CDbDataReader 一行行检索数据。
$dataReader=$command->query();// 重复调用 read() 直到它返回 falsewhile(($row=$dataReader->read())!==false) { ... }// 使用 foreach 遍历数据中的每一行foreach($dataReader as $row) { ... }// 一次性提取所有行到一个数组$rows=$dataReader->readAll();
queryXXX()
方法会直接返回数据。 例如, queryRow() 会返回代表查询结果第一行的一个数组。使用事务
当一个应用要执行几条查询,(每条查询要从数据库中读取/或向数据库中写入信息时) 保证所有sql语句都执行很重要。 事务,在 Yii 中表现为 CDbTransaction 实例,可能会在下面的情况中启动:
- 开始事务.
- 一个个执行查询。任何对数据库的更新对外界不可见。
- 提交事务。如果事务成功,更新变为可见。
- 如果查询中的一个失败,整个事务回滚。
上述工作流可以通过如下代码实现:
$transaction=$connection->beginTransaction();try{ $connection->createCommand($sql1)->execute(); $connection->createCommand($sql2)->execute(); //.... other SQL executions $transaction->commit(); //提交事务}catch(Exception $e){ $transaction->rollBack();//回滚事务}
绑定参数(即是变量)
要避免 SQL 注入攻击 并提高重复执行的 SQL 语句的效率, 你可以 "准备(prepare)"一条含有可选参数占位符的 SQL 语句,在参数绑定时,这些占位符将被替换为实际的参数。
参数占位符可以是命名的 (表现为一个唯一的标记) 或未命名的 (表现为一个问号)。调用 CDbCommand::bindParam() 或CDbCommand::bindValue() 以使用实际参数替换这些占位符。 这些参数不需要使用引号引起来:底层的数据库驱动会为你搞定这个。 参数绑定必须在 SQL 语句执行之前完成。
// 一条带有两个占位符 ":username" 和 ":email"的 SQL
$sql="INSERT INTO tbl_user (username, email) VALUES(:username,:email)"; $command=$connection->createCommand($sql);// 用实际的用户名替换占位符 ":username" $command->bindParam(":username",$username,PDO::PARAM_STR);// 用实际的 Email 替换占位符 ":email" $command->bindParam(":email",$email,PDO::PARAM_STR); $command->execute();// 使用新的参数集插入另一行 $command->bindParam(":username",$username2,PDO::PARAM_STR);$command->bindParam(":email",$email2,PDO::PARAM_STR);$command->execute();
方法 bindParam() 和 bindValue() 非常相似。唯一的区别就是前者使用一个 PHP 变量绑定参数, 而后者使用一个值。对于那些内存中的大数据块参数,处于性能的考虑,应优先使用前者。
绑定列
当获取查询结果时,你也可以使用 PHP 变量绑定列。 这样在每次获取查询结果中的一行时就会自动使用最新的值填充。
$sql="SELECT username, email FROM tbl_user";$dataReader=$connection->createCommand($sql)->query();// 使用 $username 变量绑定第一列 (username) $dataReader->bindColumn(1,$username);// 使用 $email 变量绑定第二列 (email) $dataReader->bindColumn(2,$email);while($dataReader->read()!==false){ // $username 和 $email 含有当前行中的 username 和 email }
使用表前缀(区分作用)
从版本 1.1.0 起, Yii 提供了集成了对使用表前缀的支持。 表前缀是指在当前连接的数据库中的数据表的名字前面添加的一个字符串。 它常用于共享的服务器环境,这种环境中多个应用可能会共享同一个数据库,要使用不同的表前缀以相互区分。 例如,一个应用可以使用 tbl_
作为表前缀而另一个可以使用 yii_
要使用表前缀,配置 CDbConnection::tablePrefix 属性为所希望的表前缀。 然后,在 SQL 语句中使用 {{TableName}}
代表表的名字,其中的 TableName
是指不带前缀的表名。 例如,如果数据库含有一个名为 tbl_user
的表,而 tbl_
被配置为表前缀,那我们就可以使用如下代码执行用户相关的查询:
$sql='SELECT * FROM {{user}}'; //对应表tbl_user
$users=$connection->createCommand($sql)->queryAll();
2.Active Record
简单起见,我们使用下面的数据表作为此节中的例子。注意,如果你使用 MySQL 数据库,你应该将下面的 SQL 中的 AUTOINCREMENT
替换为 AUTO_INCREMENT
。
CREATE TABLE tbl_post ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, title VARCHAR(128) NOT NULL, content TEXT NOT NULL, create_time INTEGER NOT NULL);
db
的应用组件,或者如果你想使用 AR 处理多个数据库,你应该覆盖CActiveRecord::getDbConnection()。 CActiveRecord 类是所有 AR 类的基类。定义 AR 类
要访问一个数据表,我们首先需要通过集成 CActiveRecord 定义一个 AR 类。 每个 AR 类代表一个单独的数据表,一个 AR 实例则代表那个表中的一行。 如下例子演示了代表 tbl_post
表的 AR 类的最简代码:
class Post extends CActiveRecord{ public static function model($className=__CLASS__) { return parent::model($className); } //认情况下,AR 类的名字和数据表的名字相同。如果不同,请覆盖 tableName() 方法。 model() 方法为每个 AR 类声明为如此(稍后解释) public function tableName() { return 'tbl_post'; }}
tbl_post
表中插入一个新行$post=new Post;
//从未在Post
类中显式定义属性title
,但通过上述代码访问。 因为title
是tbl_post
表中的一个列,CActiveRecord 通过PHP的__get()
魔术方法使其成为一个可访问的属性$post->title='sample post'; //如果我们尝试以同样的方式访问一个不存在的列,将会抛出一个异常$post->content='post body content';$post->save();
AR 依靠表中良好定义的主键。如果一个表没有主键,则必须在相应的 AR 类中通过如下方式覆盖 primaryKey()
方法指定哪一列或哪几列作为主键。
public function primaryKey(){ return 'id'; // 对于复合主键,要返回一个类似如下的数组 // return array('pk1', 'pk2');}
创建记录
要向数据表中插入新行,我们要创建一个相应 AR 类的实例,设置其与表的列相关的属性,然后调用 save() 方法完成插入:
$post=new Post;$post->title='sample post';$post->content='content for the sample post';$post->create_time=time();$post->save();
如果表的主键是自增的,在插入完成后,AR 实例将包含一个更新的主键。在上面的例子中, id
属性将反映出新插入帖子的主键值,即使我们从未显式地改变它。
如果一个列在表结构中使用了静态默认值(例如一个字符串,一个数字)定义。则 AR 实例中相应的属性将在此实例创建时自动含有此默认值。改变此默认值的一个方式就是在 AR 类中显示定义此属性:
class Post extends CActiveRecord{ public $title='please enter a title'; ......} $post=new Post;echo $post->title; // 这儿将显示: please enter a title
从版本 1.0.2 起,记录在保存(插入或更新)到数据库之前,其属性可以赋值为 CDbExpression 类型。 例如,为保存一个由 MySQL 的 NOW()
函数返回的时间戳,我们可以使用如下代码:
$post=new Post;$post->create_time=new CDbExpression('NOW()');// $post->create_time='NOW()'; 不会起作用,因为// 'NOW()' 将会被作为一个字符串处理。$post->save();
提示: 由于 AR 允许我们无需写一大堆 SQL 语句就能执行数据库操作, 我们经常会想知道 AR 在背后到底执行了什么 SQL 语句。这可以通过开启 Yii 的 日志功能 实现。例如,我们在应用配置中开启了 CWebLogRoute ,我们将会在每个网页的最后看到执行过的 SQL 语句。 从 1.0.5 版本起,我们可以在应用配置中设置CDbConnection::enableParamLogging 为 true ,这样绑定在 SQL 语句中的参数值也会被记录。
读取记录
条件的结果中的第一行$post=Post::model()->find($condition,$params);
// 查找具有指定主键值的那一行 $post=Post::model()->findByPk($postID,$condition,$params);// 查找具有指定属性值的行 $post=Post::model()->findByAttributes($attributes,$condition,$params);// 通过指定的 SQL 语句查找结果中的第一行 $post=Post::model()->findBySql($sql,$params);
如上所示,通过 Post::model()
调用 find
方法。 静态方法 model()
是每个 AR 类所必须的。 此方法返回在对象上下文中的一个用于访问类级别方法(类似于静态类方法的东西)的 AR 实例。
如果 find
方法找到了一个满足查询条件的行,它将返回一个 Post
实例,实例的属性含有数据表行中相应列的值。 然后就可以像读取普通对象的属性那样读取载入的值,例如 echo $post->title;
。
如果使用给定的查询条件在数据库中没有找到任何东西, find
方法将返回 null 。
调用 find
时,我们使用 $condition
和 $params
指定查询条件。此处 $condition
可以是 SQL 语句中的 WHERE
字符串,$params
则是一个参数数组,其中的值应绑定到 $condation
中的占位符。例如:
// 查找 postID=10 的那一行 $post=Post::model()->find('postID=:postID', array(':postID'=>10));
注意: 在上面的例子中,我们可能需要在特定的 DBMS 中将
postID
列的引用进行转义。 例如,如果我们使用 PostgreSQL,我们必须将此表达式写为"postID"=:postID
,因为 PostgreSQL 在默认情况下对列名大小写不敏感。
我们也可以使用 $condition
指定更复杂的查询条件。 不使用字符串,我们可以让 $condition
成为一个 CDbCriteria 的实例,它允许我们指定不限于 WHERE
的条件。 例如:
$criteria=new CDbCriteria;$criteria->select='title'; // 只选择 'title' 列$criteria->condition='postID=:postID';$criteria->params=array(':postID'=>10);$post=Post::model()->find($criteria); // $params 不需要了
注意,当使用 CDbCriteria 作为查询条件时,$params
参数不再需要了,因为它可以在 CDbCriteria 中指定,就像上面那样。
一种替代 CDbCriteria 的方法是给 find
方法传递一个数组。 数组的键和值各自对应标准(criterion)的属性名和值,上面的例子可以重写为如下:
$post=Post::model()->find(array( 'select'=>'title', 'condition'=>'postID=:postID', 'params'=>array(':postID'=>10),));
信息: 当一个查询条件是关于按指定的值匹配几个列时,我们可以使用 findByAttributes()。我们使
$attributes
参数是一个以列名做索引的值的数组。在一些框架中,此任务可以通过调用类似findByNameAndTitle
的方法实现。虽然此方法看起来很诱人, 但它常常引起混淆,冲突和比如列名大小写敏感的问题。
当有多行数据匹配指定的查询条件时,我们可以通过下面的 findAll
方法将他们全部带回。 每个都有其各自的 find
方法,就像我们已经讲过的那样。
// 查找满足指定条件的所有行$posts=Post::model()->findAll($condition,$params);// 查找带有指定主键的所有行$posts=Post::model()->findAllByPk($postIDs,$condition,$params);// 查找带有指定属性值的所有行$posts=Post::model()->findAllByAttributes($attributes,$condition,$params);// 通过指定的SQL语句查找所有行$posts=Post::model()->findAllBySql($sql,$params);
如果没有任何东西符合查询条件,findAll
将返回一个空数组。这跟 find
不同,find
会在没有找到什么东西时返回 null。
除了上面讲述的 find
和 findAll
方法,为了方便,(Yii)还提供了如下方法:
// 获取满足指定条件的行数$n=Post::model()->count($condition,$params);// 通过指定的 SQL 获取结果行数$n=Post::model()->countBySql($sql,$params);// 检查是否至少有一行复合指定的条件$exists=Post::model()->exists($condition,$params);
更新记录
在 AR 实例填充了列的值之后,我们可以改变它们并把它们存回数据表。
$post=Post::model()->findByPk(10);$post->title='new post title';$post->save(); // 将更改保存到数据库
正如我们可以看到的,我们使用同样的 save() 方法执行插入和更新操作。 如果一个 AR 实例是使用 new
操作符创建的,调用save() 将会向数据表中插入一行新数据; 如果 AR 实例是某个 find
或 findAll
方法的结果,调用 save() 将更新表中现有的行。 实际上,我们是使用 CActiveRecord::isNewRecord 说明一个 AR 实例是不是新的。
直接更新数据表中的一行或多行而不首先载入也是可行的。 AR 提供了如下方便的类级别方法实现此目的:
// 更新符合指定条件的行Post::model()->updateAll($attributes,$condition,$params);// 更新符合指定条件和主键的行Post::model()->updateByPk($pk,$attributes,$condition,$params);// 更新满足指定条件的行的计数列Post::model()->updateCounters($counters,$condition,$params);
在上面的代码中, $attributes
是一个含有以 列名作索引的列值的数组; $counters
是一个由列名索引的可增加的值的数组;$condition
和 $params
在前面的段落中已有描述。
删除记录
如果一个 AR 实例被一行数据填充,我们也可以删除此行数据。
$post=Post::model()->findByPk(10); // 假设有一个帖子,其 ID 为 10$post->delete(); // 从数据表中删除此行
注意,删除之后, AR 实例仍然不变,但数据表中相应的行已经没了。
使用下面的类级别代码,可以无需首先加载行就可以删除它。
// 删除符合指定条件的行Post::model()->deleteAll($condition,$params);// 删除符合指定条件和主键的行Post::model()->deleteByPk($pk,$condition,$params);
数据验证
当插入或更新一行时,我们常常需要检查列的值是否符合相应的规则。 如果列的值是由最终用户提供的,这一点就更加重要。总体来说,我们永远不能相信任何来自客户端的数据。
当调用 save() 时, AR 会自动执行数据验证。 验证是基于在 AR 类的 rules() 方法中指定的规则进行的。 关于验证规则的更多详情,请参考 声明验证规则 一节。 下面是保存记录时所需的典型的工作流。
if($post->save()){ // 数据有效且成功插入/更新}else{ // 数据无效,调用 getErrors() 提取错误信息}
当要插入或更新的数据由最终用户在一个 HTML 表单中提交时,我们需要将其赋给相应的 AR 属性。 我们可以通过类似如下的方式实现:
$post->title=$_POST['title'];$post->content=$_POST['content'];$post->save();
如果有很多列,我们可以看到一个用于这种复制的很长的列表。 这可以通过使用如下所示的 attributes 属性简化操作。 更多信息可以在 安全的特性赋值 一节和 创建动作 一节找到。
// 假设 $_POST['Post'] 是一个以列名索引列值为值的数组$post->attributes=$_POST['Post'];$post->save();
对比记录
类似于表记录,AR 实例由其主键值来识别。 因此,要对比两个 AR 实例,假设它们属于相同的 AR 类, 我们只需要对比它们的主键值。 然而,一个更简单的方式是调用 CActiveRecord::equals()。
信息: 不同于 AR 在其他框架的执行, Yii 在其 AR 中支持多个主键. 一个复合主键由两个或更多字段构成。相应地, 主键值在 Yii 中表现为一个数组. primaryKey 属性给出了一个 AR 实例的主键值。
自定义
CActiveRecord 提供了几个占位符方法,它们可以在子类中被覆盖以自定义其工作流。
- beforeValidate 和
beforeSave 和 afterSave: 这两个将在保存 AR 实例之前和之后被调用。
beforeDelete 和 afterDelete: 这两个将在一个 AR 实例被删除之前和之后被调用。
afterConstruct: 这个将在每个使用
new
操作符创建 AR 实例后被调用。beforeFind: 这个将在一个 AR 查找器被用于执行查询(例如
find()
,findAll()
)之前被调用。 1.0.9 版本开始可用。afterFind: 这个将在每个 AR 实例作为一个查询结果创建时被调用。
使用 AR 处理事务
每个 AR 实例都含有一个属性名叫 dbConnection ,是一个 CDbConnection 的实例,这样我们可以在需要时配合 AR 使用由 Yii DAO 提供的 事务 功能:
$model=Post::model();$transaction=$model->dbConnection->beginTransaction();try{ // 查找和保存是可能由另一个请求干预的两个步骤 // 这样我们使用一个事务以确保其一致性和完整性 $post=$model->findByPk(10); $post->title='new post title'; $post->save(); $transaction->commit();}catch(Exception $e){ $transaction->rollBack();}
命名范围
Note: 对命名范围的支持从版本 1.0.5 开始。 命名范围的最初想法来源于 Ruby on Rails.
命名范围(named scope) 表示一个 命名的(named) 查询规则,它可以和其他命名范围联合使用并应用于 Active Record 查询。
命名范围主要是在 CActiveRecord::scopes() 方法中以名字-规则对的方式声明。 如下代码在 Post
模型类中声明了两个命名范围, published
和 recently
。
class Post extends CActiveRecord{ ...... public function scopes() { return array( 'published'=>array( 'condition'=>'status=1', ), 'recently'=>array( 'order'=>'create_time DESC', 'limit'=>5, ), ); }}
每个命名范围声明为一个可用于初始化 CDbCriteria 实例的数组。 例如,recently
命名范围指定 order
属性为create_time DESC
, limit
属性为 5。他们翻译为查询规则后就会返回最近的5篇帖子。
命名范围多用作 find
方法调用的修改器。 几个命名范围可以链到一起形成一个更有约束性的查询结果集。例如, 要找到最近发布的帖子, 我们可以使用如下代码:
$posts=Post::model()->published()->recently()->findAll();
总体来说,命名范围必须出现在一个 find
方法调用的左边。 它们中的每一个都提供一个查询规则,并联合到其他规则, 包括传递给 find
方法调用的那一个。 最终结果就像给一个查询添加了一系列过滤器。
从版本 1.0.6 开始,命名范围也可用于 update
和 delete
方法。 例如,如下代码将删除所有最近发布的帖子:
Post::model()->published()->recently()->delete();
注意: 命名范围只能用于类级别方法。也就是说,此方法必须使用
ClassName::model()
调用。
参数化的命名范围
命名范围可以参数化。例如, 我们想自定义 recently
命名范围中指定的帖子数量,要实现此目的,不是在CActiveRecord::scopes 方法中声明命名范围, 而是需要定义一个名字和此命名范围的名字相同的方法:
public function recently($limit=5){ $this->getDbCriteria()->mergeWith(array( 'order'=>'create_time DESC', 'limit'=>$limit, )); return $this;}
然后,我们就可以使用如下语句获取3条最近发布的帖子。
$posts=Post::model()->published()->recently(3)->findAll();
上面的代码中,如果我们没有提供参数 3,我们将默认获取 5 条最近发布的帖子。
默认的命名范围
模型类可以有一个默认命名范围,它将应用于所有 (包括相关的那些) 关于此模型的查询。例如,一个支持多种语言的网站可能只想显示当前用户所指定的语言的内容。 因为可能会有很多关于此网站内容的查询, 我们可以定义一个默认的命名范围以解决此问题。 为实现此目的,我们覆盖 CActiveRecord::defaultScope 方法如下:
class Content extends CActiveRecord{ public function defaultScope() { return array( 'condition'=>"language='".Yii::app()->language."'", ); }}
现在,如果下面的方法被调用,将会自动使用上面定义的查询规则:
$contents=Content::model()->findAll();
注意,默认的命名范围只会应用于 SELECT
查询。INSERT
, UPDATE
和 DELETE
查询将被忽略。
验证和授权
Yii::app()->user
在任何地方访问定义身份类
不同的类可能实现不同的验证方式(例如:OpenID,LDAP)。最好是继承 CUserIdentity,此类是居于用户名和密码的验证方式。定义身份类的主要工作是实现IUserIdentity::authenticate方法。在用户会话中根据需要,身份类可能需要定义别的身份信息
getId
函数来返回验证过程中获得的_id
变量(缺省的实现则是返回用户名)。title
信息存成一个状态登录和注销
使用身份类和用户部件,我们方便的实现登录和注销。
// 使用提供的用户名和密码登录用户$identity=new UserIdentity($username,$password);if($identity->authenticate()) Yii::app()->user->login($identity);else echo $identity->errorMessage;......// 注销当前用户Yii::app()->user->logout();
基于Cookie 的登录
授权处理结果
访问控制过滤器
概览(Overview)
配置授权管理器
定义授权等级体系
使用业务规则
权限检查
使用默认角色
Yii::app() 返回在index.php中创建的CWebApplication实例
主要负责一些全局性的功能模块
比如Yii::app()->getUser()返回的是CWebUser实例(用于表达当前用户的验证信息)
在index.php使用的配置文件,其实质就是对Yii::app()进行属性的初始化
关于CComponent的说明,这是Yii的基石
Yii::app()->ComponentID
,其中的 ComponentID
是指组件的ID(例如Yii::app()->cache
)。- $trans = Yii::app()->db->beginTransaction();
- try {
- $manufacturer = new Manufacturer();
- $manufacturer->name = $name;
- $manufacturer->email = $email;
- $manufacturer->save();
- $trans->commit();
- } catch (Exception $e) {
- $trans->rollback();
- $this->response(array('status' => 1, 'msg' => $e->getMessage()));
- }
$transaction=$connection->beginTransaction();try{ $connection->createCommand($sql1)->execute(); $connection->createCommand($sql2)->execute(); //.... other SQL executions $transaction->commit();}catch(Exception $e) // 如果有一条查询失败,则会抛出异常{ $transaction->rollBack();}
杂谈:
IAuthManager->createAuthItem
\
CApplicationComponent
\
CAuthManager->createRole call createAuthItem
\
CDbAuthManager ->implents createAuthItem
- YII读书笔记
- Yii 框架读书笔记(一)
- Yii
- yii
- Yii
- yii
- yii
- yii
- Yii
- yii
- yii
- yii
- yii
- YII
- Yii
- Yii
- Yii
- Yii
- swt中的table单元格渐变颜色
- C++拷贝构造函数详解
- Oracle工具orachk使用说明
- 心累的时候,给自己放个假,给自己一个欣赏世界的理由。
- Java JNI介绍及JNI在Domino中的使用
- YII读书笔记
- 将django部署到Apache服务器
- ios-ios 7之前和之后关于状态栏隐藏的介绍
- 13. 枚举
- NDK Application.mk使用手册
- 子序列和最大问题
- c++中sort()及qsort()的用法总结
- c++ const char* to const void
- 【DP】 HDOJ 1081 To The Max