The $user object is created in one of two different ways in Drupal:

1. It is created by session.inc during bootstrap. The user 'load' op is not called here.
2. It is created by a call to user_load(). The user 'load' op is called here. This happens semi-randomly throughout the codebase, e.g. when a node author is loaded, when a user page is viewed or edited, when a user logs in or out.

The reason for this is optimization killes and I agree that it's not necessary to do a full user_load on every request.

The problem is that module writers can't be guaranteed that the $user object they are working with is a "fully loaded" $user object, as Chris Johnson notes.

The current solution is to call user_load() yourself if you want to be sure. This is expensive because user_load has no caching.

It would be a great advantage to be able to test whether user_load has already run or not. One approach would be to implement the user hook for your module and set a flag there, then test it later. But that's expensive too. So here's my proposal: a one-line patch that simply records that user_load() has run:

<?php
$user->user_loaded = TRUE;
?>

Now modules can simply check for the presence of $user->user_loaded.

Comments

hickory’s picture

Version: x.y.z » 4.7.4

Would it be easier if modules called user_load as normal, but the user_load function had a static variable that was set if it had already been run and just returned $user?

I agree that this is a problem.

killes@www.drop.org’s picture

Version: 4.7.4 » 5.x-dev

shoudl get fixed in HEAD first.

moshe weitzman’s picture

yes, a static var cache like node_load() seems best. any eaosn not to go that route?

jvandyk’s picture

How will you determine whether the calling function intends the 'load' hook to be fired or not? To be consistent with node_load, the load hook would only be fired the first time, and will require a change to the user_load function signature to reset the cache.

moshe weitzman’s picture

yes, thats what i had in mind.

the only place where we load user info without firing hook_user(load) is during session creation and i agree that that doesn't need to change.

dries’s picture

Status: Needs review » Postponed

This isn't really needed. By convention, the global $user object is the object loaded by the session handling.

If you need the full user object, do a user_load() and add caching to user_load(). It might cost you 1ms.

moshe weitzman’s picture

update: no progress. user_load() still has no static cache like node_load(). these two issues have worked on it.

pwolanin’s picture

Title: user_load() clarification » user_load() static caching
Version: 5.x-dev » 7.x-dev
Status: Postponed » Needs review
StatusFileSize
new1.86 KB

right, we should really have added a static cache to user_load in D6 since we now often take a simple int parameter. It was an unfortunate oversight (bug?).

simple (but untested) patch attached that adds such a cache.

moshe weitzman’s picture

Title: user_load() static caching » user_load() clarification
Version: 7.x-dev » 5.x-dev
Assigned: jvandyk » Unassigned
Status: Needs review » Needs work

typo at the end: if ($cachable)

pwolanin’s picture

Status: Needs review » Needs work
StatusFileSize
new2 KB

since db_fetch_object() is defined in the API as returning FALSE for no result, we can also trim a couple lines. fixed typo.

pwolanin’s picture

Title: user_load() clarification » user_load() static caching
Version: 5.x-dev » 7.x-dev
Status: Needs work » Needs review
moshe weitzman’s picture

Looks good. We need to reset this cache after a user_save(), no?

pwolanin’s picture

yea- good point. Also, we should probably remove the caching code in user_category_load()? Otherwise we are caching the same data twice.

hickory’s picture

StatusFileSize
new1.03 KB

Attached is the patch that I've been using with Drupal 5 for a year or so - it's slightly different because it uses memcache, but should illustrate what needs to be changed.

The main thing to watch out for is modules (buddylist, for example) that write directly to the {users} table without going through user_save - they either need to be rewritten to use user_save for all changes to user objects, or to have a line added that invalidates the user cache when a change is made.

sun’s picture

Q: Why do we want to clone a user object before passing it along? If a user object is changed somewhere, we would not need to reload it (again)?

Also, possibly related: #254491: Standardize static caching

mercmobily’s picture

Hi,

I hope this progresses as fast as possible, and the fix gets backported to D6.
Right now, not having user_load cache the result causes a lot of trouble.

I am writing a module that uses hook_user() and was wondering why so _many_ queries were being bounced off my DB server. Now I know...

Bye,

Merc.

frankcarey’s picture

Yeah, I'm using a panel page (Michelle's APK) adn notice that user_load is being called 11 times on one page, seems like this is gonna be a lot of overhead when the site launches.

Have others found alternative methods? (I see the memcache above)

catch’s picture

Status: Needs work » Closed (duplicate)

Went in as part of #347250: Multiple load users

bobooon’s picture

StatusFileSize
new1.82 KB

For those of you who are stuck with Drupal 5 and need a simple caching solution I have attached a patch which will get you there, effectively and reliably. It's probably not as robust as the Drupal 7 implementation but it will due for most of the performance needs. It's been tested and used on a production site for a few months with no issues.

NOTE: It also allows you to call user_load() only passing it a numeric value, rather than having to specify the entire array('uid' => X).

crea’s picture

Subscribing

carlescliment’s picture

Take care when applying patches like this.

Imagine you have a module that adds some data to users:


/**
 * Implementation of hook_user()
 */
function my_module_user($op, &$edit, &$account, $category=NULL) {
  if ($op == 'load') {
    $account->stuff = my_module_load_stuff();
  }
}

/**
 * Save some stuff to user.
 * @param $uid User uid.
 * @param $stuff Stuff
 */
function my_module_save_stuff($uid, $stuff) {
  // ...
  drupal_write_record('my_module_stuff', $item);
  // ...
}

/**
 * Load some user stuff.
 * @param $uid User uid.
 */
function my_module_load_stuff($uid) {
  // ...
  $res =  db_query('SELECT * FROM {my_module_stuff} WHERE uid = %d', $uid);
  // ...
  return $stuff;
}

As modifications are made outside user_save(), you may fall into inconsistencies if you don't explicitly clean the static cache each time you modify user-related data.

I wrote a patch to mantain users in a cache, but I'll definitely revert to the previous version. Safety beats efficiency.

robertdouglass’s picture

StatusFileSize
new1.79 KB

Here's a re-rolled version of pwolanin's patch from #8. Drupal 6.

robertdouglass’s picture

Note that #23 is not a plug-n-play safe solution. There will be cases where this caching breaks existing functionality and you will have to track them down and use the 2nd parameter to clear the cache.

Offlein’s picture

StatusFileSize
new1.75 KB

Regarding the patch in #23, using this at all seems to be "site-breakingly bad" to me, because it improperly changes the first argument, $user_info, to $array, which it used to be in 2008. This value changed a long time ago. It will cause you to not be able to log into your site.

I'm attaching a D6 patch that should accomplish the same with latest versions of Drupal.

oriol_e9g’s picture

There is a mistake in the patch:

<?php
+  $cacheable = FALSE;

+    $cachable = TRUE;

+  if ($cachable) {
?>

The var should be always "cacheable" or "cachable", not a mix :D

oriol_e9g’s picture

StatusFileSize
new1.75 KB
Offlein’s picture

Nice catch! Mine would surely generate a PHP notice or something! Thank you!

greenreaper’s picture

Issue summary: View changes

This is great, even in 2021. Thanks for the patch.

Similarly useful for D6 maintainers repeatedly accessing user profiles in e.g. comment templates is this user_load_profile caching.