Yiiブログチュートリアル 5.ポートレット - ポートレットを設計する

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

「最近のコメント」や「タグクラウド」といった機能は、portlets として実装するほうが良いでしょう。 ポートレットとは、部分的なHTMLを表示する、着脱可能なUIコンポーネントのことです。 このセクションでは、ブログアプリケーション向けにポートレットを設計する方法を解説します。

Portlet クラスを作る

Portletという名前のクラスを定義し、これから作るポートレットの基底クラスにします。 この基底クラスには、すべてのポートレットに共通するプロパティとメソッドが含まれます。 たとえば、ポートレットのタイトルをあらわすtitleプロパティです。 また、基底クラスでは装飾の方法も定義されます。ポートレットは枠で囲まれ、背景色つきのボックスで表示されます。

以下のコードがPortlet基底クラスの定義です。 ポートレットは多くの場合、ロジックとプレゼンテーションの両方を含むため、 PortletCWidgetの サブクラスとします。 つまり、ポートレットはwidgetであり、 widget()メソッドを用いてビューに埋め込むことができます。

			
class Portlet extends CWidget
{
    public $title; // the portlet title
    public $visible=true; // whether the portlet is visible
    // ...other properties...
 
    public function init()
    {
        if($this->visible)
        {
            // render the portlet starting frame
            // render the portlet title
        }
    }
 
    public function run()
    {
        if($this->visible)
        {
            $this->renderContent();
            // render the portlet ending frame
        }
    }
 
    protected function renderContent()
    {
        // child class should override this method
        // to render the actual body content
    }
}				
			
		

上記のコードでは、 init()メソッドと run()メソッドが CWidgetとして実装する必要のあるメソッドです。 これらのメソッドは、ウィジェットがビューで表示されたときに自動で実行されます。 Portletのサブクラスは、主にrenderContent()メソッドを オーバーライドして、実際のポートレットを表示します。

ページレイアウトを変更する

ページレイアウトを調整して、サイドバーにポートレットを置けるようにしましょう。 ページレイアウトはもっぱらレイアウトビューファイル /wwwroot/blog/protected/views/layouts/main.php によって決定されます。このレイアウトによって、各ページに共通の部分(ヘッダ・フッタなど)が表示され、 各アクションで生成された動的コンテンツが適切な位置に埋め込まれます。

このブログアプリケーションでは、以下のようなレイアウトを使います。

			
<html>
<head>
......
<?php echo CHtml::cssFile(Yii::app()->baseUrl.'/css/main.css'); ?>
<title><?php echo $this->pageTitle; ?></title>
</head>
 
<body>
 
...header...
 
<div id="sidebar">
...list of portlets...
</div>
 
<div id="content">
<?php echo $content; ?>
</div>
 
...footer...
 
</body>
</html>				
			
		

レイアウトビューを変更する一方で、/wwwroot/blog/css/main.cssにあるCSSファイルも調整する必要があります。 全体の見栄えをblog demoのようにするためです。 ここでは詳細には触れません。

ユーザメニューポートレットを作る

このセクションでは、初めて具体的なポートレットを作ります。 これから作るユーザメニューポートレットは、認証済みユーザのみが利用できるメニューの一覧を表示します。 メニューは四つのアイテムからなります。

UserMenuクラスを作る

ユーザメニューポートレットのロジック部分としてUserMenuクラスを作ります。 このクラスは /wwwroot/blog/protected/components/UserMenu.php ファイル内におかれます。 その内容は以下のとおりです。

			
<?php
class UserMenu extends Portlet
{
    public function init()
    {
        $this->title=CHtml::encode(Yii::app()->user->name);
        parent::init();
    }
 
    protected function renderContent()
    {
        $this->render('userMenu');
    }
}				
			
		

UserMenuクラスは、先ほど作った Portlet クラスのサブクラスです。 init() メソッドと renderContent() メソッドの両方をオーバーライドします。 前者はポートレットのタイトルに現在のユーザ名をセットし、後者はポートレットの表示内容をuserMenuビューの内容に沿って生成します。

Portletクラスをコード内で参照しているにかかわらず、 明示的にインクルードしていないことに注意して下さい。 これは前のセクションで説明した理由によるものです。

userMenu ビューを作る

次にuserMenuビューをつくり、 /wwwroot/blog/protected/components/views/userMenu.phpファイルとして保存します。

			
<ul>
<li><?php echo CHtml::link('Approve Comments', array('comment/list'))
    . ' (' . Comment::model()->pendingCommentCount . ')'; ?></li>
<li><?php echo CHtml::link('Create New Post',array('post/create')); ?></li>
<li><?php echo CHtml::link('Manage Posts',array('post/admin')); ?></li>
<li><?php echo CHtml::linkButton('Logout',array(
    'submit'=>'',
    'params'=>array('command'=>'logout'),
)); ?></li>
</ul>
			
		

デフォルトでは、ウィジェットのビューファイルはウィジェットクラスを含むディレクトリの下のviewsディレクトリ内におかれます。 ファイル名はビュー名と同じでなければなりません。

ビューではCHtml::linkで必要なリンクを作ります。 また、CHtml::linkButtonを使ってボタンのように動くリンクボタンも作ります。 ボタンがクリックされると、commandパラメータがlogoutのフォームが送信されます。

logoutリンクに応答するため、UserMenuinit()メソッドを以下のように変更します。

			
public function init()
{
    if(isset($_POST['command']) && $_POST['command']==='logout')
    {
        Yii::app()->user->logout();
        $this->controller->redirect(Yii::app()->homeUrl);
    }
 
    $this->title=CHtml::encode(Yii::app()->user->name);
    parent::init();
}				
			
		

init()メソッドにおいて、POST変数にcommandがあり、その値がlogoutであるかどうかを確認します。 もしそうなら、ログアウト処理を行い、ユーザをアプリケーションのホームページへリダイレクトします。 redirect()メソッドが黙示的に現在のアプリケーション実行を終了させることに注意してください。

UserMenuポートレットを使う

さあ、新しく完成したUserMenuポートレットを使う瞬間がやってきました。 /wwwroot/blog/protected/views/layouts/main.php にあるレイアウトビューファイルを以下のように修正します。

			
......
 
<div id="sidebar">
 
<?php $this->widget('UserMenu',array('visible'=>!Yii::app()->user->isGuest)); ?>
 
</div>
 
......				
			
		

上記のコードでは、widget()メソッドを呼び、UserMenuクラスインスタンスの生成と実行を行っています。 ポートレットは認証済みのユーザのみに表示されるべきなので、現在のユーザのisGuestプロパティによって、visibleプロパティを切り替えます。

UserMenuポートレットをテストする

これまでにやったことをテストしましょう。

  1. ブラウザを開き、http://www.example.com/blog/index.php にアクセスします。サイドバーに何も表示されないことを確認します。
  2. Loginリンクをクリックし、ログインします。ログインが成功すると、サイドバーにUserMenu ポートレットが表示され、ユーザ名がそのタイトルになっていることを確認してください。
  3. UserMenuポートレットの「ログアウト」リンクをクリックすると、ログアウト処理が行われ、UserMenuポートレットが消えることを確認してください。

まとめ

ここで作ったポートレットは非常に再利用性が高いものです。 ほかのプロジェクトでもこのポートレットをほとんど修正することなく使うことができます。 さらに、このポートレットのデザインはロジックとプレゼンテーションを分離するという哲学に基づいています。 前のセクションでは指摘しませんでしたが、これは典型的なYiiアプリケーションの全体にわたっていえることです。

ログインポートレットを作る

スケルトンアプリケーションはすでにログイン機能を備えていますが、 このセクションではログイン機能をUserLoginというログインポートレットへと変換します。 このポートレットは現在のユーザが認証済みでない場合にサイドバーに表示されます。 ログインに成功するとポートレットは消え、すでに作成したユーザメニューポートレットが表示されます。

UserLogin クラスを作る

ユーザメニューポートレットのように、ログインのロジックを含むUserLoginクラスを作り、 /wwwroot/blog/protected/components/UserLogin.php ファイルに保存します。 このファイルには以下の内容が含まれます。

			
<?php
class UserLogin extends Portlet
{
    public $title='Login';
 
    protected function renderContent()
    {
        $form=new LoginForm;
        if(isset($_POST['LoginForm']))
        {
            $form->attributes=$_POST['LoginForm'];
            if($form->validate())
                $this->controller->refresh();
        }
        $this->render('userLogin',array('form'=>$form));
    }
}				
			
		

renderContent() メソッドのコードは、SiteControlleractionLogin()メソッドからコピーしたものです。 主にuserLoginビューによって呼ばれるrender()メソッドの内容を変更します。 このメソッドで、LoginFormクラスのインスタンスを作っていることにも注意してください。 このクラスはユーザの入力したログイン情報を受け取ります。 このクラスのファイルは/wwwroot/blog/protected/models/LoginForm.phpです。 これはyiicツールで生成したものです。

userLoginビューを作る

userLoginビューの大部分は、SiteControllerloginビューのものです。 このビューは/wwwroot/blog/protected/components/views/loginUser.php に保存され、その内容は以下のとおりです。

			
<?php echo CHtml::form(); ?>
<div class="row">
<?php echo CHtml::activeLabel($form,'username'); ?>
<br/>
<?php echo CHtml::activeTextField($form,'username') ?>
<?php echo CHtml::error($form,'username'); ?>
</div>
<div class="row">
<?php echo CHtml::activeLabel($form,'password'); ?>
<br/>
<?php echo CHtml::activePasswordField($form,'password') ?>
<?php echo CHtml::error($form,'password'); ?>
</div>
<div class="row">
<?php echo CHtml::activeCheckBox($form,'rememberMe'); ?>
<?php echo CHtml::label('Remember me next time',CHtml::getActiveId($form,'rememberMe')); ?>
</div>
<div class="row">
<?php echo CHtml::submitButton('Login'); ?>
<p class="hint">You may login with <b>demo/demo</b></p>
</div>
</form>				
			
		

ログインフォームではユーザ名とパスワードの入力フィールドを表示します。 ブラウザを閉じたときに、ログイン状態を保存するチェックボックスも表示します。 ビューはローカル変数$formをもち、そのデータはUserLogin::renderContent()render()メソッドから渡されます。

LoginFormデータモデルは検証ルールを持っているため、ユーザがフォームを送信した際にデータ検証が行われます。 検証エラーがあるとフォームは入力フォームの後にエラー内容を表示します。

userLogin ポートレットを使う

UserMenuのときと同じく、/wwwroot/blog/protected/views/layouts/main.php レイアウトファイルを変更して、UserLoginを使います。

			
......
<div id="sidebar">
 
<?php $this->widget('UserLogin',array('visible'=>Yii::app()->user->isGuest)); ?>
 
<?php $this->widget('UserMenu',array('visible'=>!Yii::app()->user->isGuest)); ?>
 
</div>
......				
			
		

UserLoginはユーザがゲストのときだけ表示されることに注意してください。 UserMenuとは逆の動作です。

UserLogin ポートレットをテストする

UserLogin ポートレットをテストするため、以下の手順を踏みます。

  1. http://www.example.com/blog/index.php にアクセスし、ログインしていなければUserLogin ポートレットが表示されることを確認します。
  2. ログインフォームに何も入力せずにフォームを送信すると、エラーが表示されます。
  3. demo/demoでログインを試みます。ページが再読み込みされ、UserLoginポートレットが消え、代わりにUserMenuポートレットが表示されます。
  4. Logoutメニューをクリックし、UserMenuポートレットが消え、UserLoginポートレットが再度表示されることを確認します。

まとめ

UserLoginポートレットは典型的なMVCパターンの例です。 LoginFormモデルをデータとビジネスルールの格納に使い、 userLoginビューをUIの表示に使い、 UserLoginクラス(と小さなコントローラ)をモデルとビューを結びつけるのに使っています。

タグクラウドポートレットを作る

Tag cloudはタグの人気度を文字の大きさで視覚的に装飾して一覧表示します。

TagCloud クラスを作る

TagCloudクラスは/wwwroot/blog/protected/components/TagCloud.phpファイルに作ります。 内容は以下のとおりです。

			
<?php
class TagCloud extends Portlet
{
    public $title='Tags';
 
    public function getTagWeights()
    {
        return Tag::model()->findTagWeights();
    }
 
    protected function renderContent()
    {
        $this->render('tagCloud');
    }
}				
			
		

上記のコードでは、TagクラスのfindTagWeightsメソッドを呼んでいます。 このメソッドはタグの相対出現頻度の一覧を返します。 ひとつのタグが多くの記事に関連付けられていた場合、そのタグの重みはより高くなります。 この重みをタグの表示に利用します。

tagCloud ビューを作る

tagCloudビューは/wwwroot/blog/protected/components/views/tagCloud.phpファイルに保存されます。 TagCloud::getTagWeights()で得られた個別のタグについて、関連する記事へのリンクを表示します。 リンクのフォントサイズはタグの重みによって決定されます。重みが高ければ、フォントサイズも大きくなります。

TagCloudポートレットを使う

TagCloudポートレットの利用は簡単です。 /wwwroot/blog/protected/views/layouts/main.php レイアウトファイルを以下のように変更します。

			
......
<div id="sidebar">
 
<?php $this->widget('UserLogin',array('visible'=>Yii::app()->user->isGuest)); ?>
 
<?php $this->widget('UserMenu',array('visible'=>!Yii::app()->user->isGuest)); ?>
 
<?php $this->widget('TagCloud'); ?>
 
</div>
......				
			
		

最近のコメント ポートレットを作る

このセクションでは、最近のコメント一覧を表示するポートレットを作ります。

RecentComment クラスを作る

RecentCommentsクラスを、/wwwroot/blog/protected/components/RecentComments.phpに作ります。 ファイルの内容は以下のとおりです。

			
<?php
class RecentComments extends Portlet
{
    public $title='Recent Comments';
 
    public function getRecentComments()
    {
        return Comment::model()->findRecentComments();
    }
 
    protected function renderContent()
    {
        $this->render('recentComments');
    }
}				
			
		

上記のコードでは、CommentクラスのfindRecentCommentsメソッドを呼んでいます。 このメソッドの中身は以下のとおりです。

			
class Comment extends CActiveRecord
{
    ......
 
    public function findRecentComments($limit=10)
    {
        $criteria=array(
            'condition'=>'Comment.status='.self::STATUS_APPROVED,
            'order'=>'Comment.createTime DESC',
            'limit'=>$limit,
        );
        return $this->with('post')->findAll($criteria);
    }
}				
			
		

recentComments ビューを作る

recentCommentsビューは/wwwroot/blog/protected/components/views/recentComments.phpに保存されます。 このビューは単にRecentComments::getRecentComments()メソッドによって返されるコメントを表示するだけです。

RecentComments ポートレットを使う

/wwwroot/blog/protected/views/layouts/main.php レイアウトファイルを変更し、このポートレットを埋め込みます。

			
......
<div id="sidebar">
 
<?php $this->widget('UserLogin',array('visible'=>Yii::app()->user->isGuest)); ?>
 
<?php $this->widget('UserMenu',array('visible'=>!Yii::app()->user->isGuest)); ?>
 
<?php $this->widget('TagCloud'); ?>
 
<?php $this->widget('RecentComments'); ?>
 
</div>
......
			
		
その6.最後の仕上げ