Avoiding Loops In Preprocess Hooks

Photo of Greg Harvey
Thu, 2009-07-02 18:55By greg

I've just released a new module (fanfare) which seems to work pretty well. I have a couple of silly bug fixes to put up tonight (knew I should've called it "beta") but other than that it works great. It's called Node Reference Variables:
http://drupal.org/project/nodereference_variables

All it does is present a load of stuff (depending on other contrib modules installed and some admin options) via the hook_preprocess_node() preprocess hook for themers to use to do cool theming stuff with Node Reference CCK fields. Main feature I'm using is the jQuery UI tabs it provides.

But anyway, I digress. I had a few issues with it. You see, because with this module we're loading nodes from within the preprocess hook for nodes, the probability of creating an infinite loop by accident is very high. Especially when other functions are called within the hook that also load nodes. (We have to load referenced nodes to present them back as variables.)

To give an illustration, Node Reference Variables has (or had) these lines in the hook:

// check we want the mark-up
if (variable_get('nodereference_variables_render_enabled', TRUE)) {
// add the rendered node for display to the existing field array
$vars[$fieldname][$key]['node_rendered'] =
node_view($vars[$fieldname][$key]['node']);
}
?>

That all seems cool, until you try and run it. In certain circumstances during testing it causes an infinite loop (when a series of nodes reference each other, in a ring or as a pair).

This is because the hook is fired when the parent node is loaded, then for each valid Node Reference field containing data we call node_view(), which fires the hook again - and if there's a reciprocal reference in the now-loaded node, it will call node_view() again, this time with the parent node, which will fire the hook for the child, which will fire the hook for the parent and so on until PHP times out.

Avoiding this is pretty straightforward, thanks to a neat feature of template_preprocess(), the master function for preprocess hooks. You may have noticed an element in every $variables array called id. It so happens that this is an integer incremented by 1 with every pass of a *specific* implementation of a hook, e.g. mymodule_preprocess_node().

This is really handy for me. I only want my function to do stuff when the *first* parent node pass happens. To achieve what I need all I need to do is something like this in my hook:

/**
* Implementation of hook_preprocess_node().
*/
function mymodule_preprocess_node(&$vars) {
if ($vars['id'] === 1) {
// now we can do stuff knowing it will only
// happen once on the first pass
}
}
?>