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.
| Comment | File | Size | Author |
|---|---|---|---|
| #57 | drupal.form-wrapper.57.patch | 9.09 KB | sun |
| #49 | drupal.form-wrapper.49.patch | 9.13 KB | sun |
| #47 | drupal.form-wrapper.47.patch | 9.12 KB | sun |
| #43 | drupal.form-wrapper.43.patch | 7.44 KB | sun |
| #41 | drupal.form-wrapper-hook-forms.patch | 4.7 KB | sun |
Comments
Comment #1
merlinofchaos commentedCTools 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.
Comment #2
chx commentedone, 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.
Comment #3
sunI'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
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.
Comment #4
dmitrig01 commentedwhat about a hook_pre_form_alter something? also, it's impossible to pass args to the form function if it has a wrapper
Comment #5
sunHm.
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().
?
Comment #6
merlinofchaos commentedsun:
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.
Comment #7
merlinofchaos commentedAlso, form_alters don't work the way people expect if we fake it by making it actually 1 form and pretending it's another.
Comment #8
sunComment #10
sunThis 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.
Comment #12
chx commentedi missed something. why are you passing in a form if you override it always?
Comment #13
merlinofchaos commentedoverride? No forms are being overridden here.
Comment #14
chx commentedComment #15
webchickI don't see any .test hunks. Let's add some.
Comment #16
sun1) 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).
Comment #17
webchickOk, 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. ;)
Comment #18
sunThis one should get at least beyond the installer....
Comment #20
sunoh boy, this one should get a bit further.
Comment #22
sunThis one should pass.
Comment #23
sunFinal patch?
Comment #24
sunLatest 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:
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.
Comment #25
Nick Lewis commentedRefer 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.
Comment #26
webchickI'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.
Comment #27
sunI 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.
Comment #28
chx commentedwell, 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.
Comment #29
sunReplacing all $form = array(); initializations (where possible).
Let's see whether this comes back green. ;)
Comment #31
sunFixing that failing Color module code. Should come back green now.
Comment #32
webchickAwesome! 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.
Comment #33
webchickTagging.
Comment #34
sunchx 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)
Comment #35
yched commentedre #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
Comment #36
sunI'm killing hook_user_form() + hook_user_register() in #118345: Revamp hook_user_form/_register/_validate/_submit/_insert/_update currently. Almost done.
Comment #37
mattyoung commented,
Comment #38
sunsorry for rtbc'ing my own patch, but this is a no-brainer follow-up patch.
Comment #39
catchLooks fine here too. Recategorising as general clean-up.
Comment #41
sunok, 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' ;)
Comment #42
chx commentedoooooh 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.
Comment #43
sunJust updated the relevant documentation of hook_forms() accordingly.
Also created spin-off: #597108: Consistent array key syntax in $form_state (underscores vs. spaces)
Comment #44
dries commentedIn 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.
Comment #45
webchickComment #46
sunTagging 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.
Comment #47
sunRevamped 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 :)
Comment #48
sunSo why do we copy $form_element into $form, you ask?
Well, I asked me that, too.
Nonsense.
s/passes/also passes/
"for reference" sounds like "by reference", so let's drop that.
This review is powered by Dreditor.
Comment #49
sunah, yeah, sun is right. Fixed those issues. :)
Comment #50
dries commentedThis 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..
Comment #51
merlinofchaos commentedIt 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.
Comment #52
webchickCryptic 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.
Comment #53
dries commented@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.
Comment #55
merlinofchaos commentedOh, nevermind. The part I was concerned about already went in. Sorry.
Comment #56
sunYes. 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.
Comment #57
sunI 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.
Comment #58
sunNext 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.
Comment #59
rfayThe 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.
Comment #60
rfayI just added what I think is the update to http://drupal.org/update/modules/6/7#hook_forms_signature
Please review.
Comment #61
sunGuys, 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.
Comment #62
webchickAlthough 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.
Comment #63
webchickOops. 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?
Comment #64
catch#653068: Update documentation is incomplete
Comment #65
sun.
Comment #66
rfayOK, in #59 I updated the update docs. What other docs should be updated for this one? Otherwise, let's mark fixed.
Comment #67
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.
Comment #68
rfayPicked 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.