Problem/Motivation
#512026: Move $form_state storage from cache to new state/key-value system removed the code that deleted form "cache" entries after submission. After discussion with @znerol and especially with the recent immutable form changes, we think that is wrong, and that we should in fact always delete them, because the only ones that shouldn't be deleted are immutable form caches, and those should never get there.
Proposed resolution
For now, just hacking it in there to see what happens...
Remaining tasks
User interface changes
API changes
Beta phase evaluation
| Issue category | Bug because it's a regression from 7.x that leads to some form resubmissions causing errors (see #16) |
|---|---|
| Issue priority | Major because the regression would result in bugs for any form (core and contrib) that stores data in $form_state that is only valid for a single submission. |
| Disruption | This wouldn't require modules to make any code changes, but for some modules, it might change the behavior of how users can use the module's forms in conjunction with a browser's Back button. For simple multistep forms (like core's "Preview" or "Add another item" buttons), this doesn't introduce any regressions, because $form_state can be successfully reconstructed on-demand. For contrib cases where $form_state can't be fully reconstructed (e.g., a wizard, or something that adds CAPCHA elements that persist a state of whether they've been already solved), this might break the ability to finish the last step of the form, use the Back button, and submit a successful second submission (without needing to start over at the first step), but this is the same limitation as exists in 7.x and earlier. |
| Comment | File | Size | Author |
|---|---|---|---|
| #17 | form_caches_should_be-2473759-17-PASS.patch | 11 KB | tim.plunkett |
| #17 | form_caches_should_be-2473759-17-FAIL.patch | 2.56 KB | tim.plunkett |
| #1 | form-delete-cache-2473759-1.patch | 830 bytes | berdir |
| #5 | form-delete-cache-2473759-5.patch | 5.4 KB | berdir |
| #9 | interdiff.txt | 4 KB | tim.plunkett |
Comments
Comment #1
berdirLet's see what happens.
Comment #2
berdirComment #4
wim leersComment #5
berdirFixed tests and done properly now.
Calling it from the FormBuilder, might need to move that, but then I need to inject FormCache into the FormSubmitter :(
Comment #6
wim leersLooks good to me, but should probably be reviewed by somebody with deeper Form API knowledge.
Comment #7
znerol commentedPerhaps I'm missing something, but it looks like this line could be added in between the first and the second
ifstatement?Comment #8
znerol commentedI think it would make sense to also test that we really delete form-state on form-submission. Not sure whether
FormTestBase::simulateFormSubmission()will trigger the removal.Comment #9
tim.plunkett#8 Yes, we absolutely can use that (we need to flag it as a non-programmed submission.
Addressed #7 as well. We want to do it after doSubmitForm() runs, but before returning the response.
Comment #10
tim.plunkettUgh, wrong patch, right interdiff.
EDIT: The new tests I added pass in isolation, but not as part of the larger suite. Debugging now.
Comment #12
tim.plunkettWe have a stray static to clear.
Comment #14
effulgentsia commented+1 to this patch from me. It makes 8.0.x consistent with 7.x and earlier versions. I also left a comment in #512026-180: Move $form_state storage from cache to new state/key-value system and suggest holding off on RTBC until people following that issue have had a chance to comment here.
Comment #15
berdir@effulgentsia: Thanks. I forgot to mention that I already discussed this issue in person with @alexpott in Montpellier, he didn't remember why he thought that the form_build_id wouldn't be there and was OK with adding it back.
Comment #16
effulgentsia commentedInspired by #2465053-21: Drupal 8 only allows one user every 6 hours to register when page caching is enabled — caused by entity UUID in form state, I tried the following:
1. Create a node that you can comment on, then visit that node page.
2. Type in a comment, click "Preview". This creates a multistep workflow (i.e., CommentForm::preview() calls
$form_state->setRebuild()), which causes $form_state to be persisted.3. On the Preview step, click "Save". In HEAD, this does not delete the $form_state stored in step 2. With this issue, it should.
4. Use the back button. The browser is nice to show you the prior "Preview" step.
5. Click "Save" again.
In HEAD, this results in a fatal error, because the undeleted $form_state includes the serialized comment, which includes a UUID, and so step 5's Save attempts to create another comment with the same UUID. #2465053: Drupal 8 only allows one user every 6 hours to register when page caching is enabled — caused by entity UUID in form state might change it so that UUIDs don't get persisted in $form_state, but this still demonstrates a general problem of a form state becoming invalid after submission, prior to the 6 hour expiration.
With #12, I expected this to get fixed, but it doesn't, because alexpott was partially correct in #512026-146: Move $form_state storage from cache to new state/key-value system:
The 'form_build_id' is no longer in $form_state, because for entity forms, the doSubmitForm() invokes EntityForm::submitForm(), which calls
$form_state->cleanValues(). Changing the above to:fixes that problem.
With that change applied, the use case above works (a new comment gets saved), because $form_state gets deleted from storage in step 3, and so step 5 happens with a brand new, reconstructed $form_state. Note that a different use case might not work the same way. For example, a complex multistep workflow where there's critical information in the last step's $form_state that can't be newly reconstructed. In such a case, clicking back and resubmitting might result in some other error. But, it would be the same error as would happen in HEAD anyway if you waited for 6 hours and then did it. In other words, contrib modules building such workflows need to account for deleted form states regardless since they can expire.
NW for the change above plus we should probably expand the tests to cover entity forms.
Comment #17
tim.plunkettAdjusted the unit tests to not test get/set, just delete, and mimicked the ->cleanValues() call in the unit test.
Thanks to @Wim Leers for the clever suggestion on how to mimic hitting the back button in a web test!
Comment #18
tim.plunkettBumping this up.
Comment #21
effulgentsia commentedRTBC + added a beta evaluation to the IS.
Comment #22
effulgentsia commentedThe table of patch files at the top of the issue is ordered wrong. Trying to at least move the latest patch to the top.
Comment #23
effulgentsia commentedahem, trying again.
Comment #24
effulgentsia commentedAssigning to alexpott, because of #15 and because he originally authored the change to not delete after submission in #512026-146: Move $form_state storage from cache to new state/key-value system.
Comment #25
fabianx commentedRTBC + 1
Comment #26
catchLeaving for Alex, but patch looks right to me.
Comment #27
alexpottLooks good to me too. Committed 189be72 and pushed to 8.0.x. Thanks!