The attached patch is *just* the drupal_execute() programmatic form handling code from http://drupal.org/node/79937. That issue was RTBC, drumm wanted it split.

Comments

drumm’s picture

Status: Reviewed & tested by the community » Needs review

Thanks.

in drupal_retrieve_form(), $args already is func_get_args() shifted once. Wouldn't that be better than all the arguments including form id for something like #parameters? I think I can see the use of #parameters, but I'd like to see some documentation other than "This is very useful for http://drupal.org/node/79900, Adrian's patch to allow recording of form input. It can store build params rather than the entire form."

The rest looks like a good cleanup.

dries’s picture

I agree with Neil that the argument handling is a bit weird.

drumm’s picture

Status: Needs review » Needs work
eaton’s picture

#parameters' purpose is to store *what is necessary to reconstitute the form* at another point.

$args isn't necessarily just the array-shifted version of the initial arguments. By the time #parameters is populated, it's also been merged with any hard-coded parameters specified in the hook_forms() function.

Why is that important? If we store a copy of *the original args passed into the function* we can feed #parameters straight back into drupal_retrieve_form to reconstitute a copy of the form. If we use the 'final' copy of $args, #parameters would be almost useless -- we couldn't use it with any of the core form functions, and we'd have to manually call the builder function.

I'll be adding a few lines of documentation to the code later this morning, but the code itself shouldn't change, IMO.

eaton’s picture

Status: Needs work » Reviewed & tested by the community
StatusFileSize
new3.57 KB

Attached is a re-rolled patch with a comment explaining the purpose of #parameters, and why func_get_args() is used rather than $args.

eaton’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new3.5 KB

chx noted that drupal_execute() was irretrievably clobbering the return value of drupal_process_form(). We usually won't need that when programmatically processing a form, but it's impossible to recover that value after it's lost. It IS possible to call form_set_errors() after processing, though, so the redirect return value should win. Patch rerolled.

nickl’s picture

Subscribe for http://drupal.org/node/79900 reroll... lets move this forward.

Other good reasons for this patch:
Test cases, submitting forms pragmatically.
Workflows, by executing other forms based on minimum user input or other events.
Using already implemented form functionality to execute streamlined processes or batch updates.

With all of these use cases only the field values to be submitted will be available and no reference to the $form array thus we need to be able to execute a form only with its values.

nickl’s picture

Stupid spell checkers: pragmatically could also be programatically...

dries’s picture

  1. Why are the final arg values different from the original ones?
  2. Why do we want to reconstitute a copy of the form?

Even with the extra documentation I don't get it, and it is not properly documented in the code. (At least, not without spending 30 minutes looking that form API's control flow.) Also,

  1. Could you upate the documentation (at the top of the file) with a valid example that uses the extra paramater passing options that drupal_execute() has?
  2. drupal_execute() sounds weird. Maybe drupal_post() or drupal_submit() is better? (Not sure, not important at this point.)

Maybe I'm the only one here, but I find the form handling pretty darn hard to grok. Imagine the day, we loose our form API experts ... and we're left with something like the menu system ...

eaton’s picture

1. Why are the final arg values different from the original ones?

#parameters is, essentially, "what would have to be passed into drupal_get_form() to pull this form up again."

In cases where a given form id is mapped to a separate builder function using hook_forms(), drupal_retrieve_form() adds the callback arguments from the hook_forms() entry into the $args list. The full combined args list is passed to the form builder function. For example:

function mymodule_forms() {
  $forms['myform_id'] = array(
    'callback' => 'my_form_builder',
    'callback arguments' => node_load(arg(1))
  );
}

function mymodule_page() {
  print drupal_get_form('myform_id', 'bob');
}

function my_form_builder($node, $name) {
  // in this example, $node will contain the argument from hook_forms() 'callback argument'.
  // $name will contain the parameter passed in by the drupal_get_form() function.
}

This is rather complex. But it's what allows us to alias multiple form IDs (some with slightly different callback arguments) to a central builder function. Like some of the other more esoteric properties, the only real solution is to either stop doing complicated things with forms, or duplicate a LOT of code between very similar forms, introducing many other errors along the way.

2. Why do we want to reconstitute a copy of the form?

We do it in multiform scenerios by packing logic inside of drupal_get_form(). This parameter just allows other module code the opportunity to get at the same 'original parameters list' drupal_get_form() sees. It's a key requirement for Adrian's form-recording and playback workflow system, and it opens up opportunities for other modules to access more core data baout the formAPI system. It also doesn't break anything. ;)

If the #parameters thing is really a sticking point, I'm more than willing to pull it out. I suppose that it could, if really necessary, be turned into its own one-liner patch in a separate issue.

Could you upate the documentation (at the top of the file) with a valid example that uses the extra paramater passing options that drupal_execute() has?

creating a new node is one example, though it's a little ugly because the node system itself requires some funky parameters. I'll look around for a good example that doesn't get too complicated. (Creating a new user is very clean, thus very convenient for demo use).

drupal_execute() sounds weird. Maybe drupal_post() or drupal_submit() is better? (Not sure, not important at this point.)

I went round and round on this; it's hard to find a descriptive function without trampling on or overloading functions we already use in the form workflow. The reason I settled on execute(), after a lof of discussion in #drupal, was because it uses the formAPI system but opens the doors for using in ways that aren't 'forms' at all, like Jaza's import-export system.

Maybe I'm the only one here, but I find the form handling pretty darn hard to grok. Imagine the day, we loose our form API experts ... and we're left with something like the menu system ...

This is indeed a problem. I think the biggest areas of 'voodoo' right now are form_builder (because of the tremendous amount of work it does), the multistep form feature (because only a handful of peopel have used it), and hook_forms(). They're all necessary, but they are either super-complicated or very new.

Are there any 'hot spots' of mysteriousness that you can see? I've been neck-deep in forms for a month or two now, and while the workflow is something I feel I can confidently understand and explain now, I'm sure that I'm missing bits that are confusing to a casual peruser.

eaton’s picture

StatusFileSize
new3.47 KB

rerolled against HEAD.

adrian’s picture

We need to know the parameters used when creating the form, because in the case of the node form .. you pass a node type (to add a node) or a node object to edit (which is kind of funky , since we need to build a node object in _menu, imo).

If you don't have the original parameters that were given to the form, what you do when you replay the functionality can be wildly different. For example recording a node/add , if you didn't have the type you would create dud nodes, if it even worked at all.

A type of form is identified by it's form id, but that SPECIFIC form is the form_id + it's parameters.

eaton’s picture

StatusFileSize
new3.87 KB

Code is expensive, comments are cheap. There are now two simple examples of programmatic submission at the top of form.inc: adding a new user and creating a new node. One uses no parameters, the other (the node form) requires one. The chunk is as follows:

 * Forms can also be built and submitted programmatically without any user input
 * using the drupal_execute() function. Pass in the id of the form, the values to
 * submit to the form, and any parameters needed by the form's builder function.
 * For example:
 *
 * // register a new user
 * $values['name'] = 'robo-user';
 * $values['mail'] = 'robouser@example.com';
 * $values['pass'] = 'password';
 * drupal_execute('user_register', $values);
 *
 * // Create a new node
 * $node = array('type' => 'story');
 * $values['title'] = 'My node';
 * $values['body'] = 'This is the body text!';
 * $values['author'] = 'robo-user';
 * drupal_execute('story_node_form', $values, $node);
 *
 * Calling form_get_errors() after execution will return an array of any
 * validation errors encountered.

Hopefully, with Adrian's explanation re: the #parameters array, and the additional comments, this is RTBC. :)

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new4.81 KB

Code looks good. I tested the both example submissions and they worked. I had to change $value['author'] to $value['name'] since thats what node form expects now. I removed the programmatic submit docs from top of node.module since this patch changes how things work and gives an up to date example at top of form.inc.

RTBC.

drumm’s picture

I'm okay with this.

I'll leave it to Dries to decide if his questions got answered.

nickl’s picture

Patched to fresh head. +1
Lets move this forward...

dries’s picture

Status: Reviewed & tested by the community » Fixed

Committed to CVS HEAD. Thanks.

Anonymous’s picture

Status: Fixed » Closed (fixed)