#tree and #parents

Last updated on
28 January 2017

Normally when working with submitted data in $form_state['values'] the data is flattened and does not maintain the structure of the $form array used to generate the form. This behavior can be changed using the #tree property.

The #tree property is a boolean (default FALSE) used for collections of form elements. It is used in part along with #parents to determine the name that the <input> element in the HTML form will receive, and therefore also influences how data can be accessed in the $form_state array when a form is submitted.

Consider the following form array:

$form['name'][1]['first'] = array(
// text field ...
);

$form['name'][1]['last'] = array(
// text field ...
);

$form['name'][2]['first'] = array(
// text field ...
);

$form['name'][2]['last'] = array(
// text field ...
);

When rendered to HTML this would result in a form with 4 textfields. The names of those elements will vary depending on the use of #tree. By default, the name attribute is taken from the value of the last key, so in effect you'll end up with two input fields with a name attribute of "first" and 2 with a name attribute of last. Furthermore, by default the form API will flatten the form structure when processing submitted values. The above code would result in:

$form_state['values'] = array(
  'first' => '', // Value of name field
  'last' => '', // Value of last field
);

Notice there are only 2, and not 4 values in the array? In this case you'll only have access to the submitted values of the last element defined in the $form array with that key.

If however, you set $form['name']['#tree'] = TRUE;, or as long as the #tree attribute is TRUE at any point in the tree, the form element is aware that it is in a tree, and traverses the tree towards the root (from first, to 1, to name). Along the way, the names of the elements passed are stored in#parents#parentsis used to create the name/ID of the HTML form element itself.

Example:

<input name="first"> vs. <input name="name[1][first]">

With #tree set to TRUE the structure of the $form array is maintained and $form_state['values'] would be as follows:

$form_state['values'] = array(
  'name' => array(
    1 => array('first' => '', 'last' => ''),
    2 => array('first' => '', 'last' => ''),
  ),
);

Cascading of #tree

There are shortcuts to traversing the full tree each time. If you set #tree = TRUE at a closer point to the root of the tree, as in:

<?php
 $form['foo']['#tree'] = TRUE 
?>

and you have not specifically set #tree anywhere else, then it will cascade and make all of the sub-elements' #tree = TRUE. This is very useful because otherwise you would need to write #tree = TRUE for each element in the tree.

Common use of #tree

A common use of #tree is fieldsets. Another example is the checkbox element type where #treeis set to TRUE internally before expanding to multiple checkbox elements.

Common use of #parents

You can set #parents manually, but the need for this is rare. More common is to read #parentsto determine where in the form tree the current element is. Setting #parents does not affect the rendering of the form; that's decided by the indexes. However, setting #parents does affect placement of $form_values, as can be seen from filter_form().

#parents vs. #array_parents

#array_parents contains the actual parent array keys of an element in a form structure and is useful if you need to locate an item in the $form array.

#parents always contains the parent array keys of an element in the submitted form values and is heavily influenced as we saw above by the use of #tree.

For example:

$form['foo']['bar']['beer'] = array(...);

The element 'beer' will always have:

'#array_parents' => array('foo', 'bar', 'beer')

This is heavily influenced by previously defined #parents further up the tree, or, when speaking of tree, it's also affected by whether some element above the element defined #tree, or, whether the element itself defines #tree.

For example:

$form['foo']['bar'] = array(...);

will end up in

$form_state['values']['bar'] = '...';

and we'll see

'#parents' => array('bar')

For example:

$form['foo']['bar'] = array(...);
$form['foo']['#tree'] = TRUE;

will end up in

$form_state['values']['foo']['bar'] = '...';

and we'll see

'#parents' => array('foo', 'bar')

For example:

$form['foo']['bar'] = array(...);
$form['foo']['#parents'] = array('beer');

will end up in

$form_state['values']['beer']['bar'] = '...';

and we'll see

'#parents' => array('beer', 'bar')

For example:

$form['foo']['bar'] = array('#default_value' => 'bar');
$form['foo']['#tree'] = TRUE;
$form['foo']['bar']['beer'] = array('#default_value' => 'beer');
$form['foo']['bar']['beer']['#tree'] = FALSE;

will end up in

$form_state['values']['foo']['bar'] = 'bar';
$form_state['values']['beer'] = 'beer';

and we'll see

'#parents' => array('foo', 'bar')
'#parents' => array('beer')