User模型的验证规则:
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('default_group_id, username, password, status', 'required'),
array(
'default_group_id, status',
'numerical',
'integerOnly' => true),
array(
'username, keystr',
'length',
'max' => 50),
array(
'password, wxid',
'length',
'max' => 255),
array(
'username, status',
'safe',
'on' => 'searchNotInGroup'),
array(
'username',
'unique',
'message' => '用户“{value}”已经存在'),
// The following rule is used by search().
// @todo Please remove those attributes that should not be searched.
array(
'id, default_group_id, username, password, wxid, status, keystr',
'safe',
'on' => 'search'),
);
}
关于验证规则的使用,可以参考 blog例子中 自定义日志模型 部分,权威指南3.2.2申明验证规则部分,或cookbook第4章开头部分。总的来说,验证规则的格式如下:
array('用逗号分隔的属性列表', '验证器别名', 'on'=>'逗号分隔的场景列表', ...其它选项)
验证器别名和某个CValidator的子类对应,如上面的unique别名(在数据表中是一个唯一性约束)和CUniqueValidator类对应,我们使用了$message属性,所以传递了对应参数,通过查阅手册,可以知道$message用于用户自定义错误提示信息,并且占位符{attribute}和{value}可以替换成属性名和属性值,我们用了{value},所以,如果已经有用户“张三”,再创建时就提示“用户张三已经存在”。
验证器别名和类之间的对应关系可以参考权威指南3.2.2部分,值得注意的是,有些别名并非类名的中间部分:
in对应 CRangeValidator, length对应 CStringValidator, match对应 CRegularExpressionValidator, numerical对应 CNumberValidator
User模型与其它模型的关系:
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'courses' => array(
self::HAS_MANY,
'Course',
'teacher_id'),
'defaultGroup' => array(
self::BELONGS_TO,
'Group',
'default_group_id'),
'profile' => array(
self::HAS_ONE,
'Profile',
'owner_id'),
'tblGroups' => array(
self::MANY_MANY,
'Group',
'{{role}}(user_id, group_id)'),
'roles' => array(
self::HAS_MANY,
'Role',
'user_id'), //关系roles建立的through关系
'groups' => array(
self::HAS_MANY,
'Group',
array('group_id' => 'id'),
'through' => 'roles'),
);
}
关于模型间的关系,可以参考blog例子自定义模型部分,权威指南4.5关联的AR 部分。申明的关系每一条的格式如下:
'关系名(即一个变量名)'=>array('关系类型(4种之一)', '相关的AR类', '外键', ...其它选项)
上述格式中,相关的AR类和外键表明了当前模型通过哪个外键与别的AR类发生联系的,值得注意的是,对于多对多关系,外键需要使用“表名(外键1,外键2)”的方式。我们的例子中,关系名'groups'使用了额外的选项'through',指明该关系是通过另一个关系作为媒介的,可以参考权威指南4.5.10带through的关联查询。当然,我们的例子中使用这一关系不是必须的,因为前面已经申明了关系'tblGroups'。
模型中tableName()指明对应的表名,attributeLabels()指明属性(列)名和对应的显示标签,静态类方法model()常用于各种直接查询,search()常用于场景,返回一定的数据提供者(往往和过滤器表单一起用),这些方法都相对比较固定。模型中其它的主要包括业务逻辑所需的自定义方法,类事件方法(即beforeXxx和afterXxx方法)。
在User模型中,我们新定义了一个场景searchNotInGroup(搜索所有不在指定组的用户,返回数据提供者):
public function searchNotInGroup(Group $group)
{
$connection = Yii::app()->db;
$sql = 'select user_id from {{role}} except select user_id from {{role}} where '.
'group_id='.$group->id;
$command = $connection->createCommand($sql);
$rows = $command->queryAll();
$user_ids = array();
foreach($rows as $row) $user_ids[] = $row['user_id'];
$criteria = new CDbCriteria;
$criteria->compare('username', $this->username, true); //用户名模糊查询
$criteria->compare('status', $this->status);
$criteria->addInCondition('id', $user_ids);
$criteria->order = 'username';
return new CActiveDataProvider('User', array(
'criteria' => $criteria,
'pagination' => array(
'pageSize' => 3,
),
));
}
User模型还包括加入到组和退出某个组的业务逻辑相关方法:
public function joinGroup($group)
{
$role = new Role;
$role->user_id = $this->id;
$role->group_id = $group->id;
$role->note = '用户['.$this->username.']加入组['.$group->name.']';
$role->save();
}
/**
* User::leaveGroup() 当前用户离开一个组(即去除某种角色)
*
* @param mixed $group
* @return void
*/
public function leaveGroup($group)
{
if($role = Role::model()->findByPk(array('user_id' => $this->id, 'group_id' => $group->id)))
$role->delete();
}
在UserController的view动作中中我们使用了模型中的场景(我们把属性赋值了的模型$groupmodel传递到视图中去):
public function actionView($id)
{
$groupmodel = new Group('searchNotContainUser');
$groupmodel->unsetAttributes();
if(isset($_GET['Group']))
$groupmodel->attributes = $_GET['Group'];
$this->render('view',array(
'model'=>$this->loadModel($id),
'groupmodel' => $groupmodel
));
}
在UserController中,我们还新建了加入到组和退出组的动作(利用了模型中的业务逻辑代码),这两个动作没有对应视图,因为动作完后直接重定向到view视图了:
public function actionJoinGroup($id, $groupid)
{
if($group = Group::model()->findByPk($groupid))
$this->loadModel($id)->joinGroup($group);
$this->redirect(array('view', 'id' => $id));
}
public function actionLeaveGroup($id, $groupid)
{
if($group = Group::model()->findByPk($groupid)){
$this->loadModel($id)->leaveGroup($group);
}
$this->redirect(array('view', 'id' => $id));
}
在user/view视图中,我们使用控制器这个“胶水”带过来的有关变量:
<h1>View User #<?php echo $model->id; ?></h1>
<?php $this->widget('zii.widgets.CDetailView', array(
'data'=>$model,
'attributes'=>array(
//'id',
array(
'label' => '默认组',
'type' => 'raw',
'value' => $model->defaultGroup->name,
),
'username',
'password',
'wxid',
array(
'label' => '状态',
'type' => 'raw',
'value' => Lookup::item('userStatus', $model->status),
),
'keystr',
),
)); ?>
<div id="groups">
<h3>隶属的组</h3>
<ol>
<?php foreach($model->groups as $group): ?>
<li>
<?php
echo CHtml::link($group->name, array('group/view', 'id' => $group->id));
echo ' ';
if($group->id != $model->default_group_id){
echo CHtml::link('离开改组', array('leaveGroup', 'id' => $model->id, 'groupid' => $group->id));
}else{
echo '**不能离开默认组';
}
?>
</li>
<?php endforeach; ?>
</ol>
</div>
<div class="search-form" style="display:none">
<?php $this->renderPartial('_findgroup',array(
'groupmodel'=>$groupmodel,
)); ?>
</div><!-- search-form -->
<h3>可加入到以下组</h3>
<?php
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'group-grid',
'dataProvider'=>$groupmodel->searchNotContainUser($model),
'filter'=>$groupmodel,
'columns'=>array(
//'id',
'name',
array(
'name' => 'status',
'value' => 'Lookup::item("groupStatus", $data->status)',
'filter' => Lookup::items('groupStatus'),
),
array(
'class'=>'CLinkColumn',
'labelExpression' => '"加入组:".$data->name',
'urlExpression' => 'array("joinGroup", "id" =>'. $model->id .', "groupid" => $data->id)'
),
),
));
?>
上面的代码中$model->defaultGroup,$model->groups都用到了模型关系,$model和$groupmodel则是控制器传递到视图的变量。
以上代码基本符合一个原则:在MVC模型中,M和V应该是胖的,C是瘦的,即大量的代码是模型中的业务逻辑和视图中的具体展示,而控制器作为胶水,只负责模型和视图的联系。