theme hooks are documented as responsible for rendering child elements in render arrays, most don't do this, many are incapable

Created on 8 August 2013, over 11 years ago
Updated 28 January 2024, 11 months ago

The docs state that theme hooks are "responsible" for rendering child elements in render arrays.

  // Call the element's #theme function if it is set. Then any children of the
  // element have to be rendered there. If the internal #render_children
  // property is set, do not call the #theme function to prevent infinite
  // recursion.

In some cases, the hooks kind of "cheat" by calling drupal_render() in their preprocess functions, hence the need for #render_children in drupal_render(), but at least the job gets done.

More worryingly, however, there are theme functions and templates that do *not* render the child elements at all, but *do* invent their own attributes that look suspiciously like awkward placeholders for where the rendered children should go.

Take #theme 'maintenance_page' for example, it has two variables defined in the theme registry, 'content' and 'show_messages'.

This means that if we build a renderable array like this:

$maintenance_page = array(
  '#theme' => 'maintenance_page',
  '#content' => 'foo',
  'child' => array('#markup' => 'bar'),
);
print $maintenance_page;

We get back a maintenance page with the "content" of foo, but bar is not rendered. So, two concerns here:

- Why have we invented #content when the concept of "child elements" already exists in the render API?
- Why does this theme hook not handle rendering children, something the docs explicitly state theme hooks that are not #theme_wrappers are required to do?

There are *lots* of theme hooks in core that define a "content" variable or equivalent. I'm sure this is for historical reasons, but it now appears to be at best very "crufty" and at worse a bug.

To make matters worse, the way that many theme hooks are implemented actually makes it impossible for them to "be responsible for" rendering child elements as theme() strips the child elements out of the $variables array that is passed to the preprocess and theme function/templates unless 'render element' is set:

 // If a renderable array is passed as $variables, then set $variables to
  // the arguments expected by the theme function.
  if (isset($variables['#theme']) || isset($variables['#theme_wrappers'])) {
    $element = $variables;
    $variables = array();
    if (isset($info['variables'])) {
      foreach (array_keys($info['variables']) as $name) {
        if (isset($element["#$name"]) || array_key_exists("#$name", $element)) {
          $variables[$name] = $element["#$name"];
        }
      }
    }
    else {
      $variables[$info['render element']] = $element;
      // Give a hint to render engines to prevent infinite recursion.
      $variables[$info['render element']]['#render_children'] = TRUE;
    }
  }
🐛 Bug report
Status

Active

Version

11.0 🔥

Component
Theme 

Last updated about 1 hour ago

Created by

🇦🇺Australia thedavidmeister

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

Production build 0.71.5 2024