yii框架入门学习笔记四 日志功能完善

    总结一下上面几步所做的工作:

    一、搭建了yiiblog的基本骨架。

    二、建立了yiiblog的数据库。

    三、生成了每张数据表对应的model。

    四、为日志和评论生成了curd操作。

    五、修改了登陆验证方法,使应用通过tbl_user表来实现登陆功能。

    下面初步构思一下下面几步工作:

    一、完善日志管理功能

    二、完善评论管理功能

    三、系统细节完善

    下面执行第一步:完善日志管理功能。

     tbl_post表结构

 

首先需要更正post的验证规则

 

public function rules() {
        // 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'),
        );
    }

 

 这是根据表结构生成的定义规则。

 1     public function rules() {
 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     }
1 public function normalizeTags($attribute$params){
2         $this->tags = Tag::array2string(array_unique(Tag::string2array($this->tags)));
3     }
1         public static function string2array($tags){
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

 

本来标签验证想弄成匹配中文字符的,不过yi提供的匹配函数不支持,鼓捣了半天就放弃了。

 rule()中出现的属性必须是那些通过用户输入的属性。其他有代码或数据库设定的属性,如updatetime等不应该出现在rule()中。

下面定义relation()

 1 public function relations() {
 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下建立相应的常量

class Comment extends CActiveRecord
{
    const STATUS_PENDING = 1;
    const STATUS_APPROVED = 2;
    ...
}

 这段关联关系的意思是:文章与作者属于多对一的关系,一篇日志属于一个作者。日志与评论属于一对多的关系,一篇日志有多个评论,并且按评论创建时间降序排列并且通过审核。评论数则是一个聚合结果。

 然后可以创建一个生成url的方法来实现日志的阅览。

1 public function getUrl(){
2         return Yii::app()->createUrl('post/view', array(
3             'id' => $this->id,
4             'title' => $this->title,
5         ));
6     }

日志状态在日志表中存储为1,2,3这样的数字在页面显示是给用户很不直观的感觉,所以在类似字典表的lookup中创建文本形式的对应,显示在页面时直接显示对应的文本。     并在post.php定义状态产量。

 1 class Lookup extends CActiveRecord
 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_DRAFT = 1;
const STATUS_PUBLISHED = 2;
const STATUS_ARCHIVED = 3;

 上面做了这么多改动了,基本一直都是针对model的修改,在我理解就是系统的底层,MVC的中间层,也就是用户的控制中心controller还没有该经过,下面对这方面进行完善。(以前我觉得我对MVC的理解已经基本清晰了,不过现在发现又混乱了,惆怅。。。)

yii默认定义了控制器下动作的访问权限

View Code
 1 public function accessRules()
 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对应页面。我们重新定义规则,允许所有用户访问查看,允许认真用户可以执行所有动作。其他场景禁止用户访问。

 1 public function accessRules() {
 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#里的拉控件。

1 <div class="row">
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>

默认的状态字段为:

1 INSERT INTO tbl_lookup (name, type, code, position) VALUES ('草稿''PostStatus'11);
2 INSERT INTO tbl_lookup (name, type, code, position) VALUES ('已发布''PostStatus'22);
3 INSERT INTO tbl_lookup (name, type, code, position) VALUES ('已存档''PostStatus'33);

然后修改post类,使其在数据插入前后做一些操作。插入新纪录前自动填充记录创建事件和更新时间,更新记录时填充更新时间。记录插入后,更新所设的标签。同时,如果用户更新了标签,需要同步更新标签数据表的相关记录。

 1 protected function beforeSave(){
 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修改如下

 1 public function updateFrequency($oldTags$newTags){
 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控制器进行更改

 1 public function actionView() {
 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的视图界面

 1 <?php
 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的查询,同时只查询已发布的日志,加上之前做的评论数的统计。

 1 public function actionIndex() {
 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     }

 列表页的视图代码如下:

 1 <?php if(!empty($_GET['tag'])):?>
 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生成的代码基本不用修改及可以使用。

 1 <?php $this->widget('zii.widgets.grid.CGridView', array(
 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()方法做了调整,这部分也需要随之调整。另外,文章被删除后,相关评论需要被删除,相关标签也需要删除。

1 public function actionDelete($id) {
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     }
1 protected function afterDelete(){
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来封装罢了。不知道我理解的对不对。

 

 

 

 

 

 

 

 

posted @ 2013-04-26 16:36  albafica  阅读(891)  评论(1编辑  收藏  举报