You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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() ?>

解决方案

方法一:继承原模型并重写验证规则

这是最安全且符合要求的方式,无需修改库文件,通过创建子类覆盖原规则:

  1. 在应用目录下创建自定义模型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;
    }
}
  1. 修改控制器中的模型实例化代码,替换为自定义模型:
// 原代码
// $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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 14:57:06