0 follower

Creating and Updating Posts

With the Post model ready, we need to fine-tune the actions and views for the controller PostController. In this section, we first customize the access control of CRUD operations; we then modify the code implementing the create and update operations; and finally we implement the preview feature for both operations.

1. Customizing Access Control

The first thing we want to do is to customize the access control because the code generated by yiic does not fit our needs.

We modify the accessRules() method in the file /wwwroot/blog/protected/controllers/PostController.php as follows,

public function accessRules()
{
    return array(
        array('allow',  // allow all users to perform 'list' and 'show' actions
            'actions'=>array('list', 'show'),
            'users'=>array('*'),
        ),
        array('allow', // allow authenticated users to perform any action
            'users'=>array('@'),
        ),
        array('deny',  // deny all users
            'users'=>array('*'),
        ),
    );
}

The above rules state that all users can access the list and show actions, and authenticated users can access any actions, including the admin action. The user should be denied access in any other scenario. Note that these rules are evaluated in the order they are listed here. The first rule matching the current context makes the access decision. For example, if the current user is the system owner who tries to visit the post creation page, the second rule will match and it will give the access to the user.

2. Customizing create and update Operations

The create and update operations are very similar. They both need to display an HTML form to collect user inputs, validate them, and save them into database. The main difference is that the update operation will pre-populate the form with the existing post data found in the database. For this reason, the yiic tool generates a partial view /wwwroot/blog/protected/views/post/_form.php that is embedded in both the create and update views to render the needed HTML form.

We first change the _form.php file so that the HTML form only collects the inputs we want: title, content and status. We use plain text fields to collect inputs for the first two attributes, and a dropdown list to collect input for status. The dropdown list options are the text displays of the possible post statuses:

<?php echo CHtml::activeDropDownList($post,'status',Post::model()->statusOptions); ?>

Tip: In the above, we can also use Post::model()->getStatusOptions() instead of Post::model()->statusOptions to return the possible status options. The reason that we can use the latter expression is because Post is a component which allows us to access properties defined in terms of getter methods.

We then modify the Post class so that it can automatically set some attributes (e.g. createTime, authorId) before a post is saved to the database. We override the beforeValidate() method as follows,

protected function beforeValidate($on)
{
    $parser=new CMarkdownParser;
    $this->contentDisplay=$parser->safeTransform($this->content);
    if($this->isNewRecord)
    {
        $this->createTime=$this->updateTime=time();
        $this->authorId=Yii::app()->user->id;
    }
    else
        $this->updateTime=time();
    return true;
}

In this method, we use CMarkdownParser to convert the content from Markdown format into HTML and save the result to contentDisplay. This avoids repeated format conversion when we display a post. If the post is new, we set its createTime and authorId attributes; otherwise we set its updateTime to be the current time. Note that this method will be invoked automatically when we call validate() or save() method of the model.

Because we want to save post tags to the Tag table, we also need the following method in the Post class, which is invoked automatically after a post is saved to the database:

protected function afterSave()
{
    if(!$this->isNewRecord)
        $this->dbConnection->createCommand(
            'DELETE FROM PostTag WHERE postId='.$this->id)->execute();
 
    foreach($this->getTagArray() as $name)
    {
        if(($tag=Tag::model()->findByAttributes(array('name'=>$name)))===null)
        {
            $tag=new Tag(array('name'=>$name));
            $tag->save();
        }
        $this->dbConnection->createCommand(
            "INSERT INTO PostTag (postId, tagId) VALUES ({$this->id},{$tag->id})")->execute();
    }
}
 
public function getTagArray()
{
    // break tag string into a set of tags
    return array_unique(
        preg_split('/\s*,\s*/',trim($this->tags),-1,PREG_SPLIT_NO_EMPTY)
    );
}

In the above, we first clean up the PostTag table for rows related with the current post. We then insert new tags into the Tag table and add a reference in the PostTag table. The logic here is a bit complex. Instead of using ActiveRecord, we write raw SQL statements and execute them with the database connection.

Tip: It is good practice to keep business logic, such as the above beforeValidate() and afterSave() code, in models instead of controllers.

3. Implementing Preview Feature

Besides the above customizations, we also want to add the preview feature that would allow us to preview a post before we save it to the database.

We first change the _form.php view file to add a preview button and a preview display. The preview is only displayed when the preview button is clicked and there is not validation error.

<?php echo CHtml::submitButton('Preview',array('name'=>'previewPost')); ?>
......
<?php if(isset($_POST['previewPost']) && !$post->hasErrors()): ?>
...display preview of $post here...
<?php endif; ?>

We then change the actionCreate() and actionUpdate() methods of PostController to respond to the preview request. Below we show the updated code of actionCreate(), which is very similar to that in actionUpdate():

public function actionCreate()
{
    $post=new Post;
    if(isset($_POST['Post']))
    {
        $post->attributes=$_POST['Post'];
        if(isset($_POST['previewPost']))
            $post->validate();
        else if(isset($_POST['submitPost']) && $post->save())
            $this->redirect(array('show','id'=>$post->id));
    }
    $this->render('create',array('post'=>$post));
}

In the above, if the preview button is clicked, we call $post->validate() to validate the user input; otherwise if the submit button is clicked, we try to save the post by calling $post->save() which implicitly performs validation. If the saving is successful (no validation errors and the data is saved to the database without error), we redirect the user browser to show the newly created post.