Follow-up from #759222: #tree does not default to TRUE, fails to meet developer expectations. It was decided in that issue that changing the meaning of #tree => FALSE is out of scope for D7. However, I think the use-case in #759222-10: #tree does not default to TRUE, fails to meet developer expectations remains compelling enough to warrant a new option for #tree. I propose #tree => NULL. I'm generally not too keen on having unspecified, NULL, and FALSE meaning different things, but that's actually already quite common within FAPI. An example where this will come into play is for fieldable fields, and wanting to use the fieldgroup module for them.
Comments
Comment #1
sunI'm really not sure whether I like this change for D7. Sure, I've also thought of it in the past - but in practice, this can easily get totally confusing, as you'd have to scan for #tree TRUE, perhaps FALSE, perhaps some NULL in the middle, and any other combination that is possible.
The current behavior of just TRUE and FALSE is at least followable, even in complex form structures, as it's "absolute". #tree NULL introduces relativity.
Comment #2
effulgentsia CreditAttribution: effulgentsia commentedYeah, I agree about the complexity being unfortunate. But the alternative is for any contrib code that wants to do the equivalent of what's being proposed to instead have to mess with #parents of either itself or its children, potentially within a #process function, as #parents of the parent isn't available until the form_builder() stage, which seems more complex and hard to follow than simply setting #tree => NULL on the wrapper element at the point that it's added to $form.
Comment #3
effulgentsia CreditAttribution: effulgentsia commentedDiscussed with sun. I'm going to open a separate issue for the fieldgroup.module problem. Without that use-case, I'm at a loss to argue for this issue's inclusion in D7. If someone else (fago?) would like to argue for either this or some other change to #tree for D7, please do.
Comment #4
fagoI do think ideally fieldgroup.module should use #pre_render to do it's magic - that way the $form structure stays consistent for modules. -> That way fieldgroup is no use-case any more.
Ok. So let's think of use-cases:
1) Embed multiple forms of someone else using array parents with #tree == TRUE. With proper #tree behaviour this would be rather easy (subform element bye bye) as long as the forms contain no #ajax stuff. Yes you would have to care about the #tree defaults of the embedded form as of now, but at least it would work if #tree == FALSE would be fixed. With only having NULL you'd also have to make sure any embedded form element uses NULL instead of FALSE.. ;)
2) The next use case is using $form arrays for grouping purposes only. I'm not talking about fieldsets, but just about empty "parent" arrays with #tree == NULL. They might be useful to group several form elements together, or just to avoid possible array key conflicts when building $form in a modular way. I could have needed it for the rules UI - that's why I opened the original issue. As of now I've to use #tree == TRUE so the array level used for grouping purposes also appears in the form values and the submit handler has to work around it - yuck!
Form API currently has a bad distinction between the representation of the form and the data structure of the form values as both is built up with $form - but #pre_render/#theme could help us here. So I don't think using #tree == NULL groups for adding render elements like containers is something good to do nor do I think it's good to use it to inject array parents in hook_form_alter(), but still there are use-cases 1) and 2).
Next I don't see any use case for the current behaviour of #tree == FALSE - that just makes no sense to me.
Comment #5
kscheirerfapi_tree_null.patch queued for re-testing.
Comment #8
donquixote CreditAttribution: donquixote commentedThis issue is quite old and inactive, but I still run into this problem every once in a while.
The existing proposed patch is nice. But possibly BC-breaking?
'#tree' => NULL already has a meaning and a behavior before this patch. Especially for code that uses isset() and thus cannot distinguish NULL from a missing value. So the patch would change expected behavior.
To recap, here is the code that determines the #parents of child elements.
This code can be found both in D7
form_builder()
, and in D8FormBuilder::doBuildForm()
.Side note: Performance
One quick observation:
I suspect that this implementation using
array_merge()
is not the fastest option available.Faster would likely be something like in the *-performance.patch attached.
Idea: $element['#reparent'] = [..]
This idea gives more control over the #parents element.
Patch is made with git format-patch to distinguish the different steps.
Since '#reparent' => [..] had no meaning before and was likely not used, this is less likely to cause BC issues.
Other idea: $element['#tree'] = 'skip'
See the patch.
Since '#tree' => 'skip' had no meaning before and was likely not used, this is less likely to cause BC issues.
However, it could be that some code relies on #tree being either TRUE or FALSE.
So maybe it is better to introduce a new key like '#reparent'.
Comment #9
donquixote CreditAttribution: donquixote commentedComment #13
donquixote CreditAttribution: donquixote commentedThe problem was that sometimes
$element['#parents']
was not set.In existing code we do not have any bulletproof safeguard for these situations.
Instead, we just assume that if
$element[$key]['#tree'] && $element['#tree']
, then$element['#parents']
is set. Otherwise,$element['#parents']
is allowed to be not set.Oh wait, we also assume further above that if
!isset($element['#id'])
, then$element['#parents']
is set and an array.-----
Further research shows that
$element['#parents']
can be removed by a#process
callback.In
Drupal\form_test\Form\FormTestFormStateValuesCleanForm
:This "forgets" to
return $element;
. Instead,$element
is treated as by-reference.This causes the fails in
StateValuesCleanTest
andElementsVerticalTabsTest
.This points to a larger problem: Do we universally trust #process callbacks, or any callbacks? Or do we need to add checks for e.g. the case where a #process callback returns NULL, or an incomplete array?
Comment #14
donquixote CreditAttribution: donquixote commentedSee also #1499596-145: Introduce a basic entity form controller for
call_user_func()
vscall_user_func_array()
to call#process
callbacks.EDIT: Forget it. Dead end.
Comment #15
donquixote CreditAttribution: donquixote commentedSee #2769549: Log misbehaving callbacks (e.g. #process)
Comment #16
donquixote CreditAttribution: donquixote commentedNew patches with the fix in
FormTestFormStateValuesCleanForm::cleanValue()
.I also added "-CLEAN.patch" versions which do not contain the not really related "performance" code change, and also not the fix I just mentioned.
I used
git format-patch
all the time so different changes can be distinguished.Comment #17
donquixote CreditAttribution: donquixote commentedComment #18
donquixote CreditAttribution: donquixote commentedOne solution that already works for D7 (and D8 I suppose):
The advanced solution with dynamic reparenting, similar to the '#reparent' in the patch, can look like this:
This already works in D7.
Comment #19
donquixote CreditAttribution: donquixote commentedFor anyone interested, I added this stuff to themekit:
http://cgit.drupalcode.org/themekit/commit/?h=7.x-1.x&id=a7ac625f8c03042...
http://cgit.drupalcode.org/themekit/commit/?h=7.x-1.x&id=d293663b856772f...
Comment #25
rgpublicBuilding on #18 you can even write everything with a local closure. This worked for me:
A very convenient drop-in replacement as long as there is not #tree=>"skip"
Comment #32
needs-review-queue-bot CreditAttribution: needs-review-queue-bot as a volunteer commentedThe Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.