Making your own portal widget with Yii

Site has moved

This site has moved to a new location. Visit the new site at

By Ronald van Belzen | April 30, 2013

As an example of how to make your own portlet let me show how I made a "recent comments" portlet for this blog.

The Blog tutorial shows a couple of excellent examples of how to make your own portlets, including how to make a recent comments portlet, but this one will be different. It will not just select to show the recent comments of the specified blog (multiple blogs can be defined, see the database schema in a previous post for reference). It will actually show the recently commented blog posts instead of the recent comments.

This is the code that I placed in "/protected/component":


class RecentComments extends CPortlet { 
  public $title='Recent Comments'; 
  public $blogId=0; 
  public $maxComments=10; 

  protected function renderContent() { 
    $criteria = new CDbCriteria; 
    $criteria->select = 'post_id, max( as id'; 
    $criteria->group = 'post_id'; 
    $criteria->condition = 'blog_id = ' . $this->blogId; 
    $criteria->order = 'MAX( DESC'; 
    $criteria->limit = $this->maxComments; 
    $models = Blogcomment::model()

    foreach($models as $model) { 
      $comment = BlogComment::model()->with('commentUser')->findByPk($model->id); 
      $post = BlogPost::model()->findByPk($model->post_id)->title; 
      $username = ($comment->commentUser)?$comment->commentUser->username:$comment->username;
      $link=CHtml::link(CHtml::encode($username) . ' • ' . CHtml::encode($post), array('/blog/blogPost/view','id'=>$model->post_id,'#'=>'bottom')); 
      echo CHtml::tag('div', array('class'=>'recent',), $link) . PHP_EOL; 

The default value for $blogId will result in an empty portlet and portlets have the advantage of not being shown when they have no content.

The real work is contained in the criteria. Translated into a SQL statement it would read:

select post_id, max( from blog_comment t 
left join blog_post post on post_id = 
where blog_id = :blogId 
group by post_id 
order by MAX( DESC 
limit 10;

"->with(array('post'=>array('select'=>false)))" tells the query to join with the table "blog_post", but not to include the fields from the table "blog_post". In case you are curious the relation "post" is defined in the model BlogComment thus:

      'post' => array(self::BELONGS_TO, 'BlogPost', 'post_id'),

The portlet can be included in the layout file "main.php" thus:

  $this->widget('RecentComments', array( 

However, every controller would need to define the variable $blogId or you could define that variable in the parent class of all controllers that use the layout file.

It is up to the controller action functions to fill the variable $this->blogId or else the portlet remains empty and will not be shown.