0 关注者

自定义模型

Gii工具生成的Post模型类主要需要在两个地方修改

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

信息:一个模型包含一个属性列表,每个属性都与相应数据库表中的一个列相关联。属性可以显式地声明为类成员变量,也可以隐式地声明而无需任何声明。

1. 自定义rules()方法

我们首先指定验证规则,以确保用户输入的属性值在保存到数据库之前是正确的。例如,Poststatus属性应为整数1、2或3。Gii工具也为每个模型生成验证规则。但是,这些规则是基于表列信息,可能不合适。

根据需求分析,我们修改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'),
    );
}

在上面,我们指定titlecontentstatus属性是必需的;title的长度不应超过128;status属性值应为1(草稿)、2(已发布)或3(已归档);并且tags属性只能包含单词字符和逗号。此外,我们使用normalizeTags规范化用户输入的标签,以便标签是唯一的,并用逗号正确分隔。最后一个规则由搜索功能使用,我们将在后面描述。

诸如requiredlengthinmatch之类的验证器都是Yii提供的内置验证器。normalizeTags验证器是我们需要在Post类中定义的基于方法的验证器。有关如何指定验证规则的更多信息,请参阅指南

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

其中array2stringstring2array是我们需要在Tag模型类中定义的新方法

public static function string2array($tags)
{
    return preg_split('/\s*,\s*/',trim($tags),-1,PREG_SPLIT_NO_EMPTY);
}
 
public static function array2string($tags)
{
    return implode(', ',$tags);
}

当我们调用模型实例的validate()save()方法时,rules()方法中声明的规则将逐一执行。

注意:务必记住,出现在rules()中的属性必须是最终用户输入的属性。其他属性,例如Post模型中的idcreate_time,由我们的代码或数据库设置,不应在rules()中。有关更多详细信息,请参阅保护属性赋值

进行这些更改后,我们可以再次访问文章创建页面以验证新的验证规则是否生效。

2. 自定义relations()方法

最后,我们自定义relations()方法以指定文章的关联对象。通过在relations()中声明这些关联对象,我们可以利用强大的关系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关联的内容,用于查看它。与其在我们的代码中到处调用CWebApplication::createUrl来获取此URL,不如在Post模型中添加一个url属性,以便可以重用相同的URL创建代码片段。稍后,当我们描述如何美化URL时,我们将看到添加此属性将为我们带来极大的便利。

要添加url属性,我们通过添加如下所示的getter方法修改Post

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的最终祖先类,所以添加getter方法getUrl()使我们能够使用类似$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)获取已发布状态的字符串表示形式。

发现错别字或您认为此页面需要改进?
在github上编辑它 !