Apparently, this addition was not contained in the patch that merged ctools' form builder functions into core.

CTools uses $form_state['wrapper callback'] to invoke a separate form builder function before the actual form builder function is invoked. That is, to allow the multi-step form handling to prepopulate $form with appropriate buttons + stuff.

As of now, this patch is a direct merge of that snippet from CTools. Apparently, a 'wrapper callback' should also be restored when a form is rebuilt from cache, so the proposed $form_state['build_info'] of #367567-86: Use AJAX framework for "Add more" / load include file containing form / introduce $form_state['build_info'] would be good to have for this patch.

The entirely different argument handling needs some thought though.

Comments

merlinofchaos’s picture

CTools will need this to properly update to 7, since the ctools_build_form() it is using relies on the wrapper.

I cannot think of too many use cases for the wrapper, to be honest. However, the one I have is awesome: The CTools multi-step form wizard uses the wrapper to apply standard items to the form (in particular, buttons) prior ot the form builder being run. The reason it works best with the wrapper is that the form is then free to modify these buttons. It turns out I use this extensively in the Page Manager, where certain multi-step forms end up needing different buttons, or sometimes no buttons at all, because of the genericness of the form system I am using.

The actual change to core is minor, and affects no existing code whatsoever. The only thing I see about this patch is that it includes no documentation outside of the comments, and it probably needs to be documented somewhere.

chx’s picture

Status: Needs review » Needs work

one, calling the callback with $foo[bar]() looks a bit awkward. I did not know even know that syntax was legal.

two, i think a test would be useful here -- if for nothing else, it would provide much needed documentation.

sun’s picture

I'm not really happy with this.

If those multi-step forms already use different arguments, and they already presume that there are certain form elements in the passed $form, couldn't we just pass on the 'wrapper_form' generated by the 'wrapper callback' inside of $form_state and let the form builder functions figure out how to use it? i.e. they'd start with

  $form = $form_state['wrapper_form'];

Leveraging the $form_state['build_info'] from #367567: Use AJAX framework for "Add more" / load include file containing form / introduce $form_state['build_info'] would restore that wrapper form information properly from cache when a form is being rebuilt from cache.

dmitrig01’s picture

what about a hook_pre_form_alter something? also, it's impossible to pass args to the form function if it has a wrapper

sun’s picture

Hm.

I wonder why wizard.inc can't route all multi-step form builds through itself, prebuilding any form elements upfront, calling the (special) respective multi-step form builder callback (using those different arguments), and also auto-apply any #validate + #submit handlers (if existent) for that multi-step form builder callback?

At least, the concept sounds similar to node forms (http://api.drupal.org/api/function/node_form/7), where one form builder callback wraps an arbitrary other form. Leveraging hook_forms() (http://api.drupal.org/api/function/node_forms/7) to auto-register/route all multi-step form builder callbacks to a wizard_form().

?

merlinofchaos’s picture

sun:

Either 1) all multi step forms will have to call the wrapper explicitly, which is bad DX, or 2) I'll end up having to re-implement all of drupal_build_form() just to get the wrappers in.

merlinofchaos’s picture

Also, form_alters don't work the way people expect if we fake it by making it actually 1 form and pretending it's another.

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new59.24 KB

Status: Needs review » Needs work

The last submitted patch failed testing.

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new60.42 KB

This one should at least get "somewhere".

Additional benefit: This patch brings consistency into form builder functions and form validate/submit handlers. All of them take ($form, &$form_state) as arguments now. Great for DX.

Status: Needs review » Needs work

The last submitted patch failed testing.

chx’s picture

i missed something. why are you passing in a form if you override it always?

merlinofchaos’s picture

override? No forms are being overridden here.

chx’s picture

function locale_languages_edit_form($form, &$form_state, $langcode) {
   if ($language = db_query("SELECT * FROM {languages} WHERE language = :language", array(':language' => $langcode))->fetchObject()) {
     $form = array();
webchick’s picture

I don't see any .test hunks. Let's add some.

sun’s picture

Issue tags: +API change, +API clean-up

1) Converting all core forms alone to take $form as the first argument is a monster job. I had to manually fix a couple of pass-through functions like http://api.drupal.org/api/function/user_user_form/7 to account for that change, but I obviously didn't catch all of them.

2) As there is no form builder function in core that uses a wrapper callback/form, we are safe to keep those $form re-initializations in the form builder functions. Definitely a novice issue to wander through all of core and change/remove those accordingly.

3) Adding $form as the first argument brings consistency into the function signature of all form build/validate/submit handlers. For me, that is the only reason for accepting the 'wrapper callback' addition in D7. In D8, we should re-consider this addition along with re-considering implementations like aforementioned user_user_form() as well as http://api.drupal.org/api/function/node_form/7, which all kinda tackle a similar issue: Invoking a specific $form_id, but wrapping that form builder into another form builder. Currently, both user forms and node forms use custom API callbacks to pass on a prefilled $form.

4) Tests for this can certainly be added, but before that happens, the testbot should pass on the current code. Fixing that will require quite some work, so it would be good have some early core committer feedback on 1) - 3).

webchick’s picture

Ok, confirmed with Dries that any of these sort of "API clean-up" patches are on-topic for code slush. So I guess go nuts. :P

And since that's the case I'd also like to put in a modest cross-reference to #469698: Establish naming convention for form IDs. ;)

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new60.91 KB

This one should get at least beyond the installer....

Status: Needs review » Needs work

The last submitted patch failed testing.

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new63.46 KB

oh boy, this one should get a bit further.

Status: Needs review » Needs work

The last submitted patch failed testing.

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new64.61 KB

This one should pass.

sun’s picture

StatusFileSize
new69.38 KB

Final patch?

sun’s picture

Latest patch is still green. I can't mark my own patch as RTBC...

Summary:

1) Adds $form as the first argument to all form builder functions in Drupal. This means that the primary form handling functions now share the same arguments:

function mymodule_awesome_form($form, &$form_state) {
  ...
}

function mymodule_awesome_form_validate($form, &$form_state) {
  ...
}

function mymodule_awesome_form_submit($form, &$form_state) {
  ...
}

2) Adds the originally requested facility of a wrapping form builder function defined in $form_state['wrapper callback']. When defined, drupal_retrieve_form() invokes that function first to allow a form to be prepopulated with a form structure, which is then passed on as $form to the actual form builder (usually $form_id). Documentation for this is added to the existing $form_state documentation in the PHPDoc of drupal_build_form().

3) Adds tests to ensure the 'wrapper callback' functionality.

Nick Lewis’s picture

Status: Needs review » Reviewed & tested by the community

Refer to #1 for why we need this patch. Refer to patch itself to see how benign it is. Patch applies to bot. Checks out with my brief tests. It would be a shame to lose ctools wizard.inc.

webchick’s picture

Status: Reviewed & tested by the community » Needs review

I'd like for chx to give this a final look-through, since he had some concerns earlier, but otherwise this looks like a nice clean-up.

I also wish the tests were a bit more "real world." The tests basically amount to "yep, a function got called" but give no insight into why it's important that it did.

sun’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new69.88 KB

I think chx confirmed in IRC that he's ok with this patch.

Attached patch clarifies the API docs in drupal_retrieve_form(), where the 'wrapper callback' is actually invoked. The added test is just ensuring that functionality. In case anyone ever wants to refactor that functionality, then he/she really should be able to learn about what exactly 'wrapper callback' is for right within drupal_retrieve_form(), and not within an arbitrary test that just disapproves the killing concept he/she tried to apply - i.e. when it's too late, after people thought that they could revamp that API.

chx’s picture

well, having a wizard is nice i am fine with the patch provided we eventually go over core and remove the form array resets. Definitely (a bunch of) follow up issues.

sun’s picture

StatusFileSize
new88.17 KB

Replacing all $form = array(); initializations (where possible).

Let's see whether this comes back green. ;)

Status: Reviewed & tested by the community » Needs work

The last submitted patch failed testing.

sun’s picture

Status: Needs work » Reviewed & tested by the community
StatusFileSize
new88.62 KB

Fixing that failing Color module code. Should come back green now.

webchick’s picture

Status: Reviewed & tested by the community » Needs work

Awesome! I believe this addresses everyone's concerns.

Committed to HEAD.

Let's document this in the upgrade guide, at least. I'd love to have it in some sort of form API overview doc as well.

webchick’s picture

Issue tags: +Needs documentation

Tagging.

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new2.55 KB

chx mentioned a few more instances of $form = array();, which could be converted. Attached patch converts those.

Note that there are still some more left, but those cannot be converted, since they belong to hook implementations that apparently do not get a $form passed.

The altered color_scheme_form() in this patch is basically also one of those implementations, but since $form_state was already passed to that "implementation", my earlier patch added $form there as well. That, however, broke tests, because the passed in $form was duplicated into the original $form, so I simply reset $form previously. This patch changes the passed in $form to $complete_form to clarify that.

Overall, however, this leads to an interesting point: We also have implementations like user_user_form(), which also do not get the original, complete form passed. Like color_scheme_form(), hook_user_form() implementations are only supposed to add to the user edit form, without having a clue about the rest of the form. They neither get $form nor $form_state. So to actually do something funky, modules have to implement hook_form_alter() to alter that form. The same applies to other instances, where this pattern is applied, such as node forms. This really sounds like something worth to explore, but I already know that webchick will kill me if I apply the "API clean-up" tag to that... 8)

yched’s picture

re #34: Note that fixing user forms to receive $form and $form state are probably a prerequisite for 'fieldable users' to support the 'add more' button...
#394720-91: Migrate profile module data to field API
#501408-4: Display user fields on registration form
#367006: [meta] Field attach API integration for entity forms is ungrokable

sun’s picture

I'm killing hook_user_form() + hook_user_register() in #118345: Revamp hook_user_form/_register/_validate/_submit/_insert/_update currently. Almost done.

mattyoung’s picture

,

sun’s picture

Status: Needs review » Reviewed & tested by the community

sorry for rtbc'ing my own patch, but this is a no-brainer follow-up patch.

catch’s picture

Category: feature » task
Priority: Critical » Normal

Looks fine here too. Recategorising as general clean-up.

Status: Reviewed & tested by the community » Needs work

The last submitted patch failed testing.

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new4.7 KB

ok, re-rolled.

But, this time, also adding a pretty interesting little change, that may have been the missing puzzle piece before:

Let's allow hook_forms() to also assign this new $form_state['wrapper_callback'] in case it is not defined already. This allows the "regular Drupal developer" to use this concept by just using the tools he knows already: hook_forms() + drupal_get_form(). Quite potentially, this really was the missing piece.

If today was not today and API freeze not "tomorrow", then I could even see Drupal core using this thingy. Specifically, I'm working on #118345: Revamp hook_user_form/_register/_validate/_submit/_insert/_update and I now realized that user_register() and user_profile_form() are exactly such wrappers around user_edit_form().

In addition to that, we also want to pass form builder arguments to the 'wrapper callback' ;)

chx’s picture

Status: Needs review » Reviewed & tested by the community

oooooh i see interesting lil tricks coming with this yea -- just create a new form based on an existing form building function and apply a wrapper.

sun’s picture

StatusFileSize
new7.44 KB

Just updated the relevant documentation of hook_forms() accordingly.

Also created spin-off: #597108: Consistent array key syntax in $form_state (underscores vs. spaces)

dries’s picture

In the API documentation, I don't feel like the wrapper callback is properly documented. It is not clear, from the example, what you can do with the new callback.

It is also not clear from the documentation, why'd want the define the wrapper in hook_forms().

Could use a bit more documentation that makes this more practical or real-world.

webchick’s picture

Status: Reviewed & tested by the community » Needs work
sun’s picture

Issue tags: +D7 API clean-up

Tagging absolutely critical clean-ups for D7. Do not touch this tag. Please either help with this or one of the other patches having this tag.

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new9.12 KB

Revamped the docs of hook_forms().

But, to be honest, we don't know the actual real-world possibilities and/or limitations of this yet. :) Using the wrapper callback to setup common form elements for wizard-alike things is the use-case of CTools, but given this tiny enhancement, I doubt that it will be the most common/simple thing you can do with this.

Experiments in contrib will tell :)

sun’s picture

+++ modules/simpletest/tests/form.test	9 Oct 2009 17:51:10 -0000
@@ -305,11 +305,9 @@ class FormsElementsTableSelectFunctional
   private function formSubmitHelper($form_element, $edit) {
     $form_id = $this->randomName();
-
     $form_state = form_state_defaults();
-    $form = array();
+    $form = $form_element;
 
-    $form = array_merge($form, $form_element);

So why do we copy $form_element into $form, you ask?

Well, I asked me that, too.

Nonsense.

+++ modules/system/system.api.php	9 Oct 2009 18:36:37 -0000
@@ -573,9 +573,11 @@ function hook_form_FORM_ID_alter(&$form,
+ * in 'callback'. In case the code that calls drupal_get_form() passes

s/passes/also passes/

+++ modules/system/system.api.php	9 Oct 2009 18:36:37 -0000
@@ -583,17 +585,45 @@ function hook_form_FORM_ID_alter(&$form,
+ *   An array containing the original arguments provided to drupal_get_form()
+ *   for reference. These are always passed to the form builder and do not have

"for reference" sounds like "by reference", so let's drop that.

This review is powered by Dreditor.

sun’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new9.13 KB

ah, yeah, sun is right. Fixed those issues. :)

dries’s picture

This is still really cryptic and hard to grok, IMO. If we don't really know what this can be used for, how come it is considered a critical clean-up for Drupal 7 (see #46)? *confused* I can't help but feel like this is extra complexity that can be debated at length during the Drupal 8 development cycle..

merlinofchaos’s picture

It is critical for the CTools multi-step wizard tool, which took advantage of the fact that CTools had its own version of drupal_get_form(). Since most of that is in core, but this piece is not, putting this off until D8 means that CTools will have to retain its own version of drupal_build_form().

This was stated in the initial post and comment #1. Nothing about that has changed in 48 other comments, so I have no idea why you're even asking why this is critical, since all I can do is repeat the answer that was given in the original post here. Since generally when it gets to the point where answers get repeated, I will simply resign CTools to having to re-implement core functionality to allow these useful tools.

webchick’s picture

Status: Reviewed & tested by the community » Needs work

Cryptic and hard to grok usually means "docs could use some work." Try cornering someone relatively new in #drupal and showing them the API and talk through it with them. Have them update the documentation until it reads like something they can understand.

dries’s picture

Status: Needs work » Reviewed & tested by the community

@merlinofchaos, I think it is fair to ask what use cases we have in mind for this. If CTools's wizard is the only concrete use case that we can come up with for now, that is OK. I doesn't mean we can't discuss or brainstorm about additional use cases to help us understand the importance of this patch, to evaluate the complexity trade-off, and hopefully, to help improve the documentation. None of the above should make you grumpy, to be frank.

merlinofchaos’s picture

Oh, nevermind. The part I was concerned about already went in. Sorry.

sun’s picture

Status: Reviewed & tested by the community » Needs work

Yes. The follow-up patch is mainly about making the functionality we already added available to more developers who are just used to define 'drupal_get_form' in their menu callbacks and implement hook_forms(), because both are API constructs they know and used already.

So the idea of making 'wrapper_callback' available to hook_forms() is that many more use-cases could be possible. We don't know what's possible yet, because CTools was the only module thus far that implemented this concept, and because it used the more advanced implementation of calling drupal_build_form() with a customized $form_state - which is something that most developers will most likely not grok.

As mentioned before, we even have use-cases in core for this. user_register_form() + user_profile_form() are exactly such wrappers around user_account_form(). I doubt that I'll have the time to convert those, but it clearly shows that there are plenty of use-cases for this facility.

In short: The follow-up patch just exposes an internal Form API feature to the masses. If the docs are really insufficient, I'll try to find someone who's able to improve them.

sun’s picture

Status: Needs work » Reviewed & tested by the community
StatusFileSize
new9.09 KB

I really asked around, and several people said they would help on the docs, but didn't show up here.

So I revamped the docs for hook_forms() once more and I now have little idea on how they could be improved further.

sun’s picture

Next to the slight hook_forms() feature addition, this patch contains a fix for passing form builder arguments also to the wrapper callback, which is required for #367006-18: [meta] Field attach API integration for entity forms is ungrokable. Yes, I know that sounds strange, but you can read the comment I linked to grok why.

#367567: Use AJAX framework for "Add more" / load include file containing form / introduce $form_state['build_info'] should be committed first - afterwards this one needs a quick re-roll to apply again.

rfay’s picture

The signature change on the forms callback didn't make it into the 6/7 update docs, as far as I can tell.

For contrib module updaters, this is huge.

Is the upgrade impact of this patch normally just the change in the signature to

function my_form($form, &$form_state)?

This is critcal to get into coder module too.

rfay’s picture

I just added what I think is the update to http://drupal.org/update/modules/6/7#hook_forms_signature

Please review.

sun’s picture

Priority: Normal » Critical

Guys, we need this patch to move forward in #367006: [meta] Field attach API integration for entity forms is ungrokable. If absolutely required, then I can remove the change to hook_forms() allowing to specify a wrapper callback for unknown form ids. However, I really think that this nifty piece will allow for some interesting code in contrib during D7.

We need at least the other parts of this patch, because the current flow of Form API + Field Attach API integration is totally ungrokable for everyone. The important piece of this patch is the passing of form builder arguments to the wrapper callback.

Please note that this patch contains conflicting changes with #367567: Use AJAX framework for "Add more" / load include file containing form / introduce $form_state['build_info']. Both are highly critical at the moment, but either one can be re-rolled easily after the other one went in.

webchick’s picture

Status: Reviewed & tested by the community » Needs work

Although the patch applies fine, sun says that they touch the same code so I'm marking this CNW for a re-roll.

Upon cursory inspection, the only thing here that looked a bit red-flaggy was the c_u_f_a call in form.inc (since it's been drilled into my skull to avoid that wherever possible due to performance issues...), but since chx signed off on this, I guess it's not a huge deal.

webchick’s picture

Issue tags: -D7 API clean-up

Oops. Actually, sun was mistaken. The patch is good.

I read through the docs one more time and I think they are ok.

Committed to HEAD.

I think this still needed docs?

catch’s picture

Priority: Critical » Normal
sun’s picture

Issue tags: -API change, -API clean-up

.

rfay’s picture

OK, in #59 I updated the update docs. What other docs should be updated for this one? Otherwise, let's mark fixed.

rfay’s picture

Assigned: sun » rfay

$form_state['wrapper_callback'] needs to be documented in the Form API Reference. I'll visit that with a bunch of other things that need to be put in there.

rfay’s picture

Status: Needs work » Fixed
Issue tags: -Needs documentation

Picked this up in #859970: Cleanup form_api $form_state docs - keys in 2 places, missing some. We don't put $form_state stuff in the FAPI reference, but instead in the form_api header section in form.inc.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.