I had some troubles with the password confirmation field for when adding updating user records, so i thought that i should share the way i got it working.
The scenario is the basic one, you have a database table (say user) and this table has a field called password, which is a sha1/md5/etc hash of the user password.
This is the workflow:
When you create a new user, the password needs to be hashed and saved, but when you update a user record, if the same scenario happens, we end up with a hash of the user hashed password, and we don't want this. Instead, on update, we will empty the user password from the model object, store it temporary in another variable then check to see if the password has been submitted in the form, if it was, it means the user password needs to be updated, therefore we need to hash the password(which is plain text now), if it wasn't submitted, then it means it doesn't need to be updated therefore, we restore it from the temporary variable.
So, here we go, the model:
if ( ! defined('YII_PATH')) exit('No direct script access allowed'); class User extends CActiveRecord { // holds the password confirmation word public $repeat_password; //will hold the encrypted password for update actions. public $initialPassword; /** * @return array validation rules for model attributes. */ public function rules() { // NOTE: you should only define rules for those attributes that // will receive user inputs. return array( //password and repeat password array('password, repeat_password', 'required', 'on'=>'insert'), array('password, repeat_password', 'length', 'min'=>6, 'max'=>40), array('password', 'compare', 'compareAttribute'=>'repeat_password'), ); } public function beforeSave() { // in this case, we will use the old hashed password. if(empty($this->password) && empty($this->repeat_password) && !empty($this->initialPassword)) $this->password=$this->repeat_password=$this->initialPassword; return parent::beforeSave(); } public function afterFind() { //reset the password to null because we don't want the hash to be shown. $this->initialPassword = $this->password; $this->password = null; parent::afterFind(); } public function saveModel($data=array()) { //because the hashes needs to match if(!empty($data['password']) && !empty($data['repeat_password'])) { $data['password'] = Yii::app()->user->hashPassword($data['password']); $data['repeat_password'] = Yii::app()->user->hashPassword($data['repeat_password']); } $this->attributes=$data; if(!$this->save()) return CHtml::errorSummary($this); return true; } }
When the user is created, we do it with the "insert" scenario, meaning that the password is required, but when we update it, we do it with the "update" scenario, meaning that the password is not required anymore, therefore when the form is submitted, the password fields can be empty, and the validation won't fail. This allows us to restore the hashed password from the temporary variable.
Just as a side note, here is how my controller methods looks:
public function actionCreate() { $user=new User('insert'); $this->saveModel($user); $this->setViewData(compact('user')); $this->render('create', $this->getViewData()); } public function actionUpdate($id) { $user=$this->loadModel($id); $user->scenario='update'; $this->saveModel($user); $this->setViewData(compact('user')); $this->render('update', $this->getViewData()); } protected function saveModel(User $user) { if(isset($_POST['User'])) { $this->performAjaxValidation($user); $msg = $user->saveModel($_POST['User']); //check $msg here } }
And this is the rendering form:
<section> <?php echo $form->labelEx($user,'password'); <div> <?php echo $form->passwordField($user,'password',array('maxlength'=>40)); <?php echo $form->passwordField($user,'repeat_password',array('maxlength'=>40)); </div> <?php echo $form->error($user,'password'); </section>
Hope it helps :)
Total 5 comments
Thanks for posting this - I've used it as a starting point and come up with a simplified version. Here's how I've done it:
Then in my model beforeSave(), I just have:
Yii::app()->user->hashPassword() is a custom hashing function I got from another wiki article here.
Default scenario when you create a model with the new keyword is insert, and when you do find() the scenario is update. No need to set them again. ;)
http://www.yiiframework.com/doc/api/1.1/CActiveRecord#__construct-detail http://www.yiiframework.com/doc/api/1.1/CActiveRecord#populateRecord-detail
Got an email for this article:
For who is having this issue, the login form must use a CForm model and define the rules() for the login fields there, also the validation will be made by the CForm model, and if the validation passes, instantiate a new User and assign it the username/email and password.
Please use something like pastebin.com and share your model so that i can take a look at.
L.E: also please make sure that in your controller you set the scenario to "update" after you load the model.
Great effort.. I followed your code but both passwords are also required in update case, please help me.
Thanks
Leave a comment
Please login to leave your comment.