October 23, 2013

Put your children in their place: A Drupal debug snippet

Most of us who work with Drupal on a regular basis have probably come across this error at some point:

Warning: Invalid argument supplied for foreach() in element_children() (line 6xxx of .../includes/common.inc).

The error is relatively self explanatory. Something that isn't iterable was passed into a foreach loop. The tricky part about debugging this one is that a lot of modules call element_children() and it can be hard to pinpoint where the error is coming from. Sometimes by process of elimination, you can figure it out, but the other day I was getting it intermittently and I needed some help. Here's a little snippet I inserted at the top of element_children():

if (!is_array($elements)) {
 // Print a function backtrace (needs devel module).
 ddebug_backtrace();
 // Supress the error
 return array();
}

It's pretty simple. We just check if $elements is an array (which it should be if it's being passed into element_children()) and print a function backtrace if it's not (ddebug_backtrace() is provided by the devel module). This should point you in the direction of the culprit. In my case it was a custom hook form alter that was acting on an array key that sometimes wasn't there. Total time to fix: about 2 minutes. Here's what element_children() looks like with the little debug snippet (don't forget to undo the snippet when you're done)

/**
 * Identifies the children of an element array, optionally sorted by weight.
 *
 * The children of a element array are those key/value pairs whose key does
 * not start with a '#'. See drupal_render() for details.
 *
 * @param $elements
 * The element array whose children are to be identified.
 * @param $sort
 * Boolean to indicate whether the children should be sorted by weight.
 *
 * @return
 * The array keys of the element's children.
 */
function element_children(&$elements, $sort = FALSE) {
 // BEGIN: debug code
 if (!is_array($elements)) {
 // Print a function backtrace (needs devel module).
 ddebug_backtrace();
 // Supress the error
 return array();
 }
 // END: debug code
 // Do not attempt to sort elements which have already been sorted.
 $sort = isset($elements['#sorted']) ? !$elements['#sorted'] : $sort;
 
 // Filter out properties from the element, leaving only children.
 $children = array();
 $sortable = FALSE;
 foreach ($elements as $key => $value) {
 if ($key === '' || $key[0] !== '#') {
 $children[$key] = $value;
 if (is_array($value) && isset($value['#weight'])) {
 $sortable = TRUE;
 }
 }
 }
 // Sort the children if necessary.
 if ($sort && $sortable) {
 uasort($children, 'element_sort');
 // Put the sorted children back into $elements in the correct order, to
 // preserve sorting if the same element is passed through
 // element_children() twice.
 foreach ($children as $key => $child) {
 unset($elements[$key]);
 $elements[$key] = $child;
 }
 $elements['#sorted'] = TRUE;
 }
 
 return array_keys($children);
}

Photo Credit: Web Scrapbook