Gii
ツールによって生成されたPost
モデルクラスは、主に2箇所を変更する必要があります。
rules()
メソッド:モデル属性の検証ルールを指定します。relations()
メソッド:関連オブジェクトを指定します。情報:モデルは、属性のリストで構成され、それぞれ対応するデータベーステーブルの列に関連付けられています。属性は、クラスメンバ変数として明示的に宣言することも、宣言せずに暗黙的に宣言することもできます。
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'=>'Tags can only contain word characters.'),
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)));
}
ここで、array2string
とstring2array
は、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()メソッドを呼び出すと、1つずつ実行されます。
注記:
rules()
に表示される属性は、エンドユーザーが入力する属性であることを覚えておくことが非常に重要です。Post
モデルのid
やcreate_time
など、コードまたはデータベースによって設定されるその他の属性は、rules()
に含めるべきではありません。詳細については、属性の割り当ての保護を参照してください。
これらの変更を加えた後、投稿作成ページに再びアクセスして、新しい検証ルールが有効になっていることを確認できます。
relations()
メソッドのカスタマイズ ¶最後に、relations()
メソッドをカスタマイズして、投稿の関連オブジェクトを指定します。これらの関連オブジェクトをrelations()
で宣言することで、複雑なSQL JOIN文を書く必要なく、作成者やコメントなど、投稿の関連オブジェクト情報にアクセスするために、強力なRelational ActiveRecord (RAR)機能を利用できます。
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),
);
}
上記メソッドで使用される2つの定数をComment
モデルクラスにも導入します。
class Comment extends CActiveRecord
{
const STATUS_PENDING=1;
const STATUS_APPROVED=2;
......
}
relations()
で宣言されたリレーションは、次のことを示しています。
User
であり、投稿のauthor_id
属性値に基づいて関係が確立される作成者に属します。Comment
であり、コメントのpost_id
属性値に基づいて関係が確立される多くのコメントがあります。これらのコメントは作成時刻に従ってソートされ、コメントは承認済みである必要があります。commentCount
リレーションはやや特殊で、投稿が持つコメント数の集計結果を返します。上記のリレーション宣言により、次のように投稿の作成者とコメントに簡単にアクセスできます。
$author=$post->author;
echo $author->username;
$comments=$post->comments;
foreach($comments as $comment)
echo $comment->content;
リレーションの宣言方法と使用方法の詳細については、ガイドを参照してください。
url
プロパティの追加 ¶投稿は、表示するための固有のURLに関連付けられたコンテンツです。コードのいたるところでCWebApplication::createUrlを呼び出してこのURLを取得する代わりに、Post
モデルにurl
プロパティを追加して、同じURL作成コードを再利用できるようにすることができます。後でURLの整形方法について説明しますが、このプロパティを追加すると非常に便利になります。
url
プロパティを追加するには、次のようなゲッターメソッドを追加してPost
クラスを変更します。
class Post extends CActiveRecord
{
public function getUrl()
{
return Yii::app()->createUrl('post/view', array(
'id'=>$this->id,
'title'=>$this->title,
));
}
}
投稿IDに加えて、URLに投稿タイトルもGETパラメーターとして追加することに注意してください。URLの整形で説明するように、これは主に検索エンジン最適化(SEO)を目的としています。
CComponentがPost
の最終的な祖先クラスであるため、ゲッターメソッドgetUrl()
を追加すると、$post->url
のような式を使用できます。$post->url
にアクセスすると、ゲッターメソッドが実行され、その結果が式の値として返されます。このようなコンポーネント機能の詳細については、ガイドを参照してください。
投稿のステータスはデータベースに整数として保存されているため、エンドユーザーに表示する際に直感的に理解できるように、テキスト表現を提供する必要があります。大規模システムでは、同様の要件が非常に一般的です。
一般的なソリューションとして、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;
}
}
新しいコードは、主に2つの静的メソッド、Lookup::items()
とLookup::item()
を提供します。前者は指定されたデータ型に属する文字列のリストを返し、後者は指定されたデータ型とデータ値の特定の文字列を返します。
ブログデータベースには、PostStatus
とCommentStatus
という2つのルックアップタイプが事前に設定されています。前者は可能な投稿ステータスを表し、後者はコメントステータスを表します。
コードを読みやすくするために、ステータス整数値を表す一連の定数を宣言します。対応するステータス値を参照する際には、コード全体でこれらの定数を使用する必要があります。
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)
を呼び出して、公開ステータスの文字列表現を取得できます。
タイプミスを見つけたり、このページの改善が必要だと思われる場合は、
GitHubで編集してください !
この段階では新しい投稿を作成できません(後で追加されるbeforeSaveが不足しています)
「1-rules()メソッドのカスタマイズ」の最後で、「これらの変更を加えた後、投稿作成ページに再びアクセスして、新しい検証ルールが有効になっていることを確認できます。」と記載されていますが、新しいエントリを作成しようとしないでください(機能しません(外部キー制約がスローされます))。
投稿の作成に進めるには、「投稿の作成と更新」の次の手順を完了する必要があります(beforeSave()呼び出しとauthor_idのインスタンス化のため)。
それでも試したい場合は、新しいPostを作成するときにauthor_idを示すbeforeSave()メソッドをPostモデルに追加してください。
protected function beforeSave() { if(parent::beforeSave()) { if($this->isNewRecord) { $this->author_id=Yii::app()->user->id; } return true; } else return false; }
Tag::string2array()とTag::array2string()がありません
PostモデルでnormalizeTags()メソッドを使用する場合、2つのTagモデルメソッドを参照しています。
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); }
ステータスコードの表現は、Lookupモデルに/追加する/必要があります。
次のセクションでドロップダウンリストで問題が発生している場合は、Lookupモデルに問題がある可能性があります。上記の推奨コードはLookupモデルに追加するものであり、置き換えるものではありません。このチュートリアルの他の場所では、コードサンプルに省略記号(つまり「.....」)があり、残すべき部分を示し、コードサンプルが既存のコードを置き換える場合は省略記号がありません。しかし、ここではそうではありません。このコードをLookupモデルに追加します。
リレーションに関する重要な点
relations()メソッドを説明した後、チュートリアルは次のように述べています…
$author=$post->author; echo $author->username; $comments=$post->comments; foreach($comments as $comment) echo $comment->content;
これは注意深く読む価値があります。ポイントは、relations()メソッドが関連テーブルの属性を参照クラスの属性にすることです。つまり、authorテーブルの属性は、postの元の属性と同じように、post内で利用できるようになりました。つまり、`$post->post_id`と`$post->author->username`です。usernameはPostクラスにはどこにも記述されていませんが、relations()式を通してAuthorから継承されています。
postsモデルの最初のルール行を変更して動作させる必要がありました。
author_idも追加するために、ルールの最初の行を変更する必要がありました。
array('title, content, status, author_id', 'required'),
そうでなければ、「外部キー制約エラー」が表示されていました。
CDbCommand failed to execute the SQL statement: SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`yiiblog`.`tbl_post`, CONSTRAINT `FK_post_author` FOREIGN KEY (`author_id`) REFERENCES `tbl_user` (`id`) ON DELETE CASCADE). The SQL statement executed was: INSERT INTO `tbl_post` (`title`, `content`, `tags`, `status`) VALUES (:yp0, :yp1, :yp2, :yp3)
また、下部にあるRevelisによるコメント#4625も参照してください。
Lookupクラス
LookupコントローラーでtableNameを定義することを忘れないでください。
public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return '{{lookup}}'; }
新規投稿作成時の時刻設定
まず、post.php-ModelにbeforeSave()メソッドを追加する必要があります。これは、Revelis Luc Bonninからのコメント#4625への追加です。日付と時刻を設定するには、beforeSave()メソッドに次のように追加します。
protected function beforeSave() { ... if($this->isNewRecord) { # set time on creating posts $this->create_time=$this->update_time=time(); ... } else # changes time at updating the post $this->update_time=time(); return true; } ... }
コメントするには、サインアップまたはログインしてください。