Be careful with drupal_get_form() in theme layer

When you create Drupal code for 7 years, it's easy to mistakenly assume that you know everything regarding such basic things as theme_preprocess_node() and drupal_get_form() functions.
And that is a wrong feeling. Today I've discovered that you shouldn't mix these functions!

The bug description:
I was creating "join" form which was rendered in node.tpl.php of organic group.
When the form was submitted, validation/status messages were shown only after additional page refresh. So, after form submit page was reloading without any messages, and all the messages were appearing on the screen after additional page reload.

That was a weird bug which I never encountered earlier. After multiple-hour review of all custom modules of the website (I thought some module was doing magic things with $_SESSION array, where all the messages are stored at by drupal_set_message()) I realized that I'm looking in the wrong place.
The real issue was in our theme variables preparation.

It was a cool custom "join this group" form (we create only cool forms here in Pixeljets, no kidding), so that is how the code was looking like:

mymodule.module:

<?php  
...
function mymodule_preprocess_node(&$vars) {  
  if ($vars['node']->type == 'mygroup') {
     $vars['join_form'] = drupal_get_form('mycoolform', $vars['node']);
  }
}
...
?>

the theme, node--group.tpl.php:

<?php  
...
echo render($join_form);  
...
?>

Everything was looking good at first sight, but no validation messages were appearing after form submit.

The reason is easy (when you know it): with such code organisation, form validation happens too late, so all messages are put to $_SESSION after the moment when Drupal grabs it to show it to user on his screen.
Interesting thing is that form was working good in node teaser, but not in full node! that's because preprocess_node() was run earlier than template_process_page() on the page where we rendered node teasers.

So, I've fixed the bug by moving the form generation code from preprocessnode to hook_node_view like this:

<?php  
function mymodule_node_view($node, $view_mode) {  
   $node->content['join_form'] = drupal_get_form('mycoolform', $node);
}
?>

Now, everything is working like a charm.

Anton Sidashin

I am a web developer and CTO with 15+ years of experience, and I am passionate about performance, usability, and getting things done.

Samara, Russia