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是瘦的,即大量的代码是模型中的业务逻辑和视图中的具体展示,而控制器作为胶水,只负责模型和视图的联系。