The Post
model class generated by the yiic
tool mainly needs to be modified in three places:
rules()
method: specifies the validation rules for the model attributes;relations()
method: specifies the related objects;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.
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.
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.
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
User
and the relationship is established based on the authorId
attribute value of the post;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.
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.
Signup or Login in order to comment.