コメントの管理 - Commentモデルを変更する

作成日:2009/04/02
最終更新日:2009/04/03

Postモデルと同様に、Comment モデルの rules() realtions() safeAttribute() メソッドを変更する必要があります。 加えて、 attributeLabels() を修正し、いくつかの属性について表示ラベルを変更します。

rules() メソッドを変更する

まずはじめにyiicツールで生成した検証ルールを変更します。以下のルールがコメントに適用されます。

			
public function rules()
{
    return array(
        array('author,email,content', 'required'),
        array('author,email,url','length','max'=>128),
        array('email','email'),
        array('url','url'),
        array('verifyCode', 'captcha', 'on'=>'insert',
            'allowEmpty'=>!Yii::app()->user->isGuest),
    );
}				
			
		

上記のコードでは author email content の各属性を必須とし、 author email url を128文字まで、email を有効なメールアドレス形式に限定します。 url 属性は有効なURLでなくてはなりません。 そして verifyCode 属性はCAPTCHAコードとして有効である必要があります。

verifyCode 属性はコメントを残す際に入力される検証コードを格納するのに使われます。 Comment テーブルにはこの列が存在しないので、明示的にパブリックメンバ変数として宣言する必要があります。 verifyCode の検証には、captcha という名の特別なバリデータが使われます。 captchaverificationを参照しています。 さらに、新しいコメントが作成される場合のみこの検証が実行されます。 認証済みのユーザには、これは必要ありません。(allowEmpty オプションを見てください)

safeAtributes() メソッドを変更する

次に safeAttributes() メソッドを一括代入が可能となるように変更します。

			
public function safeAttributes()
{
    return array('author', 'email', 'url', 'content', 'verifyCode');
}				
			
		

この記述は、コメントフォームが author email URL content varificationCode の各要素からなることも示します。

relations() メソッドを変更する

「最近のコメント」ポートレットを開発する際に、直近のコメントを、コメント先の記事とともに、一覧にする必要があります。 したがって、relations() メソッドを記事と関連付けるように宣言する必要があります。

			
public function relations()
{
    return array(
        'post'=>array(self::BELONGS_TO, 'Post', 'postId',
            'joinType'=>'INNER JOIN'),
    );
}				
			
		

post に対するJOINが INNER JOIN になっていることに注意してください。 これは、コメントは必ず記事に属するからです。

attributeLabels() メソッドを変更する

最後に、attributeLabels() メソッドを変更し、属性のラベルを変えます。 このメソッドは名前とラベルのペアからなる配列を返します。 ラベルを表示するために、CHtml::activeLabel()が呼ばれると、 まず変更されたラベルがないかどうかを確認し、なければデフォルトのラベルが表示されます。

			
public function attributeLabels()
{
    return array(
        'author'=>'Name',
        'url'=>'Website',
        'content'=>'Comment',
        'verifyCode'=>'Verification Code',
    );
}				
			
		

デフォルトラベルの生成アルゴリズムは属性名に基づきます。 まず、大文字によって名前を単語に分割し、次にそれぞれの単語の先頭を大文字にします。 たとえば、verifyCodeVerify Code というデフォルトラベルを持ちます。

セーブ方法を変更する

各記事のコメント数を記録しておきたいので、コメントの追加や削除をした際に元記事のコメント数を調節する必要があります。 Comment モデルの afterSave() メソッドと afterDelete() メソッドをオーバーライドすることでこれを実現します。 同様に、beforeValidate() メソッドをオーバーライドし、 マークダウンフォーマットからHTMLへコメント内容を変換するとともに、作成日時を記録します。

			
protected function beforeValidate($on)
{
    $parser=new CMarkdownParser;
    $this->contentDisplay=$parser->safeTransform($this->content);
    if($this->isNewRecord)
        $this->createTime=time();
    return true;
}
 
protected function afterSave()
{
    if($this->isNewRecord && $this->status==Comment::STATUS_APPROVED)
        Post::model()->updateCounters(array('commentCount'=>1), "id={$this->postId}");
}
 
protected function afterDelete()
{
    if($this->status==Comment::STATUS_APPROVED)
        Post::model()->updateCounters(array('commentCount'=>-1), "id={$this->postId}");
}				
			
		

コメントの作成と表示

このセクションでは、コメントの表示と作成の機能を実装します。

コメントの表示

コメントを表示するために専用のページを用意するのではなく、記事の表次ページを使います。 記事の下にコメントの一覧を表示し、その下にコメント入力フォームを表示します。

記事ページにコメントを表示するために、以下のように PostControlleractionShow() メソッドを変更します。

			
public function actionShow()
{
    $post=$this->loadPost();
    $this->render('show',array(
        'post'=>$post,
        'comments'=>$post->comments,
    ));
}				
			
		

$post->comments というコードが有効であるのは、 Post クラスで comments という名前のリレーションを設定しているからです。 このコードが実行されると、暗黙のJOINがデータベースクエリで実行され、記事に属するコメントが返されます。 この機能はlazy relational queryとして知られています。

show ビューもまた修正します。 コメント表示を最後につけ加えます。 ここでは詳細には触れません。

コメントの作成

コメントの作成のために、PostControlleractionShow() メソッドを以下のように修正します。

			
public function actionShow()
{
    $post=$this->loadPost();
    $comment=$this->newComment($post);
    $this->render('show',array(
        'post'=>$post,
        'comments'=>$post->comments,
        'newComment'=>$comment,
    ));
}
 
protected function newComment($post)
{
    $comment=new Comment;
    if(isset($_POST['Comment']))
    {
        $comment->attributes=$_POST['Comment'];
        $comment->postId=$post->id;
        $comment->status=Comment::STATUS_PENDING;
 
        if(isset($_POST['previewComment']))
            $comment->validate('insert');
        else if(isset($_POST['submitComment']) && $comment->save())
        {
            Yii::app()->user->setFlash('commentSubmitted','Thank you...');
            $this->refresh();
        }
    }
    return $comment;
}				
			
		

上記コードでは、 show ビューを表示する前に、 newComment() メソッドを呼んでいます。 newComment() メソッド内では、Comment インスタンスを生成し、コメントフォームが送信されたかどうかを確認します。 コメントフォームは送信ボタンかプレビューボタンをクリックすることで送信されるでしょう。 もし送信ボタンがクリックされたのなら、コメントを保存し、フラッシュメッセージを表示します。 フラッシュメッセージは一回だけ表示されるので、ページをリロードすると消えます。

show ビューにコメントフォームを追加します。

			
......
<?php $this->renderPartial('/comment/_form',array(
    'comment'=>$newComment,
    'update'=>false,
)); ?>
			
		

コメントフォームは、wwwroot/blog/protected/views/comment/_form.php を埋め込むことで表示しています。 $newComment という変数は、 actionShow メソッドから渡されます。ユーザからの入力を受け取るのがその主な目的です。 update 変数はfalseにセットされます。つまり、コメント作成フォームは、コメントの新規作成のみに使われるということです。

コメントのプレビューを実現するために、コメントフォームにプレビューボタンを追加します。 プレビューボタンがクリックされたときに、一番下にコメントのプレビューが表示されます。 以下に変更部分を示します。

			
...comment form with preview button...
 
<?php if(isset($_POST['previewComment']) && !$comment->hasErrors()): ?>
<h3>Preview</h3>
<div class="comment">
  <div class="author"><?php echo $comment->authorLink; ?> says:</div>
  <div class="time"><?php echo date('F j, Y \a\t h:i a',$comment->createTime); ?></div>
  <div class="content"><?php echo $comment->contentDisplay; ?></div>
</div><!-- post preview -->
<?php endif; ?>				
			
		

コメントの管理

コメント管理は、更新・削除・コメントの承認を含みます。 これらの操作は CommentController のアクションとして実装します。

更新と削除

yiic によって生成されたコードのうち、更新と削除についてはほぼそのままで変更しません。 コメントの更新の際にプレビューを表示する必要があるので、 CommentControlleractionUpdate() メソッドのみは変更する必要があります。

			
public function actionUpdate()
{
    $comment=$this->loadComment();
 
    if(isset($_POST['Comment']))
    {
        $comment->attributes=$_POST['Comment'];
        if(isset($_POST['previewComment']))
            $comment->validate('update');
        else if(isset($_POST['submitComment']) && $comment->save())
            $this->redirect(array('post/show',
                'id'=>$comment->postId,
                '#'=>'c'.$comment->id));
    }
 
    $this->render('update',array('comment'=>$comment));
}				
			
		

PostController と大変よく似ています。

コメントの承認

コメントの新規作成では、認証されるまで画面に表示されません。 コメントの認証はステータスを変更することで行います。

以下のように、 CommentControlleractionApprove() メソッドを作ります。

			
public function actionApprove()
{
    if(Yii::app()->request->isPostRequest)
    {
        $comment=$this->loadComment();
        $comment->approve();
        $this->redirect(array('post/show',
            'id'=>$comment->postId,
            '#'=>'c'.$comment->id));
    }
    else
        throw new CHttpException(500,'Invalid request...');
}				
			
		

上のコードでは、POSTリクエスト経由で approve アクションが呼ばれたときに、 Comment モデルの approve() メソッドを実行し、ステータスを変更します。 次にユーザを記事のページにリダイレクトします。

Comment クラスの actionList() メソッドを修正し、承認待ちのコメントリストを表示するようにします。

			
public function actionList()
{
    $criteria=new CDbCriteria;
    $criteria->condition='Comment.status='.Comment::STATUS_PENDING;
 
    $pages=new CPagination(Comment::model()->count());
    $pages->pageSize=self::PAGE_SIZE;
    $pages->applyLimit($criteria);
 
    $comments=Comment::model()->with('post')->findAll($criteria);
 
    $this->render('list',array(
        'comments'=>$comments,
        'pages'=>$pages,
    ));
}				
			
		

list ビューでは、承認待ちのコメントの詳細を表示します。 特に以下のようにして approve リンクボタンを表示します。

			
<?php if($comment->status==Comment::STATUS_PENDING): ?>
    <span class="pending">Pending approval</span> |
    <?php echo CHtml::linkButton('Approve', array(
        'submit'=>array('comment/approve','id'=>$comment->id),
    )); ?> |
<?php endif; ?>				
			
		

CHtml::link()のかわりにCHtml::linkButton()を使います。 linkButton() はPOSTを発行するのに対し、link() はGETを発行します。 GETリクエストでサーバのデータを変更するのは避けるべきです。 さもないとユーザがページをリロードした際に、不注意でサーバのデータを変更してしまうという危険に直面することになりかねません。

その5.ポートレット