Post モデルのカスタマイズ

Gii ツールにより生成された Post モデルクラスは、主に二つの点において修正する必要があります。

  • rules() メソッド: モデルの属性に対する検証ルールを規定
  • relations() メソッド: リレーショナルオブジェクトを規定

情報: モデル は属性のリストから構成されます。それぞれの属性は、通常は、対応するデータベーステーブルのコラムと関連付けられます。 属性は明示的にクラスメンバ変数として宣言される場合もあり、宣言無しで黙示的に宣言されることもあります。

1. rules() メソッドのカスタマイズ

最初に、検証ルールを指定します。これは、属性の値をデータベースに保存する前に、ユーザによって入力された値が正しいものであることを保証するためのものです。 例えば、Post クラスの status 属性は、整数の 1, 2, 3 のどれかでなければなりません。 Gii ツールもそれぞれのモデルに対して検証ルールを生成します。 しかしながら、それはテーブルのカラム情報に基いたルールであり、妥当なものではないかも知れません。

要求分析に基き、rules() メソッドを以下のように修正します。

public function rules()
{
    return array(
        array('title, content, status', 'required'),
        array('title', 'length', 'max'=>128),
        array('status', 'in', 'range'=>array(1,2,3)),
        array('tags', 'match', 'pattern'=>'/^[\w\s,]+$/',
            'message'=>'タグは単語構成文字だけを含むことが出来ます。'),
        array('tags', 'normalizeTags'),
 
        array('title, status', 'safe', 'on'=>'search'),
    );
}

上記において、title, content, status 属性は必須です。 title の長さは 128 を超えてはなりません。 status 属性の値は、1 (下書き)、2 (公開)、3 (アーカイブ) のうちのいずれかでなければなりません。 tags 属性は単語構成文字とカンマしか含むことはできません。 さらに、normalizeTags を使用して、ユーザが入力したタグの文字列を正規化し、ユニークなタグがカンマで正しく分離されている文字列になるようにします。 最後のルールはサーチ機能で使用しますが、これについては後で説明します。

required, length, in, match のようなバリデータは全て Yii が提供する備え付けのものです。 normalizeTags バリデータはメソッドベースのバリデータであり、Post クラスにおいて定義する必要があります。 検証ルールを定義する方法に関するより詳細な情報は、ガイド を参照してください。

public function normalizeTags($attribute,$params)
{
    $this->tags=Tag::array2string(array_unique(Tag::string2array($this->tags)));
}

ここで、array2stringstring2array は、Tag モデルクラスで定義しなければならない新しいメソッドです。

public static function string2array($tags)
{
    return preg_split('/\s*,\s*/',trim($tags),-1,PREG_SPLIT_NO_EMPTY);
}
 
public static function array2string($tags)
{
    return implode(', ',$tags);
}

rules() メソッドで宣言されたルールは、モデルインスタンスの validate() メソッドまたは save() メソッドをコールする際に、ひとつひとつ実行されます。

注意: 覚えて欲しい重要なことがあります。rules() に出現する属性は、エンドユーザによって入力される属性でなければなりません。Post モデルの idcreate_time など、プログラムやデータベースによって設定される属性は、rules() に入れるべきではありません。詳しくは、属性への代入を安全にする を参照して下さい。

以上の変更をしたら、記事の作成ページを再び開いて、新しい検証ルールが機能していることを確認することができます。

2. relations() メソッドのカスタマイズ

最後に relations() メソッドをカスタマイズして、記事に関連するオブジェクトを指定します。 relations() で関連オブジェクトを宣言することによって、リレーショナルアクティブレコード (RAR) のパワフルな機能を引き出すことが出来ます。 すなわち、RAR を使って、複雑な SQL JOIN 構文を書くことなく、執筆者やコメント一覧などの記事に関連するオブジェクトの情報にアクセスできるようになります。

relations()メソッドを次のようにカスタマイズします。

public function relations()
{
    return array(
        'author' => array(self::BELONGS_TO, 'User', 'author_id'),
        'comments' => array(self::HAS_MANY, 'Comment', 'post_id',
            'condition'=>'comments.status='.Comment::STATUS_APPROVED,
            'order'=>'comments.create_time DESC'),
        'commentCount' => array(self::STAT, 'Comment', 'post_id',
            'condition'=>'status='.Comment::STATUS_APPROVED),
    );
}

同時に、上記メソッドで使用されている二つの定数を、Comment モデルに加えます。

class Comment extends CActiveRecord
{
    const STATUS_PENDING=1;
    const STATUS_APPROVED=2;
    ......
}

relations() で宣言したリレーションは次のような意味です。

  • 1つの記事は、1つの執筆者に所属する。執筆者のクラスは User で、記事の author_id 属性で結び付けられる。
  • 1つの記事は、多数のコメントを持つ。コメントのクラスは Comment で、コメントの post_id 属性で結び付けられる。コメントは作成日時順にソートされ、承認済み(APPROVED)のコメントだけで構成される。
  • commentCount は集計結果を返す少し特殊なリレーションで、記事が持つコメントの数を表す。

上記のリレーションを宣言することで、以下のように簡単に記事の執筆者やコメントにアクセスできます。

$author=$post->author;
echo $author->username;
 
$comments=$post->comments;
foreach($comments as $comment)
    echo $comment->content;

リレーションの宣言と使い方の詳細については、ガイド をご覧ください。

3. urlプロパティの追加

記事には、内容を閲覧するためのユニークな URL が結び付いています。この URL を取得するために、コードのいたるところで CWebApplication::createUrl を書くのではなく、Post モデルに url プロパティを追加することで、URL を生成する同一のコードを再利用できます。後で URL を美しくする方法を説明するときに、このプロパティの追加が非常に便利なことが分かります。

url プロパティを追加するために、以下のように Post クラスを修正して getter メソッドを追加します。

class Post extends CActiveRecord
{
    public function getUrl()
    {
        return Yii::app()->createUrl('post/view', array(
            'id'=>$this->id,
            'title'=>$this->title,
        ));
    }
}

URL の GET パラメータとして、post の ID に加えて title を追加していることに注目して下さい。 これは、後ほど URL をきれいにする で説明するように、主として検索エンジン最適化 (SEO) を目的としています。

Post の最上位の親クラスは CComponent なので、getUrl() という getter メソッドを追加することで $post->url という書き方ができるようになります。$post->url にアクセスすると getter メソッドが実行されて、その結果が式の値として返されます。このようなコンポーネントの機能の詳細については ガイド を参照して下さい。

4. ステータスをテキストで表現する

記事のステータスは整数でデータベースに保存されるので、エンドユーザに分かりやすく表示するために、テキスト形式での表現を提供する必要があります。大きなシステムでは、このような要求はよくあることです。

汎用的な解決策として、ここでは tbl_lookup テーブルを使います。このテーブルに整数値とテキスト表現の対応表を持たせ、このテキスト表現を他のデータオブジェクトから利用します。Lookup モデルクラスを以下のように修正し、テーブル内のテキストデータに容易にアクセスできるようにします。

class Lookup extends CActiveRecord
{
......
 
    private static $_items=array();
 
    public static function items($type)
    {
        if(!isset(self::$_items[$type]))
            self::loadItems($type);
        return self::$_items[$type];
    }
 
    public static function item($type,$code)
    {
        if(!isset(self::$_items[$type]))
            self::loadItems($type);
        return isset(self::$_items[$type][$code]) ? self::$_items[$type][$code] : false;
    }
 
    private static function loadItems($type)
    {
        self::$_items[$type]=array();
        $models=self::model()->findAll(array(
            'condition'=>'type=:type',
            'params'=>array(':type'=>$type),
            'order'=>'position',
        ));
        foreach($models as $model)
            self::$_items[$type][$model->code]=$model->name;
    }
}

新しいコードは主に二つの静的メソッド、Lookup::items()Lookup::item() を提供します。前者は、指定したデータタイプに属する文字列のリストを返します。後者は、指定したデータのタイプと値に対応する特定の文字列を返します。

ブログのデータベースには、Lookup のタイプとして PostStatusCommentStatus が事前に登録されています。前者は記事のステータスが取り得る値を示し、後者はコメントのステータスが取り得る値を示します。

さらに、コードを読みやすくするために、一連のステータスを表す整数値の定数を宣言します。コード中で各ステータス値を参照するときはこの定数を使うべきです。

class Post extends CActiveRecord
{
    const STATUS_DRAFT=1;
    const STATUS_PUBLISHED=2;
    const STATUS_ARCHIVED=3;
    ......
}

従って、Lookup::items('PostStatus') を呼ぶと、記事のステータスが取り得る値のリスト (対応する整数値をインデックスとするテキスト表現のリスト) を取得できます。また、Lookup::item('PostStatus', Post::STATUS_PUBLISHED) を呼ぶと、公開済みというステータスのテキスト表現を取得できます。

$Id$

Be the first person to leave a comment

Please to leave your comment.