Yii之从User-Group-Role例子理解Model-View-Controller

模型都用Gii生成,Role只生成Model,Group和User生成对应的CRUD(控制器和视图)

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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值