Put your children in their place: A Drupal debug snippet

Most of us who work with Drupal on a regular basis have probably come accross 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