。。。。。。。。。。。。。。。。。。。。。。
分类: 系统运维
2011-03-14 22:34:18
一、用YII建一个真正的项目 -- stackstar
(一)目标:实战 -- 用YII开发一套项目开发生命周期的跟踪程序
(二)项目需求分析:
1) 项目开发准备用敏捷开发的方法,使用测试驱动开发完成。
2) 项目组成员可以CRUD项目和对应项目中出现的问题、bug和各项开发任务,非项目组成员只能登录或者注册。
3) 一个项目组的成员可以在项目下面建立各种事件,并指定某个项目组成员去解决对应;
4) 各种事件有:
3种状态:not yet started/started/finished,
3种类型:bugs/features/tasks
5) 由于我们的数据需要存储起来,有CRUD操作,所以我们选择MySQL数据库,并设置数据库的编码为UTF8.
6) 用户权限管理
7) 事件和用户相关联,也就是说项目组成员只能看到和他相关的事件
(三)开发分析
这个应用有三个主要的角色(域):user、project和issue
(四)角色分析
1) User -- 也就是项目组成员,分为已登录和未登录(访客)
2) 已登录用户可以做CRUD,为登录用户无法执行任何操作
3) 项目所有者(创建者)可以对自己的项目进行CRUD操作,并且可以添加新的成员进来,除创建者以外的项目组成员,可以在下面添加事件,并指派成员去对应各种问题
4) Project -- 新建的或者已经建好的项目,是本次开发的重点和基础。Project下面又分为各种小的模块,用来描述Project中各种事件
5) Issue -- 是Project下面的事件,包括:
Features -- 跟项目开发相关的事件,比如:开发一个用户登录模块
Tasks -- 跟项目开发无关,但是又在本开发项目范围之内的事件,比如:设置并建立开发环境
Bugs -- 项目开发中出现的错误和BUG,比如:无法验证用户输入是一个email地址
Issue分为3种状态:还没有开始,已经开始,已经完成
(五)工作流程
上图都能看明白吧,以下省略200字。
(六)定义数据结构和模型
上面的图定义了3个数据对象之间的关系,简单的说:
一个项目组成员有一个或者多个(HAS-MANY)项目,一个项目有一个或者多个成员(HAS-MANY),所以,project和user之间是MANY-TO-MANY的关系;
一个成员有0个或者多个事件(建立此事件的是requester),一个事件只属于一个成员,这个事件指派给谁,谁就是这个事件的owner。
一个项目下面有0个或者多个事件,而这个事件只属于这个特定的项目。
(七)建立应用
使用YIIC工具建立webapp,修改主配置main.php,连接到MySql数据库(数据库需要5.1+的版本)。
(八)测试数据连接
在protected/tests/unit下面新建一个类:
class DbConnectionTest extends CDbTestCase{
function testConn(){
$this->assertTure(true); //passed -- green
$this->assertNotEquals(NULL, Yii::app()->db); //failed -- red
}
}
(九)设置和连接数据库
/* 注释掉原来末日的sqlite连接,新建一个db
'db'=>array(
'connectionString' => 'sqlite:'.dirname(__FILE__).'/../data/testdrive.db',
),
// uncomment the following to use a MySQL database
*/
'db'=>array(
'connectionString' => 'mysql:host=localhost;dbname=trackstar_dev',
'emulatePrepare' => true,
'username' => 'root',
'password' => ' 数据库密码 ',
'charset' => 'utf8',
),
(十)YII 支持的数据库
YII的DAO(数据库访问对象)建立在PHP的PDO之上,使得数据库和应用程序相互独立,可以支持的数据库包括主流的:
Oracle -- oci:dbname=
MySQL -- mysql:dbname=trackstar;host=localhost; port=3306
PGSQL -- pgsql:dbname=trackstar;port=5432;host=localhost
Sqlite -- sqlite:/path/to/trackstar.db
(十一)
二、建模
模型对象之间是多对多的关系(many-to-many),应该建立至少3个表。
比如:user和project之间,一个用户属于一个或者多个项目,一个项目有一个或者多个项目成员,所以就应该建立tbl_user,tble_project,以及tbl_project_user这3个表来表示,另外,应该建立外键约束来保证删除和更新的时候达到同步。项目又会分好多小项目,比如:tbl_issue
建立外键,我们可以使用DDL语句,也可以使用sqlyog或者phpMyAdmin这种工具来实现。
注意事项:
l 表必须是InnoDB作为引擎,从而支持事务处理,不能用MyISAM;
l 用这种工具的时候,每次建立一个对应的外键,而不是多个;
三、mysql使用
MySQL的datetime设置当前时间为默认值
由于MySQL目前字段的默认值不支持函数,所以用:
create_time datetime default now()
的形式设置默认值是不可能的。
代替的方案是使用TIMESTAMP类型代替DATETIME类型。
CURRENT_TIMESTAMP :当我更新这条记录的时候,这条记录的这个字段不会改变。
CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP :当我更新这条记录的时候,这条记录的这个字段将会改变。即时间变为了更新时候的时间。(注意一个UPDATE设置一个列为它已经有的值,这将不引起TIMESTAMP列被更新,因为如果你设置一个列为它当前的值,MySQL为了效率而忽略更改。)如果有多个TIMESTAMP列,只有第一个自动更新。
四、小物件Widget
在试图View中,使用:
echo $form->errorSummary($model);
$form=$this->beginWidget('CActiveForm', array('id' => 'issue-form', 'enableAjaxVlidation' => false,));
...
$this->endWidget();
?>
来创建form标签,这样可以使用model中的特性(attributes)来填充表单中的值。
$form->labelEx($model, 'type_id') -- 表示label标签,$model表示当前的标签的显示内容和$model 中attributeLabels()中的对应;
$form->fieldText($model, 'type_id') -- 表示文本框,用户输入的值和model中的属性type_id对应,并且通过$model->name[type_id]的方式post值。用户输入的值会被rules拦截并进行验证,任何对type_id有效的验证,都会执行。
$form->error($model, 'type_id') -- 表示验证的错误提示
而$form->errorSumarry($model) 表示验证错误后的全部错误的集合。
五、在filter中设置获取上下文的PID并实现PID的获取方法
用户请求执行一个控制器中的方法之前或者之后执行的过滤器。
一个项目下面有固定的几个项目组成员,并不是所有成员。所以在项目下面建立事件的时候,如果要选择事件的发起人(requester)和对应人(owner),必然要知道这个项目的id,一旦项目确定了,那么发起人和对应人就可以在下拉列表中提供选择了。
但是YII的GII在生成代码的时候,并没有限制。
比如我们要在PID = 1的project下面建立一个事件,这个项目组的成员有A和B两个人,那么在下拉列表中就应该只有这两个人作为选择,而不是所有的成员。
那么,既然要知道项目的PID才可以在下面新建事件,我们应该在IssueController->actionCreate之前,就应该知道这个PID才可以,这个PID要么是post的,要么是GET的,反正必须知道才行。所以在IssueController的filter中添加如下的过滤:
1:首先要获取PID,如果PID不知道,无法完成这个操作
2:将跟这个PID相关的成员找出来。
首先在IssueController中的filter方法下面添加:
...
'projectContext + create' //表示在create一个新的事件之前,需要执行filterProjectContext过滤
接下来,在IssueController中添加两个个方法和一个属性
...
private $_project = null;
protected function loadProject($projectId){
if ($this->_project ===null){
$this->_project = Issue::model()->findByPk($projectId);
if ($this->_project === null){
trhrow new CHttpException(404, 'project is not exist.');
}
}
return $this->_project;
}
public function filterProjectContext($projectChian){
$projectId = null;
if (isset($_GET['pid'] )){
$projectId = $_GET['pid'];
}else if (isset($_POST['pid']) ){
$projectId = $_POST['pid'];
}
$this->loadProject($projectId);
}
这样,我们在新建一个事件的时候,就必须保证有一个PID存在了。
六、用户验证
(一)GII自动生成的验证机制比较简单,并非基于数据库,而是在程序里面写死的,只有(demo/demo和admin/admin)。要想实现比较好的验证机制,YII提供了一个验证接口,并在配置文件中可以配置,被称为:user,它是一个实现了IWebUser这个接口的对象。
(二)这个组件封装了当前用户的所有身份信息,例如:ID,name,returnUrl,loginUrl,allowAutoLogin等
(三)YII的验证类是独立的,一般我们基于数据库的用户名和密码验证,只需要实现UserIdentify类的authenticate方法(验证逻辑),就可以了。
(四)身份验证的过程:
1) 在Controller里,设置了accessRules为@或者用户名的方法,例如:
public function accessRules(){
return array(
array(
'allow', //允许执行的操作
'actions' => array('delete', 'update'),
'users' => array( 'admin'), //只允许登录用户admin执行
),
);
}
当触发访问条件时,如果当前用户没有登录(isGuest),就好转到SiteController/actionLogin,并把returnUrl 设置为当前要返回的页面地址。
2) 用户输入用户名和密码,提交后触发validation,也就是rules定义的数据检查,例如:
public function rules()
{
return array(
array('username, password', 'required'),
array('rememberMe', 'boolean'),
array('password', 'authenticate'),
);
}
3) 执行authenticate方法去验证密码。密码验证会新建一个UserIdentity实例,并以用户提交的数据填充,并调用UserIdentity的authenticate方法,如果密码验证通过,那么设置用户为登录用户。并返回之前要访问的URL。
实际上,YII密码验证的过程就在UserIdentity的authenticate方法中实现,所以,只要把验证过程放在这里就可以了。
private $_id;
public function authentication(){
$criteria = new CDbCriteria;
$criteria->condition = 'usernmae = :name';
$criteria->parmas = array(':name' => $this->username);
$user = User::model()->find($criteria);
if (NULL == $user){
$this->errorCode = self::ERROR_USERNAME_INVALID;
}else if ($user->password != $user->encrypt($this->password)){
$this->errorCode = self::ERROR_PASSWORD_INVALID;
}else{
$this->_id = $user_id;
$this->errorCode = self::ERROR_NONE;
}
return !$this->errorCode;
}
public function getId(){
return $this->_id;
}