Problem/Motivation
Currently in ContentEntityForm we build the entity once in the validation and once in the submit, however if the validation passed without errors then the submit will be called and will have to build the entity again.
This has two problems ;
1. In case of nested entity forms building the entity is expensive.
2. Because of entity builders doing changes to form state etc.. the entity build in validate and later in submit again might differ which means it might happen that we save an entity that we haven't validated and also might not be valid at all.
There is even an existing problem in core where we are saving an entity without completely validating it - #2834030: Currently we are saving an user entity with some not validated fields on the registration form and it just proves that it is so easy to oversee such things and how important this issue is in order to prevent saving invalid entities - in core, custom and contrib.
Proposed resolution
Store the entity build in the validation phase in $this->intermediateEntity and use this later in in submit instead of building the entity again.
Remaining tasks
Write the patch plus test ensuring we are saving the same entity that we have validated.
User interface changes
None.
API changes
None.
Data model changes
None.
Comment | File | Size | Author |
---|---|---|---|
#51 | 2833682-nr-bot.txt | 144 bytes | needs-review-queue-bot |
#43 | interdiff-41-43.txt | 1.82 KB | gease |
#43 | 2833682-43.patch | 10.84 KB | gease |
#41 | interdiff-39-41.txt | 746 bytes | gease |
#41 | 2833682-41.patch | 10.63 KB | gease |
Comments
Comment #2
hchonovComment #3
vprocessor CreditAttribution: vprocessor at Skilld commentedComment #4
hchonovI've implemented the proposed solution from the issue summary.
@vprocessor sorry ;)
Comment #7
vprocessor CreditAttribution: vprocessor at Skilld commentedComment #8
vprocessor CreditAttribution: vprocessor at Skilld commentedComment #9
vprocessor CreditAttribution: vprocessor at Skilld commentedComment #11
hchonovSo the problem was that the user registration form was relying on building the entity twice and has done stuff with the form values which has to be done as part of ::buildEntity instead of ::submitForm.
Comment #12
vprocessor CreditAttribution: vprocessor at Skilld commentedComment #13
hchonov@vprocessor when you are posting a patch could you please provide interdiffs and a little explanation what you are doing? Is there anything you do not agree with regarding the last patch I've posted?
Comment #15
hchonovRe-uploading the patch from #11 as the currently working active patch to be reviewed.
Comment #16
hchonovOups.. there it is...
Comment #17
Berdirend of that sentence doesn't make sense, a not missing?
I think this is exactly why I gave up on doing something like this in #2799637: Document that #entity_builders and overrides of EntityForm::copyFormValuesToEntity() must be idempotent.
Any change that requires changing something else means that it is an API change and breaks BC, unless it is in areas where we say that's ok, which is I think not true for this.
Comment #18
hchonovYes :). Will update in the next patch version :).
Yes I know that this is a change, but it is there for two reasons - one is performance and the second is to ensure that exactly the entity we have validated is saved and not any other one. Take a look at the paragraphs module which actually is a famous one and there is an inconsistency that when the langcode of the language widget is altered and the form is submitted the entities built during the validation phase will have the language code they had during initially building the form but then in the submit the generated entities will differ than the ones generated during the validation and will have the new language code. This leads into saving entities which are not validated and under circumstances might even lead to some constraints not validating what is being saved. Consider e.g. a validation constraint on the paragraph entity that the language "test_language" is not a valid translation, but currently they would pass the validation with the previous langcode and be saved with the new one...
That is why I think that this API change is an important and needed one as everybody makes mistakes and this would prevent such ones - I guess we all want that when we start using a contrib or also a custom module in our system that this module is not saving not-validated entities.
Comment #19
hchonovAddressing #17.1 and moving all knowledge of the intermediate entity to the content entity form only.
Comment #20
hchonovJust a small improvement for a shorter statement.
Comment #21
hchonovThe part where I had to adjust the RegisterForm is actually an issue on itself - #2834030: Currently we are saving an user entity with some not validated fields on the registration form as because of the current implementation not all user entity fields are correctly validated. #2834030: Currently we are saving an user entity with some not validated fields on the registration form just proves that it is so easy to oversee such things and how important this issue is in order to prevent saving invalid entities - in core, custom and contrib.
Comment #22
hchonovComment #23
hchonovComment #25
hchonovRe-roll + instead of unsetting the intermediateEntity property in __sleep it is better to set it to NULL as in some issues we've seen problems by doing so on PHP 5.6.
Comment #26
BerdirI don't understand why you don't properly define this with default value FALSE and then set back to FALSE instead of unsetting, which is IMHO very weird and would simplify the code a lot below.
Comment #27
hchonovYou are right .. I've addressed this and also found a little mistake in buildEntity having logic still running twice, so I've covered that too.
Comment #29
hchonovComment #30
hchonovWould it be better/simpler if instead of having these two new properties on the content entity form just to put the validated entity as a temporary value in the form state and then retrieve it in the submit?
In this case we don't have to worry about the serialization of the form, form object and the form state as temporary values are being thrown away and we don't serialize between validation and submit.
What I mean is:
$form_state->setTemporaryValue('validated_entity', $entity);
Comment #32
hchonovAdding getter and setter to ContentEntityFormInterface for the intermediate entity.
Comment #33
tim.plunkettI think these should explain when to use these instead of ::getEntity()/::setEntity()
Comment #36
tstoecklerAuto-rebase thanks to Git.
Comment #39
geaseFixed Drupal\Tests\system\Functional\Entity\EntityFormTest failure for 8.9. All other failures are on layout builder, and, truly, layout builder doesn't work with this patch. Entity built on validation doesn't contain layout elements, while entity built on submit contains them. The solution may be to introduce "entity_rebuild" flag and rebuild entity on submit only when this flag is set.
Comment #41
geaseFixed layout builder by removing condition which was meant to be removed with this patch.
Comment #42
hchonovThis is just amazing. The current approach has been just validated as needed for core!
When referring to methods in a comment we should append braces.
Also #33 still has to be addressed.
Comment #43
geaseTried to fix the comments in response to #33 and #42 p2.
Comment #47
rp7 CreditAttribution: rp7 as a volunteer commentedI have a use-case where I need the built entity (intermediate entity as it's called in this issue) in a
#element_validate
callback for a specific form element. I believe this would be a solution for this as well?Comment #51
needs-review-queue-bot CreditAttribution: needs-review-queue-bot as a volunteer commentedThe 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.