由Gii
工具生成的Post
模型类主要需要在两个地方修改
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);
}
当我们调用模型实例的validate()或save()方法时,rules()
方法中声明的规则将逐一执行。
注意:务必记住,出现在
rules()
中的属性必须是最终用户输入的属性。其他属性,例如Post
模型中的id
和create_time
,由我们的代码或数据库设置,不应在rules()
中。有关更多详细信息,请参阅保护属性赋值。
进行这些更改后,我们可以再次访问文章创建页面以验证新的验证规则是否生效。
relations()
方法 ¶最后,我们自定义relations()
方法以指定文章的关联对象。通过在relations()
中声明这些关联对象,我们可以利用强大的关系ActiveRecord(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()
中声明的关系表明
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
属性,我们通过添加如下所示的getter方法修改Post
类
class Post extends CActiveRecord
{
public function getUrl()
{
return Yii::app()->createUrl('post/view', array(
'id'=>$this->id,
'title'=>$this->title,
));
}
}
请注意,除了文章ID之外,我们还在URL中添加了文章标题作为GET参数。这主要是出于搜索引擎优化(SEO)的目的,我们将在美化URL中进行描述。
因为CComponent是Post
的最终祖先类,所以添加getter方法getUrl()
使我们能够使用类似$post->url
的表达式。当我们访问$post->url
时,将执行getter方法,并将结果作为表达式的值返回。有关此类组件功能的更多详细信息,请参阅指南。
因为文章的状态以整数形式存储在数据库中,所以我们需要提供文本表示,以便在显示给最终用户时更直观。在一个大型系统中,类似的需求非常普遍。
作为通用解决方案,我们使用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()
。前者返回属于指定数据类型的字符串列表,而后者返回给定数据类型和数据值的特定字符串。
我们的博客数据库预先填充了两种查找类型:PostStatus
和CommentStatus
。前者指的是可能的文章状态,后者指的是评论状态。
为了使我们的代码更易于阅读,我们还声明了一组常量来表示状态整数值。在引用相应的状态值时,我们应该在代码中使用这些常量。
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()方法”的最后,当提到“进行这些更改后,我们可以再次访问文章创建页面以验证新的验证规则是否生效”时,不要尝试创建新条目,因为它将不起作用(抛出FK约束)。
您必须完成下一步“创建和更新文章”才能继续创建文章(由于beforeSave()调用和author_id实例化)。
如果您无论如何想尝试,只需在Post模型中添加一个beforeSave()方法,以在创建新文章时指示author_id
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() 方法将相关表的属性变成了引用类的属性。也就是说,作者表的属性现在可以在 post 中以与 post 原有属性相同的方式使用。例如,$post->post_id 和 $post->author->username。尽管在 Post 类中没有提到 username,但它通过 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 控制器中定义 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; } ... }
请注册或登录以发表评论。