For Boolean fields "isEmpty()" is always false

Created on 11 March 2021, almost 4 years ago
Updated 20 March 2023, almost 2 years ago

Problem/Motivation

Logically, when a boolean (checkbox) field is unchecked, then logically the inherited isEmpty() method should return true. Because the "On" and "Off" labels are both required in the field's configuration, the isEmpty method returns FALSE even when the checkbox is unchecked when the entity is saved.

Steps to reproduce

Add a boolean field to a content type, and then save a node of that type with the field's checkbox unchecked. Use a debugging tool to test the value of the field's isEmpty() method and observe that the value returned is FALSE.

Proposed resolution

Override the isEmpty() method to evaluate the field value against the "On" label, and return TRUE if the field's value is anything else.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

πŸ› Bug report
Status

Closed: works as designed

Version

9.5

Component
FieldΒ  β†’

Last updated 27 minutes ago

Created by

πŸ‡¨πŸ‡¦Canada mandclu

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.

  • πŸ‡§πŸ‡·Brazil cassioalmeida

    I closed the MR until I didn't have the time to work on a complete solution.

    That said, I don't believe that passing the responsibility of knowing the underline implementation of a method called isEmpty will return true only once for the Boolean field, a good thing.

    Mainly because it's not the same behavior for other field types, although they share the same interface.

    The interface docs mention the expected results:

    Drupal\Core\TypedData\ComplexDataInterface

      /**
       * Determines whether the data structure is empty.
       *
       * @return bool
       *   TRUE if the data structure is empty, FALSE otherwise.
       */
      public function isEmpty();
    

    Beyond that, we have a "field-especific method" - we can see it on other field types. For example:

    Drupal\Core\Field\Plugin\Field\FieldType\EmailItem overwrites the isEmpty method and checks for the value:

    Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem also does it.

    Drupal\Core\Field\Plugin\Field\FieldType\StringItemBase and Drupal\Core\Field\Plugin\Field\FieldType\UriItem too.

    No matter how many times you edit a node, all those fields, if the field value is empty, the method isEmpty return true; if not, false.

    The goal is to:

    1-) Avoid custom code to check if the field is empty by accessing its value.
    2-) Expect the exact behavior of fields that implements the interface.

  • πŸ‡ΊπŸ‡¦Ukraine gilmord πŸ‡ΊπŸ‡¦Ukraine

    Hi @cassioalmeida

    Again - this makes no sense to push this issue

    It looks like you are confused by the "Checkbox" form widget, as it looks the same for NULL and for FALSE
    Please try using the Radio Buttons widget, and you will see the boolean field has actually 3 options:
    - NULL
    - FALSE
    - TRUE

    And only NULL is EMPTY, FALSE and TRUE are NOT EMPTY
    and this is how the isEmpty() works now, and it is logical.
    Treating the value FALSE as EMPTY is not logical.

    Please see the attached image:
    radios β†’

    No matter how many times you edit a node, all those fields, if the field value is empty, the method isEmpty return true; if not, false.

    This is the wrong statement. use Radio Buttons and set the field value to empty as many times as you want, not only once.

    1-) Avoid custom code to check if the field is empty by accessing its value.

    This is wrong also. You do not need to check value to know if the field is empty. You can use the isEmpty() method. If you want to check if the field value is FALSE - you HAVE to check the field value. Do not mix EMPTY and FALSE.

    Please stop pushing this.

  • πŸ‡¬πŸ‡§United Kingdom jonathanshaw Stroud, UK

    @gilmord is right AFAICS.

    In php empty(FALSE ) === TRUE. But the semantic of empty is just different in Drupal.

    Drupal's semantic is more like php's is_null() than empty().
    is_null(FALSE ) === TRUE

  • πŸ‡§πŸ‡·Brazil cassioalmeida

    Thanks for the explanation.

    This is the wrong statement. use Radio Buttons and set the field value to empty as many times as you want, not only once.

    That statement was about the EmailItem and some other classes under the namespace Drupal\Core\Field\Plugin\Field\FieldType;

    I did the following test β€” a CT with two fields. Email and Boolean (checkbox widget).
    Then I created a node and left both fields without filling them out.
    Then, on a node preprocess, I created the following example:

    //Boolean field
    $node->get('field_featured')->isEmpty();
    //Email field
    $node->get('field_email')->isEmpty();
    

    The result of calling isEmpty on the Email field changes every time I update the node. If I save the node with some value in the Email field, the isEmpty result is false; if I update the node and leave the field without any value, the result of isEmpty is true. So, in that case, I can "trust" the isEmpty method to check against the value.

    Probably that was the motivation to create the issue. By using the Radio widget as an example, it does make sense. But this thread can clarify for anyone in the future looking for why it "does not work" for Boolean fields.

  • Issue was unassigned.
  • πŸ‡©πŸ‡°Denmark Steven Snedker

    Boolean field can have 2 states:
    - NULL - the entity was never saved with the field, the field is empty
    - 0/1 - the field has a value

    A boolean with three values (NULL, 0, 1) has no upside whatsoever. At least I cannot imagine a single example where it's nice or convenient to use a boolean field to find out if a user has saved his/her user profile, webform or some node.

    Booleans with three values (or two states and two values) makes a routine entityQuery awfully complex.

    Here's an example:

    $query = \Drupal::entityQuery('node')
      ->condition('type', 'measurement_result')
      ->condition('has_been_processed.value', 1, '!=')
      ->sort('changed', 'DESC')
      ->range(0, 20)
      ->accessCheck(FALSE)
      ->execute();
    

    will not give me the 0 and NULL nodes. Only the has_been_processed=0 nodes.

    So I have to make an additional

      $query = \Drupal::entityQuery('node')
      ->condition('type', 'measurement_result')
      ->notExists('has_been_processed.value')
      ->sort('changed', 'DESC')
      ->range(0, 20)
      ->accessCheck(FALSE)
      ->execute();
    

    "Ah," you may say. "You can make that more compact. Try:

    $orGroup = $query->orConditionGroup()
      ->condition('field_saved_as_single.value', 1, '!=')
      ->notExists('field_saved_as_single.value');
    

    But no. That does not work. notExists's and isEmpty's are not welcome in orConditionGroup's.

    Is there a simpler way to find out if a checkbox has been checked?
    And if not, can we make it?

  • πŸ‡ΊπŸ‡¦Ukraine gilmord πŸ‡ΊπŸ‡¦Ukraine

    Hi @Steven Snedker

    This question is not new, and it was discussed many times with the core contributors. Long story short - it works as designed (=

    The "good" way for your case (get unchecked and never checked) will be to force 0 as a default value.
    But here comes the problem - what if you add a boolean field to the existing entity? The problem appears when you have millions of existing entities.
    It does not allow us to implement any fast filling of the values - it can get timeouts and staff like that.
    So this has to be handled by the developer by adding batch processing, like Drush command or views bulk operation. There are contrib options for that - https://www.drupal.org/project/field_defaults β†’

    I suppose this is the main reason we face empty values - as you can see here https://www.drupal.org/project/drupal/issues/1919834 β†’ - it works as designed (you can check the thread to find explanations).

    Another possible option is to use a query instead of an entity query and use the LEFT JOIN to relate the boolean field table.

  • πŸ‡ΊπŸ‡¦Ukraine gilmord πŸ‡ΊπŸ‡¦Ukraine

    A boolean with three values (NULL, 0, 1) has no upside whatsoever. At least I cannot imagine a single example where it's nice or convenient to use a boolean field to find out if a user has saved his/her user profile, webform or some node.

    Sounds right, but as far as a boolean is not just a value but a separate table - the problem appears, you have to JOIN the table to use it, and default join (INNER JOIN) will skip rows with unexisting values, and LEFT JOIN is much heavier to execute.

    Just thought of one more workaround for you - you can add boolean field as a base property ( https://www.drupal.org/docs/drupal-apis/entity-api/defining-and-using-co... β†’ ).
    It can inherit all the benefits of the field but will be stored in the base entity table as single column. Like a "status" of the node.
    I think this will simplify the queries as there will be no need to join the boolean table.

    The bad side of this option - you may end up with many extra columns in the base entity table.

  • πŸ‡©πŸ‡°Denmark Steven Snedker

    You are absolutely correct. I added a boolean field to an existing content type and pretty soon had

    "NULL" in 5000 nodes
    "0" in 200 nodes
    "1" in 150 nodes

    Loading all nodes without "1" took two fairly long entityqueries.

    That sucks.

    Thank you for pointing me to Field Defaults β†’

    That is the fastest, most robust and least sucky solution to this unfortunate problem.

    Base property is a novel solution and creative. Well spotted. But knowing how Drupal is increasingly creaking under it's own weight I'll go with the simpler Field Defaults β†’ solution.

    Thanks a lot for the answer.

Production build 0.71.5 2024