0 follower

Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in three places:

  • the rules() method: specifies the validation rules for the model attributes;
  • the relations() method: specifies the related objects;
  • the safeAttributes() method: specifies which attributes can be massively assigned (mainly used when passing user input to the model);

Info: A model consists of a list of attributes, each associated with a column in the corresponding database table. Attributes can be declared explicitly as class member variables or implicitly without any declaration.

1. Customizing rules() Method

We first specify the validation rules which ensure the attribute values populated by user inputs are correct before they are saved to the database. For example, the status attribute of Post should be an integer 0, 1 or 2. The yiic tool also generates validation rules for each model. However, these rules are based on the table column information and may not be appropriate.

Based on the requirements analysis, we modify the rules() method as follows:

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

In the above, we specify that the title, content and status attributes are required; the length of title should not exceed 128; the status attribute value should be 0 (draft), 1 (published) or 2 (archived); and the tags attribute should only contain word characters and commas. All other attributes (e.g. id, createTime) will not be validated because their values do not come from user input.

After making these changes, we can visit the post creation page again to verify that the new validation rules are taking effect.

Info: Validation rules are used when we call the validate() or save() method of the model instance. For more information about how to specify validation rules, please refer to the Guide.

2. Customizing safeAttributes() Method

We then customize the safeAttributes() method to specify which attributes can be massively assigned. When passing user inputs to the model instance, we often use the following massive assignment to simplify our code:

$post->attributes=$_POST['Post'];

Without using the above massive assignment, we would end up with the following lengthy code:

$post->title=$_POST['Post']['title'];
$post->content=$_POST['Post']['content'];
......

Although massive assignment is very convenient, it has a potential danger that a malicious user may attempt to populate an attribute whose value should remain read only or should only be changed by developer in code. For example, the id of the post currently being updated should not be changed.

To prevent from such danger, we should customize the safeAttributes() as follows, which states only title, content, status and tags attributes can be massively assigned:

public function safeAttributes()
{
    return array('title', 'content', 'status', 'tags');
}

Tip: An easy way to identity which attributes should be put in the safe list is by observing the HTML form that is used to collect user input. Model attributes that appear in the form to receive user input may be declared as safe. Since these attributes receive input from end users, they usually should be associated with some validation rules.

3. Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post. By declaring these related objects in relations(), we can exploit the powerful Relational ActiveRecord (RAR) feature to access the related object information of a post, such as its author and comments, without the need to write complex SQL JOIN statements.

We customize the relations() method as follows:

public function relations()
{
    return array(
        'author'=>array(self::BELONGS_TO, 'User', 'authorId'),
        'comments'=>array(self::HAS_MANY, 'Comment', 'postId',
            'order'=>'??.createTime'),
        'tagFilter'=>array(self::MANY_MANY, 'Tag', 'PostTag(postId, tagId)',
            'together'=>true,
            'joinType'=>'INNER JOIN',
            'condition'=>'??.name=:tag'),
    );
}

The above relations state that

  • A post belongs to an author whose class is User and the relationship is established based on the authorId attribute value of the post;
  • A post has many comments whose class is Comment and the relationship is established based on the postId attribute value of the comments. These comments should be sorted according to their creation time.

The tagFilter relation is a bit complex. It is used to explicitly join the Post table with the Tag table and choose only the rows with a specified tag name. We will show how to use this relation when we implement the post display feature.

With the above relation declaration, we can easily access the author and comments of a post like the following:

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

For more details about how to declare and use relations, please refer to the Guide.

4. Representing Status in Text

Because the status of a post is stored as an integer in the database, we need to provide a text representation so that it is more intuitive when being displayed to end users. For this reason, we modify the Post model as follows,

class Post extends CActiveRecord
{
    const STATUS_DRAFT=0;
    const STATUS_PUBLISHED=1;
    const STATUS_ARCHIVED=2;
 
    ......
 
    public function getStatusOptions()
    {
        return array(
            self::STATUS_DRAFT=>'Draft',
            self::STATUS_PUBLISHED=>'Published',
            self::STATUS_ARCHIVED=>'Archived',
        );
    }
 
    public function getStatusText()
    {
        $options=$this->statusOptions;
        return isset($options[$this->status]) ? $options[$this->status]
            : "unknown ({$this->status})";
    }
}

In the above, we define class constants to represent the possible status values. These constants are mainly used in the code to make it more maintainable. We also define the getStatusOptions() method which returns a mapping between status integer values and text display. And finally, we define the getStatusText() method which simply returns the textual status display of the current post.