Yii2中不修改SettingsForm模型,如何按new_password值条件必填current_password?
问题
在Yii Web应用中使用Dektrium的SettingsForm模型处理用户资料设置时,当前模型要求用户更新资料时必须输入current_password(当前密码),但希望仅当用户填写new_password(新密码)、意图修改密码时,才将current_password设为必填项;其他资料修改场景下无需强制填写,且不修改SettingsForm库模型。
相关代码片段
原SettingsForm模型代码
/** * SettingsForm gets user's username, email and password and changes them. * * @property User $user * * @author Dmitry Erofeev <dmeroff@gmail.com> */ class SettingsForm extends Model { /** @var string */ public $new_password; /** @var string */ public $current_password; /** @inheritdoc */ public function rules() { return [ "newPasswordLength" => ['new_password', 'string', 'max' => 72, 'min' => 6], "currentPasswordRequired" => ['current_password', 'required'], "currentPasswordValidate" => ['current_password', function ($attr) { if (!Password::validate($this->$attr, $this->user->password_hash)) { $this->addError($attr, Yii::t('user', 'Current password is not valid')); } }], ]; }
控制器action代码
public function actionAccount() { /** @var SettingsForm $model */ $model = \Yii::createObject(SettingsForm::className()); $profile = Profile::find()->where(['user_id' => Yii::$app->user->identity->getId()])->one(); $event = $this->getFormEvent($model); $this->performAjaxValidation($model); $old_attachs = []; $files_preview = []; if ($profile->gravatar_id) { $old_attachs = $profile->getImageUrl(); $files_preview = $profile->getImagePreview(); } $this->trigger(self::EVENT_BEFORE_ACCOUNT_UPDATE, $event); if ($model->load(\Yii::$app->request->post()) && $model->save()) { \Yii::$app->session->setFlash('success', Yii::t('user', 'Your account details have been updated')); $this->trigger(self::EVENT_AFTER_ACCOUNT_UPDATE, $event); if ($profile->load(\Yii::$app->request->post())) $profile->save(); $profile->avatarFile = UploadedFile::getInstance($profile, 'avatarFile'); $profile->upload(); return $this->refresh(); } return $this->render('@app/views/my-settings/account', [ 'model' => $model, 'profile' => $profile, 'old_attachs' => $old_attachs, 'files_preview' => $files_preview, ]); }
视图字段代码
<?php echo $form->field($model, 'new_password')->passwordInput() ?> <?php echo $form->field($model, 'current_password')->passwordInput() ?>
解决方案
方法一:继承原模型并重写验证规则
这是最安全且符合要求的方式,无需修改库文件,通过创建子类覆盖原规则:
- 在应用目录下创建自定义模型
app\models\CustomSettingsForm,继承自Dektrium的SettingsForm:
<?php namespace app\models; use dektrium\user\models\SettingsForm as BaseSettingsForm; class CustomSettingsForm extends BaseSettingsForm { /** * 重写验证规则 */ public function rules() { // 获取父类所有规则 $rules = parent::rules(); // 移除原有的current_password必填规则 foreach ($rules as $key => $rule) { if ($rule[0] === 'current_password' && $rule[1] === 'required') { unset($rules[$key]); break; } } // 添加条件必填规则:仅当new_password不为空时,current_password才必填 $rules[] = [ 'current_password', 'required', 'when' => function ($model) { return !empty($model->new_password); }, 'whenClient' => "function (attribute, value) { return $('#customsettingsform-new_password').val().length > 0; }" ]; // 为原密码验证规则添加生效条件 foreach ($rules as $key => $rule) { if (is_array($rule) && $rule[0] === 'current_password' && is_callable($rule[1])) { $rules[$key]['when'] = function ($model) { return !empty($model->new_password); }; break; } } return $rules; } }
- 修改控制器中的模型实例化代码,替换为自定义模型:
// 原代码 // $model = \Yii::createObject(SettingsForm::className()); // 修改为 $model = \Yii::createObject(\app\models\CustomSettingsForm::className());
方法二:动态修改模型验证规则(无需创建子类)
如果不想创建新模型类,可在控制器中动态调整模型规则:
在actionAccount方法实例化模型后,添加以下代码:
/** @var SettingsForm $model */ $model = \Yii::createObject(SettingsForm::className()); // 动态重构验证规则 $newRules = []; foreach ($model->rules() as $rule) { // 替换原必填规则为条件必填 if ($rule[0] === 'current_password' && $rule[1] === 'required') { $newRules[] = [ 'current_password', 'required', 'when' => function ($model) { return !empty($model->new_password); }, 'whenClient' => "function (attribute, value) { return $('#settingsform-new_password').val().length > 0; }" ]; } // 为密码验证规则添加生效条件 elseif (is_array($rule) && $rule[0] === 'current_password' && is_callable($rule[1])) { $rule['when'] = function ($model) { return !empty($model->new_password); }; $newRules[] = $rule; } // 保留其他原有规则 else { $newRules[] = $rule; } } // 替换模型规则 $model->rules = $newRules;
可选:视图交互优化
为提升用户体验,可添加JS逻辑,仅当用户输入新密码时才显示当前密码输入框:
<?php $this->registerJs(" const newPassInput = $('#settingsform-new_password'); const currentPassGroup = $('#settingsform-current_password').closest('.form-group'); // 输入新密码时显示当前密码字段 newPassInput.on('input', function() { if ($(this).val().length > 0) { currentPassGroup.show(); } else { currentPassGroup.hide(); $('#settingsform-current_password').val(''); } }); // 初始化时隐藏当前密码字段(如果新密码为空) if (newPassInput.val().length === 0) { currentPassGroup.hide(); } "); ?>
内容的提问来源于stack exchange,提问作者Abdelilah Dahdahi
相关产品推荐
相关产品推荐

