Greetings, folks! There's been talk for the past week or so about the 'pull model' patch for Forms API that chx rolled. It's been committed after some furious rounds of polishing, and it removes many barriers for advanced use of the Forms API. It DOES, however, require some conversion/updates to existing functions that generate and display forms. Here's the quick and easy guide to the changes.
 
1) What changed and why?
Previously, Forms API used a 'push model.' You built an array of form elements, then called drupal_get_form($form_id, $form) to 'push' it into the API for processing and rendering. This worked OK for many situations, but also made it terribly cumbersome for programmatically submitting forms (for import/export). It also made workflow-oriented systems that chained multiple forms together a lot more hacky and kludgy than necessary.
 
Now, the Forms API uses a 'pull model.' You call drupal_get_form($form_id), and it 'pulls' the form from a builder function, then processes and renders it as appropriate. That means that existing modules need to separate their form-building code from their general page and content rendering code -- most already do this, but it's an important distinction, and making it 'official' allows the system to treat forms more consistently.
 
2) So, how do I convert my code?
By default, Forms API will check for a function that shares the same name as the form's ID. For example:
 
<?php
function mymodule_edit_record($record) {
  $output = 'This is my edit page!';
  $form['my_field'] = array(
    '#type' => 'textfield',
    '#title' => 'Record name',
    '#default_value' => $record->name,
  );
  $output .= drupal_get_form('record_edit_form', $form);
  return $output;
}
?>
 
Would need to change to the following:
 
<?php
function mymodule_edit_record($record) {
  $output = 'This is my edit page!';
  $output .= drupal_get_form('mymodule_edit_record_form', $record);
  return $output;
}
 
function mymodule_edit_record_form($record) {
  $form['my_field'] = array(
    '#type' => 'textfield',
    '#title' => 'Record name',
    '#default_value' => $record->name,
  );
  return $form;
}
?>
 
Once that 'builder' function is defined, you can call drupal_get_form('mymodule_edit_record_form', $record) anywhere in your code, and the API will handle retrieving it. In fact, if your form page was simple and just consisted of building a form, then returning the results of drupal_get_form(), you can now use the following trick when you define the menus for your module:
 
<?php
function mymodule_menu($may_cache = TRUE) {
  $items[] = array(
    'path' => 'mymodule/form-page',
    'title' => t('edit a record'),
    'callback' => 'drupal_get_form',
    'callback arguments' => array('mymodule_form_builder')
    'type' => MENU_LOCAL_TASK
  );
  return $items;
?>
 
In the above example, instead of putting a custom page-generation function in as your callback, and adding a single drupal_get_form($form_id) call as its body, you can just tell the menu system to call drupal_get_form() with your form ID.
 
3) What if I have several forms that use different IDs, but share the same layout, submit and validate functions, and so on?
We're glad you asked. Modules can now implement hook_forms() to handle complex mapping of form IDs to builder functions. It's how node.module maps the id for each node type editing form to the central form-building function for the node edit screen. Here's an example:
 
<?php
function mymodule_hook_forms() {
  $forms['mymodule_first_form'] = array(
    'callback' => 'mymodule_form_builder',
    'callback arguments' => array('some parameter'),
  );
  $forms['mymodule_second_form'] = array(
    'callback' => 'mymodule_form_builder',
    'callback arguments' => array('a different parameter'),
  );
  return $forms;
}
 
function mymodule_first_page() {
  return drupal_get_form('mymodule_first_form');
}
 
function mymodule_second_page() {
  return drupal_get_form('mymodule_second_form');
}
 
function mymodule_form_builder($param) {
  $form = array()
  // build the form here
 
  if ($param == 'some parameter') {
    // Add another field, change a default value...
  }
 
  // This is used the way $callback was in 4.7 Forms API: it is used as the prefix for
  // _submit() and _validate() functions to process the form.
  $form['#base'] = 'mymodule_form';
 
  return $form;
}
?>
 
In the above code, the mymodule_first_page() and mymodule_second_page() functions display slight variations on the same form. Because no functions named 'mymodule_first_form' or 'mymodule_second_form' exist, Forms API looks to hook_forms() to find the builder function. Each entry in the array returned by hook_forms() can also define callback arguments that will be passed to the builder function.
 
In the mymodule_form_builder() function, the use of $form['#base'] is also important to note. It's a lot like the little-used but helpful $callback parameter that was used in the 4.7 version of drupal_get_form(). If $form['#base'] is set, its value will be used to look up the proper submit, validate, and theme functions for the form rather than the form's ID.
 
In the future, hook_forms() will also allow us to capture other meta data about forms like access restrictions, workflow information, and so on. For now, though, the callback and optional callback arguments are all you need to worry about if you use it.
 
4) OK, that's great. How do I programmatically submit form data, then?
Aha, the meat of it. Now, it's possible to completely circumvent drupal_get_form() and all of its submission, rendering, and redirection logic. You just pull up a form using its ID, and populate the new $form['#post']['edit'] element with the form values you'd like to submit. Then call drupal_process_form(). For example:
 
<?php
function mymodule_create_user() {
  // register a new user
  $form = drupal_retrieve_form('user_register');
  $form['#post']['edit']['name'] = 'robo-user';
  $form['#post']['edit']['mail'] = 'robouser@example.com';
  $form['#post']['edit']['pass'] = 'password';
  drupal_process_form('user_register', $form);
 
  if (form_get_errors()) {
    // whoops, something went wrong!
  }
}
?>
 
And that, dear friends, is about it. To recap:
 
1) drupal_get_form() now takes a $form_id, NOT a $form_id and a $form.
2) You need a builder function with the name of the $form_id that returns the fully constructed $form array
3) If your page content is JUST a form, you can use drupal_get_form as your menu callback, with $form_id as your menu callback argument.
4) If you need to use the same $form for multiple $form_ids, use hook_forms()
5) If you want the submit, validate, and theme functions to use naming different than the $form_id, set $form['#base']
6) Use drupal_retrieve_form(), $form['#post']['edit'], and drupal_process_form() to programmatically submit forms.
 
Have fun!