I have a profile for each user. Its represented in ‘profiles’ table with a matching Profile class (of type CActiveRecord). The Profile class is related to the user class in 1:1 relationship, as you’d expect (every user has 1 profile, and vice versa).
In my custom ‘webuser’ class (that extends CWebUser), I’ve arranged that during login the user identity class loads the profile record of the user and does: $this->setState(‘myProfile’, $profile).
I noticed a problem: when I update the user’s profile (lets say the user updated his last name), save it back to the web user (with ‘Yii::app()->user->myProfile->first_name = ‘somename’’, then do $profile->save, if there was some validation problem, the data in the myProfile attribute of the web user was already updated but the firing of $profile->save() fails and thus the DB is not updated - introducing integrity error (the DB has one version, the web user contains another version).
I’m kind of puzzled. I think I misused the setState() stuff. Is it correct to assume that anything that can change during the current user’s session’s lifetime should NOT be setState()?
How otherwise would you put order into this equation?
I thought about this but its not a good solution IMHO: this requires the programmer (whoever that might be) to always follow this non-intuitive order of statements. The needed order is not implied or understood by the code itself.
Maybe its the best I could find though . Lets see if anyone has a better thought on this.
I agree with redguy. The save() method performs validation as well as saving the data, so it does make sense to call it first. This way you’ve verified that the data is acceptable before using it to update the profile in the session.
storing profile in database has higher priority as it is persistent. So first you try to persist the profile and if it succeed - you update cached profile in session (user state is simply session cache)
In the complete implementation you need to be careful. The following holds a hidden bug. Can you spot it? [size="2"]:[/size]
// lets take the profile data out
$profile = Yii::app()->user->myProfile;
$profile->relevant_attr = $some_value;
// now lets attempt to save the profile and only it, before updating the profile record stored in the user's session (in the WebUser object).
if( $profile->save() ) {
// yey! validation passed, profile record saved, now just save the profile in the WebUser object.
// update profile object in the WebUser object:
Yii::app()->user->myProfile->relevant_attr = $profile->relevant_attr;
// signal ok - return true, for example
}
// signal error - return false, for example
The above contains a fundamental error that completely destroys the solution. Its the first line of code - the ‘taking’ of the profile record from the WebUser object. Thing is, as written above, the profile record is assigned to $profile by reference (as objects normally do). This implies that any update to the $profile variable actually changes the same data as the one used by the WebUser object!.. .
The solution? Simple:
Change:
$profile = Yii::app()->user->myProfile;
To:
$profile = clone(Yii::app()->user->myProfile);
Which will effectively copy the profile object and ensure the separation we need here.