自定义日志模型

yiic 工具生成的 Post 日志模型类主要需要做如下两处修改:

  • rules() 方法:指定对模型属性的验证规则;
  • relations() 方法:指定关联的对象;

信息: 模型 包含了一系列属性,每个属性关联到数据表中相应的列。属性可以在类成员变量中显式定义,也可以隐式定义,不需要事先声明。

1. 自定义 rules() 方法

我们先来指定验证规则,它可以确保用户输入的信息在保存到数据库之前是正确的。例如, Poststatus 属性应该是 1, 2 或 3 中的任一数字。 yiic 工具其实也为每个模型生成了验证规则,但是这些规则是基于数据表的列信息的,可能并不是非常恰当。

基于需求分析,我们把 rules() 做如下修改:

public function rules()
{
    return array(
        array('title, content, status', 'required'),
        array('title', 'length', 'max'=>128),
        array('status', 'in', 'range'=>array(1,2,3)),
        array('tags', 'match', 'pattern'=>'/^[\w\s,]+$/',
            'message'=>'Tags can only contain word characters.'),
        array('tags', 'normalizeTags'),
 
        array('title, status', 'safe', 'on'=>'search'),
    );
}

在上面的代码中,我们指定了 title, contentstatus 属性是必填项;title 的长度不能超过 128;status 属性值应该是 1 (草稿), 2 (已发布) 或 3 (已存档);tags 属性应只允许使用单词字母和逗号。另外,我们使用 normalizeTags 来规范化用户输入的Tag,使Tag是唯一的且整齐地通过逗号分隔。最后的规则会被搜索功能用到,这个我们后面再讲。

required, length, inmatch 这几个验证器(validator)是Yii提供的内置验证器。normalizeTags 验证器是一个基于方法的验证器,我们需要在 Post 类中定义它。关于如何设置验证规则的更多信息,请参考 指南

public function normalizeTags($attribute,$params)
{
    $this->tags=Tag::array2string(array_unique(Tag::string2array($this->tags)));
}

其中的 array2stringstring2array 是在 Tag 模型类中定义的新方法。详情请参考 /wwwroot/yii/demos/blog/protected/models/Tag.php 文件。

rules() 方法中定义的规则会在模型实例调用其 validate()save() 方法时逐一执行。

注意: 请务必记住 rules() 中出现的属性必须是那些通过用户输入的属性。其他的属性,如 Post 模型中的 idcreate_time ,是通过我们的代码或数据库设定的,不应该出现在 rules() 中。详情请参考 属性的安全赋值(Securing Attribute Assignments).

作出这些修改之后,我们可以再次访问日志创建页检查新的验证规则是否已生效。

2. 自定义 relations() 方法

最后我们来自定义 relations() 方法,以指定与日志相关的对象。通过在 relations() 中声明这些相关对象,我们就可以利用强大的 Relational ActiveRecord (RAR) 功能来访问日志的相关对象,例如它的作者和评论。不需要自己写复杂的 SQL JOIN 语句。

我们自定义 relations() 方法如下:

public function relations()
{
    return array(
        'author' => array(self::BELONGS_TO, 'User', 'author_id'),
        'comments' => array(self::HAS_MANY, 'Comment', 'post_id',
            'condition'=>'comments.status='.Comment::STATUS_APPROVED,
            'order'=>'comments.create_time DESC'),
        'commentCount' => array(self::STAT, 'Comment', 'post_id',
            'condition'=>'status='.Comment::STATUS_APPROVED),
    );
}

我们还在 Comment 模型类中定义了两个在上面的方法中用到的常量。

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

relations() 中声明的关系表明:

  • 一篇日志属于一个作者,它的类是 User ,它们的关系建立在日志的 author_id 属性值之上;
  • 一篇日志有多个评论,它们的类是 Comment ,它们的关系建立在评论的 post_id 属性值之上。这些评论应该按它们的创建时间排列,且评论必须已通过审核;
  • commentCount 关系有一点特别,它返回一个关于日志有多少条评论的一个聚合结果。

通过以上的关系声明,我们现在可以按下面的方式很容易的访问日志的作者和评论信息。

$author=$post->author;
echo $author->username;
 
$comments=$post->comments;
foreach($comments as $comment)
    echo $comment->content;

关于如何声明和使用关系的更多详情,请参考 指南.

3. 添加 url 属性

日志是一份可以通过一个唯一的URL访问的内容。我们可以在 Post 模型中添加一个 url 属性,这样同样的创建URL的代码就可以被复用,而不是在代码中到处调用 CWebApplication::createUrl 。 稍后讲解怎样美化 URL 的时候,我们将看到添加这个属性给我们带来了超拽的便利。

要添加 url 属性,我们可以按如下方式给 Post 类添加一个 getter 方法:

class Post extends CActiveRecord
{
    public function getUrl()
    {
        return Yii::app()->createUrl('post/view', array(
            'id'=>$this->id,
            'title'=>$this->title,
        ));
    }
}

注意我们除了使用日志的ID之外,还添加了日志的标题作为URL中的一个 GET 参数。这主要是为了搜索引擎优化 (SEO) 的目的,在 美化 URL 中将会讲述。

由于 CComponentPost 的最顶级父类,添加 getUrl() 这个 getter 方法使我们可以使用类似 $post->url 这样的表达式。当我们访问 $post->url 时,getter 方法将会被执行,它的返回结果会成为此表达式的值。关于这种组件的更多详情,请参考 指南

4. 以文本方式显示状态

由于日志的状态在数据库中是以一个整型数字存储的,我们需要提供一个文本话的表现形式,这样在它显示给最终用户时会更加直观。在一个大的系统中,类似的需求是很常见的。

作为一个总体的解决方案,我们使用 tbl_lookup 表存储数字值和被用于其他数据对象的文本值的映射。为了更简单的访问表中的文本数据,我们按如下方式修改 Lookup 模型类:

class Lookup extends CActiveRecord
{
    private static $_items=array();
 
    public static function items($type)
    {
        if(!isset(self::$_items[$type]))
            self::loadItems($type);
        return self::$_items[$type];
    }
 
    public static function item($type,$code)
    {
        if(!isset(self::$_items[$type]))
            self::loadItems($type);
        return isset(self::$_items[$type][$code]) ? self::$_items[$type][$code] : false;
    }
 
    private static function loadItems($type)
    {
        self::$_items[$type]=array();
        $models=self::model()->findAll(array(
            'condition'=>'type=:type',
            'params'=>array(':type'=>$type),
            'order'=>'position',
        ));
        foreach($models as $model)
            self::$_items[$type][$model->code]=$model->name;
    }
}

我们的新代码主要提供了两个静态方法: Lookup::items()Lookup::item()。前者返回一个属于指定的数据类型的字符串列表,后者按指定的数据类型和数据值返回一个具体的字符串。

我们的博客数据库已经预置了两个查询类别: PostStatusCommentStatus。前者代表可用的日志状态,后者代表评论状态。

为了使我们的代码更加易读,我们还定义了一系列常量,用于表示整数型状态值。我们应该在涉及到相应的状态值时在代码中使用这些常量。

class Post extends CActiveRecord
{
    const STATUS_DRAFT=1;
    const STATUS_PUBLISHED=2;
    const STATUS_ARCHIVED=3;
    ......
}

这样,我们可以通过调用 Lookup::items('PostStatus') 来获取可用的日志状态列表(按相应的整数值索引的文本字符串),通过调用 Lookup::item('PostStatus', Post::STATUS_PUBLISHED) 来获取发布状态的文本表现形式。

$Id: post.model.txt 2119 2010-05-10 01:27:29Z qiang.xue $

Total 1 comment

#3327 report it
liu1084 at 2011/04/03 12:13pm
the code is expired

the code is expired, and in model named Lookup, I think the code is confused to me.

public function getItems($itemType){
        return $this->model()->findAll(array(
                    'condition' => 'type = :type', 
                    'params' => array(':type' => $itemType),
                    'order' => 'position DESC',
            )
        );
    }
 
    public function getItem($itemType, $itemCode){
        return $this->model()->find(array(
                'condition' => 'type = :type AND code = :code',
                'params' => array(':type' => $itemType, ':code' => $itemCode),
        ))->name;
    }

in this manner, easy to me

Leave a comment

Please to leave your comment.