yii框架入门学习笔记四 日志功能完善
总结一下上面几步所做的工作:
一、搭建了yiiblog的基本骨架。
二、建立了yiiblog的数据库。
三、生成了每张数据表对应的model。
四、为日志和评论生成了curd操作。
五、修改了登陆验证方法,使应用通过tbl_user表来实现登陆功能。
下面初步构思一下下面几步工作:
一、完善日志管理功能
二、完善评论管理功能
三、系统细节完善
下面执行第一步:完善日志管理功能。
tbl_post表结构
首先需要更正post的验证规则
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('title, content, author_id', 'required'),
array('status, author_id', 'numerical', 'integerOnly' => true),
array('title', 'length', 'max' => 100),
array('tags', 'length', 'max' => 50),
array('create_time, update_time', 'safe'),
// The following rule is used by search().
// Please remove those attributes that should not be searched.
array('id, title, content, tags, status, create_time, update_time, author_id', 'safe', 'on' => 'search'),
);
}
这是根据表结构生成的定义规则。
2 // NOTE: you should only define rules for those attributes that
3 // will receive user inputs.
4 return array(
5 array('title, content, status', 'required'),
6 array('status', 'numerical', 'integerOnly' => true),
7 array('status', 'in', 'range' => array('1', '2', '3')),
8 array('tags', 'match', 'pattern' => '/^[\w\s,]+$/', 'message' => '标签只能为常用字符'),
9 array('tags', 'normalizeTags'),
10 array('title', 'length', 'max' => 50),
11 array('tags', 'length', 'max' => 50),
12 // The following rule is used by search().
13 // Please remove those attributes that should not be searched.
14 array('title, tags, status', 'safe', 'on' => 'search'),
15 );
16 }
2 $this->tags = Tag::array2string(array_unique(Tag::string2array($this->tags)));
3 }
2 return preg_split('/\s*,\s*/', trim($tags),-1, PREG_SPLIT_NO_EMPTY);
3 }
4
5 public static function array2string($tags){
6 return implode(',', $tags);
7 }
定义的验证规则为:title,content,status必填,status为整数型且必须要在1,2,3之中,标签纸可以为常用字符或‘,’,自定义了格式化标签字符串的方法,title最大长度不超过50,标签最大不超过50。
为了测试,现在想修改title最大长度不得为5,标签最大长度不得为10
rule()中出现的属性必须是那些通过用户输入的属性。其他有代码或数据库设定的属性,如updatetime等不应该出现在rule()中。
下面定义relation()
2 // NOTE: you may need to adjust the relation name and the related
3 // class name for the relations automatically generated below.
4 return array(
5 'author' => array(self::BELONGS_TO, 'User', 'author_id'),
6 'comments' => array(self::HAS_MANY, 'Comment', 'post_id',
7 'condition' => 'comments.status='.Comment::STATUS_APPROVED,
8 'order' => 'comment.create_time DESC'),
9 'commentCount' => array(self::STAT, 'Comment', 'post_id',
10 'condition' => 'status='.Comment::STATUS_APPROVED),
11 );
12 }
同时在comment.php下建立相应的常量
{
const STATUS_PENDING = 1;
const STATUS_APPROVED = 2;
...
}
这段关联关系的意思是:文章与作者属于多对一的关系,一篇日志属于一个作者。日志与评论属于一对多的关系,一篇日志有多个评论,并且按评论创建时间降序排列并且通过审核。评论数则是一个聚合结果。
然后可以创建一个生成url的方法来实现日志的阅览。
2 return Yii::app()->createUrl('post/view', array(
3 'id' => $this->id,
4 'title' => $this->title,
5 ));
6 }
日志状态在日志表中存储为1,2,3这样的数字在页面显示是给用户很不直观的感觉,所以在类似字典表的lookup中创建文本形式的对应,显示在页面时直接显示对应的文本。 并在post.php定义状态产量。
2 {
3 private static $_item = array();
4
5 public static function items($type){
6 if(!isset(self::$_item[$type]))
7 self::loaditem($type);
8 return self::$_item[$type];
9 }
10
11 public static function item($type, $code){
12 if(!isset(self::$_item[$type]))
13 self::loaditems($type);
14 return isset(self::$_item[$type][$code])?self::$_item[$type][$code]:false;
15 }
16
17 private static function loaditems($type){
18 self::$_item[$type] = array();
19 $models = self::model()->findAll(array(
20 'condition' => 'type=:type',
21 'params' => array(':type' => $type),
22 'order' => 'position',
23 ));
24 foreach ($models as $model) {
25 self::$_item[$type][$model->code] = $model->name;
26 }
27 }
28 ...
29 }
const STATUS_PUBLISHED = 2;
const STATUS_ARCHIVED = 3;
上面做了这么多改动了,基本一直都是针对model的修改,在我理解就是系统的底层,MVC的中间层,也就是用户的控制中心controller还没有该经过,下面对这方面进行完善。(以前我觉得我对MVC的理解已经基本清晰了,不过现在发现又混乱了,惆怅。。。)
yii默认定义了控制器下动作的访问权限
2 {
3 return array(
4 array('allow', // allow all users to perform 'index' and 'view' actions
5 'actions'=>array('index','view'),
6 'users'=>array('*'),
7 ),
8 array('allow', // allow authenticated user to perform 'create' and 'update' actions
9 'actions'=>array('create','update'),
10 'users'=>array('@'),
11 ),
12 array('allow', // allow admin user to perform 'admin' and 'delete' actions
13 'actions'=>array('admin','delete'),
14 'users'=>array('admin'),
15 ),
16 array('deny', // deny all users
17 'users'=>array('*'),
18 ),
19 );
20 }
默认所有用户可以访问index,view动作对应的页面,认证用户可以访问create,update页面,只有admin可以访问admin,delete对应页面。我们重新定义规则,允许所有用户访问查看,允许认真用户可以执行所有动作。其他场景禁止用户访问。
2 return array(
3 array('allow', // allow all users to perform 'index' and 'view' actions
4 'actions' => array('index', 'view'),
5 'users' => array('*'),
6 ),
7 array('allow', // allow authenticated user to perform all actions
8 'users' => array('@'),
9 ),
10 array('deny', // deny all users
11 'users' => array('*'),
12 ),
13 );
14 }
完成这部分,可以到页面上进行查看,权限的控制也完成了。
下面进行数据的增加和改动,这两点基本类似,区别在于,改动是先查出相关信息再保存。之前说过,post表中需要手动填写的字段为:title,content,tag,status,其中status用下拉菜单的形式显示字典数据。所以首先要对视图文件夹view下post里的_form.php进行改动,不需要的字段删除,同时将status的字段改成下拉菜单的。个人感觉很想c#里的拉控件。
2 <?php echo $form->labelEx($model,'status'); ?>
3 <?php echo $form->dropDownList($model, 'status', Lookup::items('PostStatus')); ?>
4 <?php echo $form->error($model,'status'); ?>
5 </div>
默认的状态字段为:
2 INSERT INTO tbl_lookup (name, type, code, position) VALUES ('已发布', 'PostStatus', 2, 2);
3 INSERT INTO tbl_lookup (name, type, code, position) VALUES ('已存档', 'PostStatus', 3, 3);
然后修改post类,使其在数据插入前后做一些操作。插入新纪录前自动填充记录创建事件和更新时间,更新记录时填充更新时间。记录插入后,更新所设的标签。同时,如果用户更新了标签,需要同步更新标签数据表的相关记录。
2 if(parent::beforeSave()){
3 if($this->isNewRecord){
4 $this->create_time = $this->update_time = date("Y-m-d H:i:s", time());
5 $this->author_id = Yii::app()->user->id;
6 }else{
7 $this->update_time = date("Y-m-d H:i:s", time());
8 }
9 return true;
10 }else{
11 return false;
12 }
13 }
14
15 protected function afterSave(){
16 parent::afterSave();
17 Tag::model()->updateFrequency($this->_oldTags, $this->tags);
18 }
19
20 protected function afterFind(){
21 parent::afterFind();
22 $this->_oldTags = $this->tags;
23 }
tag类中对tag修改如下
2 $oldTags = self::string2array($oldTags);
3 $newTags = self::string2array($newTags);
4 $this->addTags(array_values(array_diff($newTags, $oldTags)));
5 $this->removeTags(array_values(array_diff($oldTags, $newTags)));
6 }
7
8 public function addTags($tags){
9 $criteria = new CDbCriteria();
10 $criteria->addInCondition('name', $tags);
11 $this->updateCounters(array('frequency' => 1), $criteria);
12 foreach ($tags as $name) {
13 if(!$this->exists('name=:name', array(':name' => $name))){
14 $tag = new Tag();
15 $tag->name = $name;
16 $tag->frequency = 1;
17 $tag->save();
18 }
19 }
20 }
21
22 public function removeTags($tags){
23 if(empty($tags))
24 return ;
25 $criteria = new CDbCriteria();
26 $criteria->addInCondition('name', $tags);
27 $this->updateCounters(array('frequency' => -1), $criteria);
28 $this->deleteAll('frequency<=0');
29 }
至此,日志的创建和修改完成。
view操作是展示日志的详细信息,自动生成的基本满足了使用,下面做一些改进使其更加符合blog的应用场景。首先对post控制器进行更改
2 $post = $this->loadModel();
3 $this->render('view', array(
4 'model' => $post,
5 ));
6 }
7 ...
8 public function loadModel() {
9 if($this->_model === null){
10 if(isset($_GET['id'])){
11 if(Yii::app()->user->isGuest){
12 $condition = 'status=' . Post::STATUS_PUBLISHED . ' OR ' .Post::STATUS_ARCHIVED;
13 }else{
14 $condition = "";
15 }
16 $this->_model = Post::model()->findByPk($_GET['id'], $condition);
17 if($this->_model === NULL)
18 throw new CHttpException(404, "请求的内容不存在");
19 }
20 }
21 return $this->_model;
22 }
view的视图界面
2 /* @var $this PostController */
3 /* @var $model Post */
4
5 $this->breadcrumbs=array(
6 $model->title,
7 );
8 $this->pageTitle = $model->title;
9 ?>
10
11 <h1>View Post #<?php echo $model->id; ?></h1>
12 <?php
13 $this->renderPartial("_view", array('data' => $model));
14 ?>
以后还会继续添加相关的评论内容。
列表页添加对标签tag的查询,同时只查询已发布的日志,加上之前做的评论数的统计。
2 $criteria = new CDbCriteria(array(
3 'condition' => 'status=' . Post::STATUS_PUBLISHED,
4 'order' => 'update_time DESC',
5 'with' => 'commentCount',
6 ));
7 if(isset($_GET['tag']))
8 $criteria->addSearchCondition('tags', $_GET['tag']);
9
10 $dataProvider = new CActiveDataProvider('post', array(
11 'pagination' => array(
12 'pageSize' => 5,
13 ),
14 'criteria' => $criteria,
15 ));
16 $this->render('index', array(
17 'dataProvider' => $dataProvider,
18 ));
19 }
列表页的视图代码如下:
2 <h1>Posts Tagged with<i><?php echo CHtml::encode($_GET['tag']);?></i></h1>
3 <?php endif;?>
4
5 <?php
6 $this->widget('zii.widgets.CListView', array(
7 'dataProvider'=>$dataProvider,
8 'itemView'=>'_view',
9 'template'=>"{items}\n{pager}",
10 )); ?>
数据的显示用了CListView这个小物件,现在还不怎么理解具体用法,像按照教程敲吧。
日志管理使用了CGridView这个小物件,gii生成的代码基本不用修改及可以使用。
2 'id'=>'post-grid',
3 'dataProvider'=>$model->search(),
4 'filter'=>$model,
5 'columns'=>array(
6 'id',
7 array(
8 'name' => 'title',
9 'type' => 'raw',
10 'value' => 'CHtml::link(CHtml::encode($data->title), $data->url)',
11 ),
12 'content',
13 'tags',
14 array(
15 'name' => 'status',
16 'value' => 'Lookup::item("PostStatus", $data->status)',
17 'filter' => Lookup::items("PostStatus"),
18 ),
19 array(
20 'name' => 'create_time',
21 'type' => 'datetime',
22 'filter' => false,
23 ),
24 array(
25 'class'=>'CButtonColumn',
26 ),
27 ),
28 )); ?>
删除操作也基本采用gii生成的,因为loadmodel()方法做了调整,这部分也需要随之调整。另外,文章被删除后,相关评论需要被删除,相关标签也需要删除。
2 if(Yii::app()->request->isPostRequest){
3 $this->loadModel()->delete();
4 if(!isset($_POST['ajax']))
5 $this->redirect (array('index'));
6 }else{
7 throw new CHttpException(400, "非法访问,请重试。");
8 }
9 }
2 parent::afterDelete();
3 Comment::model()->deleteAll('post_id='.$this->id);
4 Tag::model()->updateFrequency($this->tags, "");
5 }
至此,日志的相关操作均以完成。
总结一下,这一步完成的主要工作有
一、添加修改访问权限控制
二、将由gii生成的加载记录的方式改为获取get数据并读取数据库
三、修改表单页面显示的内容,将有系统自动填充的内容删去并在post类中对这些字段进行自动填充,包括对标签使用频率的更新等后台逻辑。
四、查看页面变动显示方式,微调。
五、列表页面变动查询条件。
六、增加删除日志时相关后台记录。
晚上回来的路上想到yii以widget的方式展现表单元素,很像控件,其实应该就是一个实例化的对象。以前的写法只是单纯的编辑html元素,现在用widget来封装罢了。不知道我理解的对不对。