What is it? Everything this simple class does is to wrap around the user_load() function, loading a user from the Drupal database only if he does not exist in the cache yet, and returning the cached user object otherwise. The class is inspired by the Singleton design pattern.

Why all this? In its default configuration, Heartbeat stores the username along with a corresponding message (e.g. !username added a new article whereas !username gets replaced by the HTML code for a username linked to its profile) so that everytime a Heartbeat stream is generated you can just display the message(s) without having to query the database again for the username, heavily reducing the database traffic which makes sense as the streams tend to be displayed throughout the site often (think of Facebooks news stream). On the other hand, however, when thinking of use-cases, this way of caching imho is practically useless, because you would most certainly like to display the user picture along with a message, i.e. you would have to load the user object anyways which makes Drupal to query against the database. Furthermore, you might find a use-case where a user can change his username, e.g. in a social network or intranet application, which would lead to inconsistencies if the username is stored along with a message in the Heartbeat database table. Therefore I created this simple caching class as a fit between minimal database load and maximal customization.

Do you need it? Using this class might be helpful if you want to customize the Heartbeat stream by displaying additional and/or editable user information along with a message, e.g. user picture.

How to use it? In the Heartbeat and Rules settings, do not log the username (or any other user related information) but just the message itself (e.g. the shout message, the node id, etc.). In your custom module, include the PHP class below (e.g. include('UserCache.class.inc.php');) and implement the yourmodule_heartbeat_theme_alter() (and other) hooks to customize the Heartbeat messages of your stream. Load the user information manually but using UserCache::get($uid) instead of Drupals user_load() function.

/**
 * UserCache
 * 
 * a simple class to cache user objects. A user is only
 * loaded from the database if he does not exist in the 
 * cache yet.
**/
class UserCache {
  private static $cache = null;
  /**
   * UserCache::get()
   * 
   * reads a user from the database if he does not yet exist
   * in the cache or returns the cached user otherwise
   * 
   * @param int $user the user id
   * @return a user object
  **/
  public static function get($user) {
    if (is_numeric($user) && $user) {
      if (self::$cache[$user] == null) {
        self::$cache[$user] = user_load($user);
      } 
      return self::$cache[$user];
	} else {
	  return $user;
	}
  } 
  
}

Feature request?I thought this might be useful for others, so I shared it. I marked it "feature request" and "needs review" as there is no category which fits better. You might just close this ticket as fixed or whatever. Anyways, because of the above mentioned use-cases, I would like to suggest to change the way Heartbeat is "caching" the user information. Imho a better way would be to extend the heartbeat_messages table adding fields for the most commonly needed user information, e.g. username, user picture, maybe his organic groups, and then keeping these fields up to date by using hooks whenever the corresponding information changes, e.g. when a user changes his username, update all messages. Such changes will occur far less than the times a Heartbeat stream is generated; furthermore updating the table costs only 1 query. E.g. assume that a user changes his user information every half year, and that a stream is displayed once a day (i.e. 180 times per half year):

  1. Current storage: 1x UPDATE TABLE user (user changes his user information) +
    180x SELECT FROM TABLE heartbeat_message (load all messages for a stream) +
    180x z x SELECT FROM TABLE user (for every message, load additional user information)
  2. Proposed storage: 1x UPDATE TABLE user (user changes his user information) +
    1x UPDATE TABLE heartbeat_message (also update the user information in all Heartbeat messages) +
    180x SELECT FROM TABLE heartbeat_message (load all messages for a stream)

Depending on the z (how many different users are included within a stream) and the assumption how often a stream is accessed (I guess it will be much more than once a day) this could save a lot of database load. Or am I not seeing something?

Comments

Stalski’s picture

jup i intended to do that. but it's possible without a class. I am very fond of OOP but in this case, it will be faster like this. The load of just a class file include will be already higher that the function beneath.

function heartbeat_user_load($uid) {
  static $users;
  if (!isset($users[$uid])) {
    $users[$uid] = user_load($uid);
  }

  return $users[$uid];
}
Stalski’s picture

Besides, this is not helpfull at logging time what so ever. The load performance on "event action" is not as expensive as during display time.
On the other hand, it's usefull when loading the actor for instance. I don't think you saw this, but the actor is loaded in a heartbeatActivity message. So i planned this function for a while and i will implement this.

I think you're idea is good, but that's what i would recommend to drupal core code to load users, not within heartbeat. As you mentioned, lots of modules could use this (adding a "reset" variable). Maybe you should offer this to the drupal maintainers and i would love to use this class invocation in heartbeat then ;)

Stalski’s picture

Done :)

jaochoo’s picture

The load of just a class file include will be already higher that the function beneath.

Thanks for the advise, I did not know that. I will move it to my custom module then.. I also store the themed username along with it; as I am using the RealName module, which uses its own database table to store the realname (i.e. a name consisting of a combination of other values, e.g. of "first name" and "last name" field of a Content Profile CCK type), this would mean another query per every user who is part of a stream.

On the other hand, it's usefull when loading the actor for instance. I don't think you saw this, but the actor is loaded in a heartbeatActivity message. So i planned this function for a while and i will implement this.

Not sure if I understand you. I am only using this as a replacement for the core user_load() (as well as, see above, the theme('username', $user) function) when I build/theme my custom stream. I do not use it for logging anything.

I think you're idea is good, but that's what i would recommend to drupal core code to load users, not within heartbeat. As you mentioned, lots of modules could use this (adding a "reset" variable).

Haha, funny enough, I first thought that my idea was useless, because I assume that the core user_load() must already do this, but after looking into the API I was surprised that in core it just loads from the database without caching any user object. So then I was thinking the same like you: How could I easily wrap all user_load() calls with my own function (instead of just walking through my theme and replacing it manually)? I was thinking about the hook_user($op = 'load') but according to the API it is called after the user object was loaded, not before.

Done :)

Added it to the next dev version?

jaochoo’s picture

As for Drupal core, other had the idea before. Even since D4 as it seems o.O Strange that it did not make it into core so far (as far as I completely read and understood the threads):
http://drupal.org/node/281596
http://drupal.org/node/91786

Stalski’s picture

Status: Needs review » Fixed

Heartbeat now has a function heartbeat_user_load that takes care of performance where possible on multiple user loads.

ManyNancy’s picture

I was just about to propose something like this. But shouldn't this be a general module so not not everyone implements their own user cache.

Stalski’s picture

Status: Fixed » Closed (fixed)

Jup but that's not an issue for me, i would be glad if this is fixed in drupal core.

jaochoo’s picture

Have a look at the links posted above (and maybe search further): There are some patches (for Drupal core though; do not know why this did not make it into core yet while it was discusses since 4.7 as it seems) provided to cache the user within the user_load() method directly.