Fix the documentation for #empty_value

Created on 7 March 2013, over 11 years ago
Updated 21 May 2023, over 1 year ago

Documentation location/URL

https://api.drupal.org/api/drupal/developer!topics!forms_api_reference.h...

Problem/Motivation

The 'values' section for #empty_value contains the following text.

Values: Defaults to empty string. Can be anything except NULL.

anything except NULL would mean that #empty_value could also be set to TRUE, an object, or an array.
Setting #empty_value to TRUE when the form element is defined as in the following code would also have a side effect.

$form['selected'] = array(
  '#type' => 'select',
  '#title' => t('Selected'),
  '#options' => array(
    0 => t('No'),
    1 => t('Yes'),
  ),
  '#empty_value' => TRUE,
  '#default_value' => $category['selected'],
  '#description' => t('Set this to <em>Yes</em> if you would like this category to be selected by default.'),
);

With that code, #options would be changed to array(0 => t('No'), 1 => t('- None -')). This happens because form_process_select() contains the following code.

// The empty option is prepended to #options and purposively not merged
// to prevent another option in #options mistakenly using the same value
// as #empty_value.
$empty_option = array(
  $element['#empty_value'] => $element['#empty_option'],
);
$element['#options'] = $empty_option + $element['#options'];

In PHP, settings $array[TRUE] or $array['1'] is like setting $array[1]. This means that the option whose value is 1 will be replaced by the empty option, when the empty option value is TRUE.

The documentation should not say that #empty_value can be any value and make clear that #empty_value can overrides a value given for #options.

[The issue summary have been updated to make more explicit what the issue is.]

Original report

The 'values' section for #empty_value states "Values: Defaults to empty string. Can be anything except NULL." -- this is incorrect, if it is set to the value TRUE then the empty option will overwrite $options[1] preventing the original value in $options[1] from being selected.

📌 Task
Status

Active

Component

API documentation files

Created by

🇪🇸Spain Infoloko

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • 🇮🇹Italy apaderno Brescia, 🇮🇹
  • 🇮🇹Italy apaderno Brescia, 🇮🇹
  • 🇮🇹Italy apaderno Brescia, 🇮🇹

    form_process_select() gives the following description for #empty_value.

    #empty_value: (optional) The value for the first default option, which is used to determine whether the user submitted a value or not.

    • If #required is TRUE, this defaults to '' (an empty string).
    • If #required is not TRUE and this value isn't set, then no extra option is added to the select control, leaving the control in a slightly illogical state, because there's no way for the user to select nothing, since all user agents automatically preselect the first available option. But people are used to this being the behavior of select controls. @todo Address the above issue in Drupal 8.
    • If #required is not TRUE and this value is set (most commonly to an empty string), then an extra option (see #empty_option above) representing a "non-selection" is added with this as its value.

    It is different from the description given in #empty_value, but it does not say anything about setting #empty_value to TRUE.

    Even the code in form_process_select() seems to handle #empty_value in a particular way, when it is equal to TRUE.

        // If the element is required and there is no #default_value, then add an
        // empty option that will fail validation, so that the user is required to
        // make a choice. Also, if there's a value for #empty_value or
        // #empty_option, then add an option that represents emptiness.
        if ($required && !isset($element['#default_value']) || isset($element['#empty_value']) || isset($element['#empty_option'])) {
          $element += array(
            '#empty_value' => '',
            '#empty_option' => $required ? t('- Select -') : t('- None -'),
          );
    
          // The empty option is prepended to #options and purposively not merged
          // to prevent another option in #options mistakenly using the same value
          // as #empty_value.
          $empty_option = array(
            $element['#empty_value'] => $element['#empty_option'],
          );
          $element['#options'] = $empty_option + $element['#options'];
        }
      }
    
  • 🇮🇹Italy apaderno Brescia, 🇮🇹

    The reason for which #empty_value say it can be any value except NULL is that form_process_select() checks its value with isset($element['#empty_value']); for a NULL value, isset() returns FALSE.

    Given that #empty_value is used as array index, saying it can be any value is too broad. It can be any value PHP allows for an array index.

  • 🇪🇸Spain tunic Madrid

    I think the problem is in:

     $empty_option = array(
            $element['#empty_value'] => $element['#empty_option'],
          );
    

    Let's say we have this array of options ($element['#options']) like this:

    $element['#options'] = [ 
      0 => 'zero', 
      1 => 'one', 
      2 => 'two'
    ];
    

    If #required is TRUE and #empty_option is TRUE execution will reach that line, setting $empty_option to:

    $empty_option = [ 
      TRUE => 'empty option string'
    ];
    

    TRUE is equivalent to 1, so when execution reaches the line:

    $element['#options'] = $empty_option + $element['#options']
    

    ... the final value of $element['#options'] will be:

    [
      1 => "empty option string",
      0 => "zero",
      2 => "two",
    ];
    

    The array valuw itrh index 1 is overwritten.

    But the problem is not #empty_value is TRUE but #empty_value having a value that's already used in the $element['#options'] array.

    So I would say that #empty_value can be any valid PHP array index value but should not collide with any already defined index value of $element['#options'] because it will be overwritten.

    I have deduced this form the code, I didn't test it manually using a real Drupal site.

    In Drupal 10 almost the same code is present. I'm wondering if Form API should check if the #empty_value is already in use and trigger an error, or fail silently. I think the second option will lead to obscure bugs hard to debug.

  • 🇮🇹Italy apaderno Brescia, 🇮🇹

    @tunic Thank you! Your explanation makes clear what the exact issue is. I tried the following code on PHP 7.4; it prints out 100.

    $array = [];
    $array[true] = 100;
    echo $array[1], "\n";
    

    Even replacing the $array[true] = 100; line with $array['1'] = 100; would get the same value printed.

  • 🇮🇹Italy apaderno Brescia, 🇮🇹
  • 🇪🇸Spain tunic Madrid

    Good! What would be the next steps? I'm not sure where is the code used to generate https://api.drupal.org/api/drupal/developer!topics!forms_api_reference.h.... Or just content in D.O?

    Additionally, I was wondering about triggering a warning is #empty_value is already used in #options, but it seems Logger is not available in the class.

    But I think it would be great to do somethingf when the problem is detected.

            // The empty option is prepended to #options and purposively not merged
            // to prevent another option in #options mistakenly using the same value
            // as #empty_value. However, do something is this condition is detected.
            if (array_key_exists($element['#empty_value'], $element['#options'])) {
              // Do something.
            }
            
            $empty_option = [$element['#empty_value'] => $element['#empty_option']];        
            $element['#options'] = $empty_option + $element['#options'];
    
  • 🇮🇹Italy apaderno Brescia, 🇮🇹

    The file used for the Drupal 7 Form API Reference page is the forms_api_reference.html file in this very project repository.

    The documentation page for #empty_value is present in Drupal 10 too, even if the documentation page is a different one. (See Select.)
    The documentation page should be first changed on Drupal 10, if it needs to be changed, and then the Drupal 7 documentation page should be changed to match the Drupal 10 documentation page (at least for the #empty_value part).

    Can be anything except NULL. in the Drupal 7 documentation should at least be removed, or changed.

  • 🇪🇸Spain tunic Madrid

    Created issue in Drupal core to fix the documentation: 📌 Fix the documentation for #empty_value on Drupal\Core\Render\Element\Select Needs work .

Production build 0.71.5 2024