I’m using the SettingsForm model from Dektrium in my Yii web app for user profile settings. In this model, there’s a requirement to input the current password (current_password) whenever a user wants to update their profile. However, I want to make current_password required only if the user intends to change their password (inserting a new value in the "new_password" field; otherwise, it shouldn’t be mandatory for other profile changes.
How can I achieve that without altering the library model SettingsForm?
Here’s a snippet of the relevant parts of my code:
/**
* SettingsForm gets user's username, email and password and changes them.
*
* @property User $user
*
* @author Dmitry Erofeev <[email protected]>
*/
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'));
}
}],
];
}
This is the action in the controller
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,
]);
}
And finally the fields inside the view are
<?php echo $form->field($model, 'new_password')->passwordInput() ?>
<?php echo $form->field($model, 'current_password')->passwordInput() ?>
2
Answers
It can be made using conditional validation Yii docs:
I can think of two ways to achieve that without directly modifying the 3rd party code.
1. Extend the
SettingsForm
Model – the pretty wayYou can extend the library’s
SettingsForm
model, override itsrules()
method and tell DI container to use your model instead of library’s one.Your model can look like this:
Now we need to tell DI container to use our model when it’s looking for
dektriumusermodelsSettingsForm
. We can do that in config files. For example inconfig/web.php
:Now whenever
dektriumusermodelsSettingsForm
instance is created usingYii::createObject(SettingsForm::className());
we will get instance of ourappmodelsSettingsForm
instead and our modified rules will be used for validation.2. Modify the existing rules on the instace – the ugly way
Other option is to modify existing rules when the instance is already created. It’s a bit hacky and ugly but it might have it’s own use if you can’t use the first approach.
First we will prepare method that will find and modify rules in the controller.
When we have method to modify rules prepared we can call it in our controller’s action:
Note:
Namespaces and files used in my answer matches basic app template. If your structure is different modify them to match your structure.
Note 2:
Property
when
only affects server side validation. If you want to affect client side validation (before the form is submitted) you also need to usewhenClient
property.Note 3:
Package
dektrium/yii2-user
has been abandoned 4 years ago. It might be a good idea to look for a replacement if possible.