Problem/Motivation
While trying to alter a node form to deny access to a boolean field, I faced an issue that disallow me to submit the form.
In a hook_form_alter I just changed #access on the field to FALSE.
When I submit the form I now get the following error:
Drupal\Core\Entity\EntityStorageException: SQLSTATE[HY000]: General error: 1366 Incorrect integer value: '' for column 'field_test_value' at row 1
Steps to reproduce:
- install Drupal standard
- add a boolean field named field_test on article content type not checked by default
- add a standard_form_alter function in standard.profile that adds #access FALSE to fied_test
- try to add a new article
Expected result: the node is created using the defaut value to the field_test field
Current result: EntityStorageException
Proposed resolution
TBD
Remaining tasks
Task | Novice task? | Contributor instructions | Complete? |
---|---|---|---|
Create a patch | Instructions | Yes | |
Update the issue summary noting if allowed during the rc | Template | ||
Add automated tests | Instructions |
User interface changes
None.
API changes
None.
Data model changes
None.
Comment | File | Size | Author |
---|---|---|---|
#56 | boolean_field_with-2609252-56.patch | 8.95 KB | Ginovski |
#56 | interdiff-2609252-51-56.txt | 1.33 KB | Ginovski |
#51 | boolean_field_with-2609252-51.patch | 8.85 KB | Ginovski |
#51 | interdiff-test-2609252-49-51.txt | 3.51 KB | Ginovski |
#51 | interdiff-old-new-patch.txt | 1.05 KB | Ginovski |
Comments
Comment #2
dawehnerThat sounds like some default value is not applied? I guess your field doesn't have a default value, so maybe the NULL is passed along?
Comment #3
DuaelFrYep that totally sounds like you said.
The thing is that I'm talking about a boolean field added via Field UI so I have no way to set it's default value when I want it unchecked by default.
Comment #4
drubbI've done a litte bit of debugging and found a difference regarding the values in $form_state supplied to the submit handlers:
- with '#access' set to true, $form_state->values['field_test']['value'] contains '0'
- with '#access' set to false, $form_state->values['field_test']['value'] contains 'false'
Looking at the form array, there's a difference, too:
- with '#access' set to true, '#default_value' contains 'false' and #value contains '0'
- with '#access' set to false, '#default_value' contains 'false' and #value contains 'false'
But in both cases, I don't get any errors.
Maybe it helps somehow.
Comment #5
ArlaI accidentally created a duplicate of this: #2645316: "Incorrect integer value" for boolean fields with disabled access Reposting my patch from there. As stated there, "SqlContentEntityStorage uses the schema from the field definition to cast the value, but only for base fields (or more correctly, fields that live in the share table). I guess it should be done also for non-base fields."
Comment #6
swentel CreditAttribution: swentel commentedWe'll definitely want a test for this
Comment #7
xjmThe addition of a procedural function here caught my eye, but this class is also the only other place the function is used. So not in scope here to worry about that. Could be a followup.
The other usage has a comment above it that references #2279395: The PostgreSQL backend does not handle NULL values for serial fields gracefully; however, that other usage wasn't actually added in that patch. It was already there and has been at least since the EntityNG conversion.
Comment #8
alexpottDiscussed with @xjm and @catch. We agreed that this is a major bug as this is something that it is reasonable to expected to work and currently there is no way to programmatically disable a boolean field on an entity without causing this error.
Comment #9
ArlaSame error occurs if you're setting the boolean field to FALSE programmatically:
I'm surprised this aspect has not been discovered and repaired before.
(IS edit: adding "Yes" to the patch complete cell)
Comment #10
BerdirNeeds work for tests.
Comment #12
lokapujyaIgnore this one.
Comment #13
lokapujyaIgnore this one too.
Comment #14
lokapujyaTried modifying a test to cover this situation.
Comment #15
lokapujyaComment #18
lokapujyaSo, I created a module that sets the boolean field's #access to FALSE.
Had to use an an actual name for the field in order to do it.
Comment #19
justAChris CreditAttribution: justAChris at Bounteous commented@lokapujya: I don't think you should be modifying an existing test as that removes test coverage, try adding fields/cases to test instead
Additionally, the resulting fails do not line up with the original error, perhaps because an existing test was modified:
Comment #20
lokapujyaThat makes sense. This was just the quickest way to get started. Posted it to get the ball rolling. Is there a better place to put this test? Will wait for some more input before continuing. To anyone interested in contributing, feel free to go in a different direction if you want to post patch or add some more advice on creating this test.
Comment #21
BerdirJust keep the existing part of the test as it is, and instead duplicate the relevant parts, possibly into a new testFormAccess or so and only there use your hardcoded name. Then in the form alter, check if the field is present and deny access if so.
Instead of a form alter, you could also implement hook_entity_field_access().
Comment #22
agentrickardhook_entity_field_access() returns the same fatal error.
Comment #23
chx CreditAttribution: chx at Smartsheet, Migrate Rocks commenteddrupal_schema_get_field_value is a really silly name it needs to be called (with a BC wrapper) drupal_schema_cast_field_value. Otherwise , great!
Comment #24
toncic CreditAttribution: toncic at MD Systems GmbH commentedI tried to make new test case.
Comment #25
chx CreditAttribution: chx at Smartsheet, Migrate Rocks commentedI believe you forgot to git add the deny_access module and as such it is missing from the patch. Also, as #21and #22 says you can use hook_entity_field_access as well instead of form alter. Hook form alter #access FALSE 'ing a field widget in an entity form is dubious at best but most likely is completely wrong and hook_entity_field_access should be used instead.
Comment #28
eiriksmJust experienced this on a site. Working on this now, updating the patch per #25
Comment #29
eiriksmComment #31
eiriksmOK, then. Was maybe a bit quick.
Here is a test-only patch and one with the suggested fix.
Comment #33
chx CreditAttribution: chx at Smartsheet, Migrate Rocks commentedGreat! thanks for the quick reroll. You can call ->getName on the fieldDefinition and use AccessResult::forbiddenIf for shorter code.
Comment #34
eiriksmDid not run the test, but here is a change as suggested. should(tm) work :)
Comment #35
chx CreditAttribution: chx at Smartsheet, Migrate Rocks commentedforbiddenIf returns neutral if the condition is false and since we use === in the condition we know no funny business (like
"foo" == 0
) can ensue.Comment #36
eiriksmI posted that right before I went to bed, and realised that had to be what you meant. Almost got back up to post a new one :)
Comment #38
BerdirComment #39
Soul88Both tests and the fix look good to me.
Comment #40
alexpottCommitted f4da728 and pushed to 8.3.x. Thanks!
Setting as 'patch to be ported' for cherry-pick to 8.2.x once 8.2.0 is out.
Wow - how painful that this database abstraction bleeds out into the entity storage layer. Funnily enough the only other usage of drupal_schema_get_field_value() is in SqlContentEntityStorage. It is tempting to ask for a followup issue to copy the logic into a castValue() method on this class and deprecate drupal_schema_get_field_value(). Doing that is certainly not in scope here.
Coding standards fixes on commit.
Comment #42
alexpottCommitted e0fd432 and pushed to 8.2.x. Thanks!
Comment #46
alexpottThis broke something in Drupal commerce. The serialization needs to occur before the call to drupal_schema_get_field_value() just as in \Drupal\Core\Entity\Sql\SqlContentEntityStorage::mapToStorageRecord(). And we need to add a test for this.
Comment #47
bojanz CreditAttribution: bojanz at Centarro commentedThe reason why this commit broke things is because it calls drupal_schema_get_field_value() before serialization, which attempts to cast an object to a string.
Possibly related: #2788023: Serialized single-value base field columns are not unserialized when loading entities and #2788637: Values in shared table for SQL content entity storage do not get unserialized..
Comment #48
mikeegoulding CreditAttribution: mikeegoulding at Ashday Interactive Systems commentedThis appears to still be an issue in 8.2.2
Comment #49
Ginovski CreditAttribution: Ginovski at MD Systems GmbH commentedAdded serialization before drupal_schema_get_field_value() is called.
Comment #50
Berdir@bojanz: Do we have something in core to be able to test this? Do we need a field type?
Comment #51
Ginovski CreditAttribution: Ginovski at MD Systems GmbH commentedAdded test to assert the serialization.
4 Files in attachment:
1. Test-only with the old patch
2. Interdiff between old patch and new (serialization made before drupal_schema_get_field())
3. Interdiff (the added test for the new patch)
4. Final working patch with test
Comment #53
BerdirNice, just two tiny things and we should be good to go.
copied I think?
Lets write "Test field type that has an object as value to test serialization." as the description.
missing docblock
Comment #54
BerdirNote that this actually also gives us test coverage for #2788637: Values in shared table for SQL content entity storage do not get unserialized., we just need to remove the @todo there.
Comment #55
BerdirComment #56
Ginovski CreditAttribution: Ginovski at MD Systems GmbH commentedAdded description and missing docs for testObjectItem() function.
Comment #57
slimbk CreditAttribution: slimbk commentedThank you it works!!
Comment #58
BerdirGreat work, I think this is ready to be committed again and hopefully stay in this time :)
See #51 for failing test.
Comment #59
alexpottCommitted and pushed 44227b1 to 8.3.x and 9d41d1c to 8.2.x. Thanks!
Coding standards fixes on commit.