This article is about a very insidious MODX error. It involves using the id
field of the user profile to retrieve a user's profile. It will often work at first, but it can create an unholy mess over time.
The fact is that the id
field of the user profile is completely arbitrary. It reflects the order in which the profile records were created and has no real relationship to the users whose profiles are stored there. The profile records are connected to their users by the profile's internalKey
field. The internalKey
field is guaranteed to hold the user ID of the user related to that profile.
Here's the confusing thing: because the users and their profiles are usually created at the same time, the profile's id
field often matches the user ID of that profile's user. So if you try to get the current user's profile with this code, it will often work:
$profile = $modx->getObject('modUserProfile', $modx->user->get('id'));
Unfortunately, it's possible that the two will diverge over time as users and profiles are created and deleted. In that case, you will get a user profile with the code above, but it won't be the profile of the user you think it is. If you then modify the profile in your code, you'll be modifying the profile of the wrong user without knowing it. All User Profiles from that point on will have an id
field that is unrelated to the profile they contain. At that point, you'll have a giant, almost unrepairable mess on your hands.
Note that this is not usually a problem in the MODX Manager, since Users and User Profiles are created together, and MODX itself knows never to use the Profile's id
field in relation to a user object. In non-core code (e.g., your code, and that of extras), it's something to be aware of. When you call the save()
method on a new user object, for example, no profile is created.
Some Solutions
Luckily, avoiding this problem is easy. You just need to get the user profile the right way.
If you have the user object (or want the profile of the current user), you can use the getOne()
method:
/* If you have retrieved the user object yourself */
$profile = $user->getOne('Profile');
/* If you want the profile of the current logged-in user */
$profile = $modx->user->getOne('Profile');
Note the capital 'P' in 'Profile'. Profile is the xPDO alias for the modUserProfile
object. Similarly, if you have the user profile and want to get the user object, you can do this:
$user = $profile->getOne('User');
What if you have the user's ID, but not the user object? You might, for example, be using the createdby
, editedby
, or publishedby
field of the current resource. Those fields hold the user ID of the relevant user. Maybe you want to display the full name of the user who created the current resource, which is help in the User Profile. You could do this:
$id = $modx->resource->get('createdby');
$user = $modx->getObject('modUser', $id);
$profile = $user->getOne('Profile');
if ($profile) {
return $profile->get('fullname');
}
But notice that in the code above, you don't really need the user object. It's wasteful to retrieve the user object when all you need is that user's profile. Remember that the internalKey
field of the user profile always holds the correct user ID, so you can do this instead and make your snippet much more efficient:
$id = $modx->resource->get('createdby');
$profile = $modx->getObject('modUserProfile', array('internalKey' => $id));
if ($profile) {
return $profile->get('fullname');
}
Final Notes
By now you should know never to use the id
field of the user profile, but you might be wondering about the if ($profile)
line in the code above. It's unlikely, but possible, for a user to have no user profile. Users created with "New User" in the Manager will always have a profile (though it may be empty except for the user's email address, which is required). Users created in code, though, can exist with no profile. They have a user ID, but there's no corresponding record in the user profile table. If you don't check to see if you got the profile before calling the profile's get()
method, you risk having the site visitor experience a fatal, and fairly ugly, PHP error (or a blank screen) when get()
is called on a non-object.
This can be avoided by calling the "Create" processor for users, but that's actually fairly tricky because you have to handle the password options like whether the password will be created automatically and whether it should be emailed to the user. It's not obvious how to tell modx what you want.
An easier method is to create the User object and a User Profile object, then add the Profile to the User object before saving it, like this:
$user = $modx->newObject('modUser');
$user->set('username', 'JoeBlow');
$user->set('fullname', 'Joe Blow');
$user->set('password', 'somePassword');
/* Optional - other user fields here */
$profile = $modx->newObject('modUserProfile');
$profile->set('email', '[email protected]');
$user->addOne($profile);
$user->save();
If you have many fields to add to either object, it may be easier to use fromArray()
rather than calling set
on each one. See the documentation here. The final example is my preferred method.
Bob Ray is the author of the MODX: The Official Guide and dozens of MODX Extras including QuickEmail, NewsPublisher, SiteCheck, GoRevo, Personalize, EZfaq, MyComponent and many more. His website is Bob’s Guides. It not only includes a plethora of MODX tutorials but there are some really great bread recipes there, as well.