#基础
##入口脚本
WEB 应用一般为 index.php
, 控制台应用一般为 yii.php
并在文件开头加上 #! /usr/bin/env php
入口脚本是定义全局常量的好地方
支持三个常量: YII_DEBUG
, YII_ENV
, YII_ENABLE_ERROR_HANDLER
WEB:
1 |
|
控制台:
1 | #!/usr/bin/env php |
##应用主体
创建: Yii::createWebApplication($configFile)
访问: 可以在任何地方使用 Yii::app()|YiiBase::app
访问
##控制器
控制器路由格式: moduleID/controllerID/actionID
控制器创建决策步骤:
- 如果指定了
CWebApplication::catchAllRequest
, 用户指定的 ID 将被忽略. (通常用于设置应用为维护状态, 显示一个静态页面) - 如果在
CWebApplication::controllerMap
中找到 ID, 相应的控制器配置则被用于创建控制器 - 如果 ID 为
path/to/xyz
形式, 则按控制器路由格式解析并创建
创建:
-
默认控制器在
CWebApplication::defaultController
中定义, 默认动作为index
, 对应的方法名为actionIndex
, 可通过CController::defaultAction
修改1
2
3
4
class SiteController extends CController {
} -
也可以由一个动作类来定义动作, 以便重用动作:
1
2
3
4
5
6
7
class UpdateAction extends CAction {
public function run() {
// place the action logic here
}
} -
然后需覆盖控制器类的
actions
方法:1
2
3
4
5
6
7
8
9
class PostController extends CController {
public function actions() {
return array(
'edit'=>'application.controllers.post.UpdateAction',
);
}
}
动作参数绑定:
1 |
|
过滤器可被配置在动作执行之前或之后执行, 如访问控制过滤器, 性能过滤器(参见访问控制过滤器)
-
定义:
- 可被定义为一个
filter
前缀的控制器方法:
1
2
3
4
5
public function filterAccessControl($filterChain) {
// 调用 $filterChain->run() 以继续后续过滤器与动作的执行。
}- 也可是一个
CFilter
或其子类的实例:
1
2
3
4
5
6
7
8
9
10
11
12
class PerformanceFilter extends CFilter {
protected function preFilter($filterChain) {
// 动作被执行之前应用的逻辑
return true; // 如果动作不应被执行,此处返回 false
}
protected function postFilter($filterChain) {
// 动作执行之后应用的逻辑
}
} - 可被定义为一个
-
配置使用: 需要覆盖控制器的
filter()
方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
class PostController extends CController {
......
public function filters() {
return array(
'postOnly + edit, create', // 使用 filter 前缀方法定义的过滤器
array( // 使用类定义的过滤器
'application.filters.PerformanceFilter - edit, create',
'unit'=>'second',
),
);
}
}
##模型
Yii 实现了两种类型的模型: 表单模型和 Active Record, 二者都继承于 CModel
如果用户输入被收集然后丢弃, 应该创建一个表单模型; 如果用户输入被收集后要保存到数据库, 应使用一个 Active Record
##视图
CController::render('edit')
将会在 protected/views/ControllerID
目录下寻找 edit.php
视图文件
可以通过在视图中使用 $this->propertyName
访问控制器的任何属性, 也可以在控制器中将数据传递到视图中:
1 |
|
protected/views/layouts/main.php
是默认的布局
文件, 可通过 CWebApplication::layout
自定义. 要渲染一个不带布局的视图, 需调用 CController::renderPartial
小物件
是 CWidget
或其子类的实例, 它也可以有自己的视图文件
-
定义
1
2
3
4
5
6
7
8
9
10
11
class MyWidget extends CWidget {
public function init() {
// 此方法会被 CController::beginWidget() 调用
}
public function run() {
// 此方法会被 CController::endWidget() 调用
}
} -
按如下视图脚本来使用一个小物件:
1
2
3
4
5$this->beginWidget('path.to.WidgetClass', $config);
...可能会由小物件获取的内容主体...
$this->endWidget();
// 或
$this->widget('path.to.WidgetClass', $config);
系统视图用于展示 Yii 的错误和日志消息, 如如果 CHttpException 抛出一个 404 错误, 那么 error404
就会被展示. Yii 在 framework/views
下提供了默认的系统视图, 也可以通过在 protected/views/system
下创建同名视图文件进行自定义
##组件
加载: 通过配置应用的 components|CApplication::components
属性
- 可以配置
enabled
为false
禁用组件 - 组件是按需创建的, 但是可以将组件 ID 列入应用的
preload|CWebApplication::preload
属性中强制其加载
访问: Yii::app()->ComponentID
预定义的核心应用组件:
- assetManager
- authManager
- cache
- clientScript
- coreMessage
- db
- errorHandler
- format
- messages
- request
- securityManager
- session
- statePersister
- urlManager
- user
- themeManager
组件属性
- 可以通过直接定义一个公共成员变量定义
- 也可以使用 getter 和 setter 更灵活的定义
- 通过 getter 和 setter 定义的属性和类成员变量有个区别: 他们是不区分大小写的
组件事件
-
定义组件事件(
on
开头)1
2
3
4
5
public function onClicked($event) {
$this->raiseEvent('onClicked', $event);
} -
定义事件回调
1
2
3
4
5
function callbackName($event) {
......
} -
绑定事件回调
1
2
3
4
5
6
$component->onClicked=$callback;
// 或使用匿名函数
$component->onclicked=function($event) {
}
组件行为
-
行为类必须实现
IBehavior
-
大多数行为可继承自
CBehavior
, 如果行为需要绑定到模型, 则也可以继承自CModelBehavior
或CActiveRecordBehavior
-
两个同名行为绑定到同一个组件下是有可能的, 在这种情况下, 先绑定的行为则拥有优先权
-
当和 events, 一起使用时, 行为会更加强大. 当行为被绑定到组件时,行为里的一些方法就可以绑定到组件的一些事件上了. 这样一来,行为就有机观察或者改变组件的常规执行流程
-
一个行为的属性也可以通过绑定到的组件来访问. 这些属性包含公共成员变量以及通过 getters 和/或 setters 方式设置的属性. 例如, 若一个行为有一个
xyz
的属性,此行为被绑定到组件$a
, 然后我们可以使用表达式$a->xyz
访问此行为的属性 -
绑定行为:
1
2
3
4
5
6
// $name 在组件中实现了对行为的唯一识别
$component->attachBehavior($name,$behavior);
// test() 是行为中的方法。
$component->test(); -
访问行为:
1
2
3
4
5
$behavior=$component->tree;
// 等于下行代码:
// $behavior=$component->asa('tree'); -
禁用行为:
1
2
3
4
5
6
7
8
$component->disableBehavior($name);
// 下面的代码将抛出一个异常
$component->test();
$component->enableBehavior($name);
// 现在就可以使用了
$component->test();
##模块
模块是一个独立的软件单元,它包含 模型, 视图, 控制器 和其他支持的组件. 如 forum
模块的典型目录结构
forum/
ForumModule.php 模块类文件
components/ 包含可复用的用户组件
views/ 包含小物件的视图文件
controllers/ 包含控制器类文件
DefaultController.php 默认的控制器类文件
extensions/ 包含第三方扩展
models/ 包含模块类文件
views/ 包含控制器视图和布局文件
layouts/ 包含布局文件
default/ 包含 DefaultController 的视图文件
index.php 首页视图文件
模块可以嵌套
使用模块:
-
继承 CWebModule, 并命名为 ucfirst($id).‘Module’
-
将模块目录放入
modules
目录中, 然后在应用的modules
配置 属性中声明模块 ID. 模块也可以在配置是带有初始属性值 -
使用 CController::module 访问
##路径别名和名字空间
YiiBase::getPathOfAlias()
获取别名的真实路径, YiiBase::setPathOfAlias()
设置新的别名的真实路径
预定义的根别名
- system
- zii
- application
- webroot
- ext
- 模块 ID
使用别名导入类: Yii::import('system.web.CController')
. (所有核心类已被预先导入)
预导入: 在 CWebApplication::run()
之前执行:
1 |
|
导入目录: Yii::import('system.web.*');
#使用表单
##创建模型
1 |
|
1 |
|
- 验证是基于
场景(scenario)
的 - 可以手动调用
CModel::validate()
触发; 对于CActiveRecord
, 会在CAcitveRecord::save()
时自动触发验证 - 验证错误可以使用
CModel::getError()
或CModel::getErrors()
获取
标签
CModel
默认将返回特性的名字作为其标签- 也可使用
CModel::attributesLabels
方法自定义标签
##创建动作
1 |
|
##创建视图
1 | <div class="form"> |
##收集表格输入(批量)
action:
1 |
|
view:
1 | <div class="form"> |
##使用表单生成器 @todo
action:
1 |
|
protected/views/site/loginForm.php:
1 |
|
view:
1 | <h1>Login</h1> |
#使用数据库
##数据访问对象(DAO)
建立数据库连接:
- 使用
CDbConnection
:
1 |
|
- 作为应用组件配置, 然后使用
Yii::app()->db
访问
1 |
|
##执行 SQL 语句
-
创建
CDbCommand
实例1
2
3
4
5
6
7
8
$connection=Yii::app()->db; // 假设你已经建立了一个 "db" 连接
// 如果没有,你可能需要显式建立一个连接:
// $connection=new CDbConnection($dsn,$username,$password);
$command=$connection->createCommand($sql);
// 如果需要,此 SQL 语句可通过如下方式修改:
// $command->text=$newSQL; -
使用以下方法执行语句
1
2
3
4
5
6
7
8
$rowCount=$command->execute(); // 执行无查询 SQL(Insert, delete, update)
$dataReader=$command->query(); // 执行一个 SQL 查询(select), 返回 CDbDataReader 实例
$rows=$command->queryAll(); // 查询并返回结果中的所有行
$row=$command->queryRow(); // 查询并返回结果中的第一行
$column=$command->queryColumn(); // 查询并返回结果中的第一列
$value=$command->queryScalar(); // 查询并返回结果中第一行的第一个字段 -
获取查询结果
1
2
3
4
5
6
7
8
9
$dataReader=$command->query();
// 重复调用 read() 直到它返回 false
while(($row=$dataReader->read())!==false) { ... }
// 或使用 foreach 遍历数据中的每一行
foreach($dataReader as $row) { ... }
// 一次性提取所有行到一个数组
$rows=$dataReader->readAll(); -
使用事务
1
2
3
4
5
6
7
8
9
10
11
$transaction=$connection->beginTransaction();
try {
$connection->createCommand($sql1)->execute();
$connection->createCommand($sql2)->execute();
//.... other SQL executions
$transaction->commit();
} catch(Exception $e) { // 如果有一条查询失败,则会抛出异常
$transaction->rollBack();
} -
使用 Prepare Statment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 一条带有两个占位符 ":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(); -
绑定结果列
1
2
3
4
5
6
7
8
9
10
11
$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
} -
使用表前缀
配置
CDbConnection::tablePrefix
属性为所希望的表前缀, 然后便可以在 SQL 语句中使用代表表的名字
##查询构建器
查询构建器构建于一个 CDbCommand
实例上
查询构建器不能用于修改一个已经存在的 SQL 查询
可用的查询构建器示例:
1 |
|
也可通过使用属性赋值方式:
1 |
|
或在创建 CDbCommand
是传配置参数的方式构建:
1 |
|
构建完成后, 可以使用在执行 SQL 语句中讲到的方法执行之; 也可使用 CDbCommand::getText()
获取最后构建完工后的 SQL 语句, 绑定的参数被保存在 CDbCommand::params
中
同一个 CDbCommand
实例可用于多次构建不同的查询, 但是记得要再另一次之前调用 CDbCommand::reset()
以清理上次的查询
每个 AR 类代表一个数据表(或视图), 数据表(或视图)的列在 AR 类中体现为类的属性, 一个 AR 实例则表示表中的一行
最佳应用是模型化数据表为 PHP 结构和执行不包含复杂 SQL 语句的查询. 对于复杂查询的场景, 应使用 Yii DAO
如果你数据库的表结构很少改动, 你应该通过配置 CDbConnection::schemaCachingDuration
属性的值为一个大于零的值开启表结构缓存
通过 AR 使用多个数据库有两种方式. 如果数据库的结构不同, 你可以创建不同的 AR 基类实现不同的 getDbConnection()
; 否则, 动态改变静态变量 CActiveRecord::db
是一个好主意
由于 AR 类经常在多处被引用, 我们可以导入包含 AR 类的整个目录, 而不是一个个导入. 见路径别名和命名空间
通过 Yii 的日志功能, 可以查看 AR 在背后到底执行了那些语句
定义 AR 类:
1 |
|
创建记录:
- 如果表的主键是自增的, 在插入完成后, AR 实例将包含一个更新的主键
1 |
|
读取记录:
find
系列返回一个 AR 实例, 或者null
findAll
系列返回 AR 实例数组, 或者空数组
1 |
|
更新记录:
-
如果一个 AR 实例是使用 new 操作符创建的, 调用
CActiveRecord::save
将会向数据表中插入一行新数据; 如果 AR 实例是某个 find 或 findAll 方法的结果, 调用CActiveRecord::save
将更新表中现有的行. 实际上, 我们是使用CActiveRecord::isNewRecord
说明一个 AR 实例是不是新的 -
直接更新数据表中的一行或多行而不首先载入也是可行的:
1 |
|
删除记录:
- 实例化后删除: 这样删除之后, AR 实例仍不改变
1 |
|
- 不实例化直接删除
1 |
|
如果要确定两个 AR 是否是同一个记录, 只需对比它们的主键值, 或直接调用 CActiveRecord::equals()
通过以下几个占位符方法, 可以自定义 AR 的工作流:
占位符方法 | 含义 |
---|---|
beforeValidate, afterValidate | 在验证之前(后)执行 |
beforeSave, afterSave | 在保存 AR 实例之前(后)执行 |
beforeFind, afterFind | 在执行查询之前(后)执行 |
afterConstruct | 在 AR 实例化之后执行 |
事务处理, 参见使用事务
1 |
|
命名范围: 即查询时的过滤器
- 定义
1 |
|
- 使用
1 |
|
##关系型 Active Record
为了使用关系型 AR, 建议在关联的表中定义主键-外键约束
关系包括: BELONGS_TO
, HAS_MANY
, HAS_ONE
, MANY_MANY
, STAT
使用 STAT
关系已获取统计数据
适当使用 together
查询选项, 会加快查询速度
在 AR 查询中, 基础表的别名为 t
, 其他关联表的别名和关系的名称一样
声明关系
1 |
|
执行关联查询
1 |
|
使用命名空间
1 |
|
##数据库迁移 @todo
#专题
##验证
验证即核查一个人是否真实他声称的那个人(用户名, 密码); 授权即检查是否有权操作特定的资源
定义身份类:
1 |
|
登录和注销:
1 |
|
如果使用 cookie 登录, 要确保不要保存敏感信息到 State, 而是保存到持久存储(数据库) 上, 最好(参见安全):
- 当用户成功登录时, 保存同一个随机串到 cookie State 和数据库
- 在之后的的自动 cookie 登录时, 对比 cookie 中和数据库中的随机串是否一致
- 如果用户通过登录表单登录, 刷新这个随机串
##授权
访问控制过滤器的定义参见过滤器
过滤器定义之后, 还要通过重载 CController::accessRules
指定具体授权规则
1 |
|
如果授权失败
-
已经配置
CWebUser::loingUrl
, 则重定向到此 URL, 可以这样配置:1
2
3
4
5
6
7
8
9
10
11
array(
......
'components'=>array(
'user'=>array(
// 这实际上是默认值
'loginUrl'=>array('site/login'),
),
),
) -
否则显示一个 401 HTTP 例外
如果希望在用户登录成功后转到之前页面:
1 |
|
##基于角色的访问控制(Role-Based Access Control)
授权项目
可分为操作(operations)
, 任务(tasks)
和角色(roles)
一个角色由若干任务组成, 一个任务由若干操作组成, 而一个操作就是一个许可
, 不可再分. Yii 还允许一个角色包含其他角色或操作, 一个任务可以包含其他操作, 一个操作可以包括其他操作. 授权项目是通过它的名字唯一识别的
一个授权项目可能与一个业务规则
关联. 业务规则是一段 PHP 代码, 在进行涉及授权项目的访问检查时将会被执行. 仅在执行返回 true
时, 用户才会被视为拥有此授权项目所代表的权限许可
Yii 提供了两种授权管理器: CPhpAuthManager
和 CDbAuthManager
. 前者将授权数据存储在一个 PHP 脚本文件中而后者存储在数据库中. 配置 CWebApplication::authManager
应用组件时, 我们需要指定使用哪个授权管理器组件类, 以及所选授权管理器组件的初始化属性值:
1 |
|
然后, 我们便可以使用 Yii::app()->authManager
访问
定义授权等级体总共分三步
- 定义授权项目
CAuthManager::createRole
CAuthManager::createTask
CAuthManager::createOperation
- 建立授权项目之间的关系
CAuthManager::addItemChild
CAuthManager::removeItemChild
CAuthItem::addChild
CAuthItem::removeChild
- 分配角色给用户
CAuthManager::assign
CAuthManager::revoke
如:
1 |
|
权限检查:
1 |
|
默认角色就是一个隐式分配给每个用户的角色, 这些用户包括通过身份验证的用户和游客
配置:
1 |
|
定义:
1 |
|
##安全
-
XSS: 跨站脚本攻击
定义: 攻击者常常向易受攻击的 web 应用注入 JavaScript, VBScript, ActiveX, HTML 或 Flash 来迷惑访问者以收集访问者的信息
防范: 在显示用户输入的内容之前进行检查
1
2
3
4// 这里将 CHtmlPurifier 作为一个 Widget 来过滤用户输入
$this->beginWidget('CHtmlPurifier');
//...这里显示用户输入的内容...
$this->endWidget(); -
CSRF: 跨站请求伪造
定义: 攻击者在用户浏览器在访问恶意网站的时候, 让用户的浏览器向一个受信任的网站发起攻击者指定的请求
防范: GET 请求只允许检索数据而不能修改服务器上的任何数据, 而 POST 请求应当含有一些可以被服务器识别的随机数值, 用来保证表单数据的来源和运行结果发送的去向是相同的
1
2
3
4
5
6
7
8
9
10
11
// 启用 CsrfValidation 组件
// 该组件会自动在用 CHtml::form 生成的表单中嵌入一个保存随机值的隐藏项, 在表单提交的时候发送到服务器进行验证
return array(
'components'=>array(
'request'=>array(
'enableCsrfValidation'=>true,
),
),
); -
Cookie 攻击
定义: session ID 通常存储在 Cookie中, 如果攻击者窃取到了一个有效的 session ID, 他就可以使用这个 session ID 对应的 session 信息
防范:
- 您可以使用 SSL 来产生一个安全通道, 且只通过 HTTPS 连接来传送验证 cookie
- 设置 cookie 的过期时间, 对所有的 cookie 和 session 令牌也这样做
- 防范跨站代码攻击, 因为它可以在用户的浏览器触发任意代码, 这些代码可能会泄露用户的 cookie
- 在 cookie 有变动的时候验证 cookie 的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 启用 CookieValidation 组件
return array(
'components'=>array(
'request'=>array(
'enableCookieValidation'=>true,
),
),
);
// 然后只使用 CHttpRequest::cookies 进行 cookie 操作(而不是 $_COOKIES)
// 检索一个名为$name的cookie值
$cookie=Yii::app()->request->cookies[$name];
$value=$cookie->value;
......
// 设置一个cookie
$cookie=new CHttpCookie($name,$value);
Yii::app()->request->cookies[$name]=$cookie;