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.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

sun’s picture

I'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.

effulgentsia’s picture

Yeah, 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.

effulgentsia’s picture

Version: 7.x-dev » 8.x-dev

Discussed 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.

fago’s picture

I 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.

kscheirer’s picture

fapi_tree_null.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, fapi_tree_null.patch, failed testing.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

donquixote’s picture

This 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.

        $element[$key]['#parents'] = $element[$key]['#tree'] && $element['#tree'] ? array_merge($element['#parents'], array($key)) : array($key);

This code can be found both in D7 form_builder(), and in D8 FormBuilder::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'.

donquixote’s picture

Status: Needs work » Needs review

The last submitted patch, 8: D8-784874-8-FormBuilder-performance.patch, failed testing.

The last submitted patch, 8: D8-784874-8-FormBuilder-reparent.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 8: D8-784874-8-FormBuilder-tree-skip.patch, failed testing.

donquixote’s picture

The 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:

    $form['wine'] = [
      '#type' => 'value',
      '#value' => 3000,
      '#process' => [[$class, 'cleanValue']],
    ];
[..]
  public static function cleanValue(&$element, FormStateInterface $form_state, &$complete_form) {
    $form_state->addCleanValueKey('wine');
  }

This "forgets" to return $element;. Instead, $element is treated as by-reference.
This causes the fails in StateValuesCleanTest and ElementsVerticalTabsTest.

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?

donquixote’s picture

See also #1499596-145: Introduce a basic entity form controller for call_user_func() vs call_user_func_array() to call #process callbacks.

EDIT: Forget it. Dead end.

donquixote’s picture

donquixote’s picture

New 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.

donquixote’s picture

Status: Needs work » Needs review
donquixote’s picture

One solution that already works for D7 (and D8 I suppose):

function element_pop_parent(array $element) {
  array_pop($element['#parents']);
  return $element;
}
$form['container'] = [
  '#type' => 'container',
  '#process' => ['element_pop_parent'],
  'value' => [
    '#type' => 'textfield',
    [..]
  ],
];

The advanced solution with dynamic reparenting, similar to the '#reparent' in the patch, can look like this:

// Call ReparentProcessor::__invoke()
$element['#process'][] = new ReparentProcessor([FALSE, 'replacement_key_0', 'replacement_key_1']);

This already works in D7.

donquixote’s picture

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

rgpublic’s picture

Building on #18 you can even write everything with a local closure. This worked for me:

"#process"=>[function(array $element) {array_pop($element['#parents']);return $element;}]

A very convenient drop-in replacement as long as there is not #tree=>"skip"

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
FileSize
168 bytes

The 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.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.