Placing a block on block structure page (Zurb Foundation OR Bartik) causes the following error:
RuntimeException: The subform and parent form must contain the #parents property, which must be an array. Try calling this method from a #process callback instead. in Drupal\Core\Form\SubformState->getParents() (line 76 of /Users/maria.mcdowell/Sites/devdesktop/eemmcdowell-test/docroot/core/lib/Drupal/Core/Form/SubformState.php).
[EDITED]
I tested against multiple blocks. Any block in the category "content" fails to load, as does "user" blocks. My initial test was against what I thought was a view block, but it turns out that it was the content block created by a module, probably CTools, of the same name.
Note that the Entity (user and content) blocks work without error, so it is not all things created by CTools.
Comment | File | Size | Author |
---|---|---|---|
#56 | 2798261-form_state.patch | 3.34 KB | dmitrypro77 |
#25 | drupal_2798261_25.patch | 19.75 KB | Xano |
#22 | 2798261-form_state-22.patch | 3.34 KB | tim.plunkett |
#22 | 2798261-form_state-22-interdiff.txt | 3.1 KB | tim.plunkett |
Comments
Comment #2
JesperWe CreditAttribution: JesperWe commentedHaving the same issue. On my setup what I see so far is that all block types added by Chaos Tools Blocks fail.
Comment #3
dawehner@mariagwyn
Do you also have ctools installed? Maybe it is a bug in ctools itself.
Comment #4
mariagwyn CreditAttribution: mariagwyn commentedI fully uninstalled CTools. Still having the issue on Image Embed, Embed module, but all others seem fine, including my Lists (Views) block. However, I do recall that I was able to insert Entity (content) which is a CTools block. It is now gone so I can't test. So, this is not CTools, but it is also not clear that it is core.
Comment #5
mariagwyn CreditAttribution: mariagwyn commentedAfter re-enabling CTools, I am pretty sure this error has more to do with blocks place by Panelizer. Blocks in the category "Content" or some "User" blocks, all fail with this error. Since I am running D8.2 with lightning, which is a big no-no right now, I think this is not core. I will close and shift when I have a better idea where to shift.
Comment #6
mariagwyn CreditAttribution: mariagwyn commentedComment #7
KarlSheaI can't place or edit Ctools content blocks, I'm getting the same error.
It's failing when the form calls $form_state->getValue().
Comment #8
KarlSheaThis is a core issue, any usage of $form_state->getValue() in BlockBase's blockForm causes this error.
Comment #9
KarlSheaComment #10
KarlSheaComment #11
ransomweaver CreditAttribution: ransomweaver commented$form_state->getCompleteFormState()->getValue() will fix.
Comment #12
KarlSheaThat does fix it, but FormStateInterface doesn't have that method. Shouldn't BlockBase/BlockPluginInterface then have SubformStateInterface in the method signature instead?
Comment #13
tim.plunkettI'm not 100% sure this is the right fix, but it's a start.
Most form builders should not be calling getValue/getValues, but it is valid to do so when a form is rebuilding.
Comment #14
KarlSheaThat fixes the immediate error, but then the form doesn't rebuild.
This is the code from Ctools that is breaking (from ctools/modules/ctools_block/src/Plugin/Block/EntityField.php):
If Ctools is not rebuilding the form correctly I'd be happy to write a patch, but I'm having a hard time finding an example of the correct way to do this now after the SubformState changes.
Comment #15
XanoI'm one of the authors of the subform state code, and I've got some time to look at this problem this week and the next. However, I'm not too familiar with Ctools. Could someone update the issue summary with step-by-step instructions on how to reproduce the problem on a clean Drupal installation? That would be a great help in debugging this issue. Thanks!
Comment #16
tim.plunkettTry to configure ANY block with the -fail patch applied.
Comment #18
tim.plunkettHere's a more targeted approach. Actually getting the #parents wasn't ever really possible during build anyway.
Comment #19
XanoTo make a long IRC conversation short:
#process
callbacks are executed.'#tree' => TRUE
), or if someone does this usinghook_form_alter()
.SubformState
relies on this information to work, effectively forcing subforms that receive it to use#process
.PluginFormInterface
andBlockPluginInterface
implementations, for instance.Comment #20
catchI'm going to bump this to critical since it's an unexpected (albeit @internal) API change from 8.1.x to 8.2.x, and it looks like we can mitigate the uncaught exception in core.
Comment #22
tim.plunkettHere's a test, fwiw
Comment #23
KarlShea#22 doesn't fix the form in the linked Ctools issue. It doesn't outright break the same as it did before, but now the AJAX callback doesn't update the form.
It might be that Ctools is depending on old behavior that Subforms now change/fix, and will have to be updated, but like I said above—I can't find an example of how the Subform authors are now expecting these forms to work so I can't write that patch. It would be helpful to have an example form and some documentation around what the expectations are now. All I can find is "Embedded forms do not have to do anything, except assume that their $form and $form_state parameters belong together" which is clearly not the case when AJAX callbacks are involved.
Here's an example:
Formatters list:
When changing the formatter, the AJAX callback tries to load configuration into the form, but now fails (Image style was from the Image formatter):
Comment #24
EclipseGc CreditAttribution: EclipseGc commentedSo, I looked at this for a bit tonight. Seems like a requirement of using SubformState is that both the parent and subform must have documented #parents. I naively fixed this by forcing BlockForm::form() to properly document this before creating the SubformState. I don't even think I did it right and that's working for me. Is this a wrong approach?
If not, we could introspect the subform and parentform in the SubformState::__construct() to ensure it has parents and throw much more reasonable error messages based upon incorrect usage of the class. Just spit-balling here.
Eclipse
Comment #25
XanoThis adds deprecation errors to the two types of plugin forms we converted to using subform states in #2537732: PluginFormInterface must have access to the complete $form_state (introduce SubFormState for embedded forms). Let's see which plugins still don't receive subform states from their calling code in practice.
I do not think the approach in #22 is a good idea. Even in Drupal 7, subforms could only be properly supported by leveraging
#process
, as it is the earliest moment where#parents
is known, which is needed to get the subform's values from the complete form state. This cannot be solved any other way until we change this behavior in Form API. Instead, I think we should warn developers to build subforms the right way. Next to that, #22 can lead to weird behavior, because it returns empty arrays by reference to pretend the current input is empty, while in a situation in which the input cannot technically be retrieved.Comment #27
effulgentsia CreditAttribution: effulgentsia at Acquia commentedSo, I think the underlying problem here is that
$form_state->getValue()
SHOULD NOT be called during form building, prior to when input processing has taken place, since there is no logical definition for what value ought to be there. So, in principle, I think for Drupal 9, we should haveFormState::getValues()
and friends throw an exception when called too early. And a step in that direction might be something along the lines of #22, but moved fromSubformState
toFormState
. However, I think adding deprecation errors should happen in a minor release, not a patch release.But since HEAD's current implementation of SubformState::getValues() represents an unintentional and undocumented breakage to places that used to receive a FormState object (such as in blockForm()), I think we should still consider some kind of fix in an 8.2 patch release. I don't yet have a suggestion for what that fix should be.
But I'm not sure about this being Critical priority. From https://www.drupal.org/core/issue-priority#critical-bug:
Do we know of anything in the wild that is running into this error other than the
ctools_block
submodule of CTools? And that's a module that's in the"Chaos tool suite (Experimental)"
package. I don't think that a break in a single contrib module, listed as Experimental, justifies this issue being Critical.Comment #28
ransomweaver CreditAttribution: ransomweaver commentedI have had to patch the Field as Block module with
$form_state->getCompleteFormState()->getValue()
In blockForm() To prevent a fatal error. But the maintainer gets a warning that I do not, so its maybe not a universal solution.
https://www.drupal.org/node/2810783
https://www.drupal.org/project/fieldblock
Comment #29
KarlSheaI don't think the issue is that there's only one or two modules that are known to be breaking, the issue is that there's no documentation on what Ctools is doing wrong. How should a form be rebuilt based on the current value of a field?
Ctools is doing exactly what I've seen in many other places in D7, and is what the pattern used to be.
For example: http://cgit.drupalcode.org/examples/tree/ajax_example/ajax_example.modul...
in ajax_example_autotextfields() line 470:
Unfortunately there isn't an AJAX example for D8, and there's no docs I can find about how this should work. Was it just that this situation wasn't thought about when making the SubformState changes?
Comment #30
EclipseGc CreditAttribution: EclipseGc commentedSooo... what if we just reverted blockForm? Maybe a handful of other places where this change wasn't entirely necessary? After discussing this at length in irc, it's obvious that some of the acrobatics Tim and I did to make blocks be embeddable for things like panels really made this change unnecessary there. Or at the very least, much less likely to run into problems.
The problem of course with calling to the getCompleteFormState() method is that you no longer have a sub-set of form state values. This is fine so long as no one down the chain mis-uses a #tree, but if that were to happen at any point, the form state values we're attempting to retrieve would instantly become problematic. Reverting blockForm to the manually created FormState specific to the 'settings' form element would actually solve most of the "critical" problem here. I'm happy to discuss what a proper solution looks like in the CTools code base too, but just for the sake of not breaking potential undiscovered contrib blocks at this point, I think this is our best course of action. (just my suggestion)
This doesn't mean the new API isn't useful, and it doesn't mean we're not using it, it just means that for the block system, we would continue to use what was working in 8.1.x. I haven't looked at conditions yet, but I think the same nuances that were used to make block plugin config forms completely stand alone was also used for conditions, so if we've made the same expectations at that level, we should probably revert that implementation as well. I don't know of any other core plugins that had this level of detail. Tim could provide more color to this I'm sure (and he may totally disagree with me, I don't know) Whatever the case I wanted to propose this and see what the reaction was.
Eclipse
Comment #31
tim.plunkett8.0.0 blocks shipped with a fix for *form values* only.
Any other method calls made on $form_state within block plugins were completely thrown away.
Most problematic is form errors.
Drupal\block\Tests\BlockUiTest::testBlockValidateErrors() and Drupal\block_test\Plugin\Block\TestSettingsValidationBlock illustrate this problem.
Essentially, setting an error in BlockBase::blockValidate() did absolutely nothing before 8.2.0
We could revert, and solve that specific problem by continuing to code around the problem in the block system.
Or we could come up with some as-yet-unknown fix for this problem.
Comment #32
effulgentsia CreditAttribution: effulgentsia at Acquia commentedWithin
Drupal\ctools_block\Plugin\Block\EntityField::blockForm()
, what if you simply change:to:
In other words, why do you need
#default_value
to reflect the new value duringblockForm()
? During form processing, validation, and submission, you have access to#value
instead, which will be correct, regardless of#default_value
.If there are cases where there's other code that branches based on
#default_value
rather than#value
, then you could add a#process
callback to$form
(or whatever other appropriate parent element) that adjusts the descendent element's#default_value
to match what's in$form_state
, but per #25, that's the earliest point at which you can do so in a way that works for subforms.Comment #33
effulgentsia CreditAttribution: effulgentsia at Acquia commentedYes, that example is incorrect for subforms, because it assumes that you know that
ask_first_name
appears at the top-level of$form_state['values']
.Yep, good point. This would be really nice to get done.
Comment #34
KarlSheaI made that change and it did indeed seem to work, I'll post a patch in the Ctools issue and see what they think over there.
Comment #35
Xano@effulgentsia: Form builders may need to access submitted form values if they're multi-step. Say you're at step 2, and go back to step 1. The elements should no longer default to the hardcoded defaults for every instance of that form, but to the values you entered the first time you submitted step 1.
The fix for this is, and has been in Drupal 7 and likely even in earlier Drupal versions, to use
#process
callbacks. Those callbacks are invoked after Form API sets#array_parents
and#parents
, which are needed to determine the subform's position within the parent form, and the subform's values within the parent form's values. If these have not been set whenSubformState
is asked to get the subform's values, it throws an exception telling you to use#process
callbacks.The only way I see around this, is to provide a way to set
#parents
and#array_parents
in the parent form's build method. We could abstract out this logic fromFormBuilder
, so form build methods can call this code and set these properties before they invoke subform builders, andFormBuilder
itself can also call it to ensure the properties are set everywhere, just like it does now. There'll be some duplication and a small speed penalty, but it'll ensure subforms do not have to use#process
as long as their parent forms use this new code. We might be able to perform this insideSubformState
, so it's all done automatically.Comment #36
XanoNow I've looked at
SubformState
again I'm not sure if the approach I suggested in #35 is even possible, asSubformState
would have to know its position in the parent form when it is constructed, and that's exactly the information its factory method does not receive. Luckily the constructor was originally made protected, so we can always modify or extend it and add additional factory methods. If we can come up with a solution that tellsSubformState
which exact parent form's element the subform will become, we may have a shot at the approach I suggested we try.Back to the question of why form build methods need to access submitted form values: what if the submitted form values cause the form to be rebuilt with a different structure? Can we still properly access form values in build methods then? If not, what would be the right solution?
Comment #37
alexpottThis was discussed with @xjm, @catch, @webchick, @effulgentsia, @Cottser, @cilefen and myself. We decided that this should be downgraded to a major because they is nothing in the issue that qualifies it to be critical as per https://www.drupal.org/core/issue-priority but only after a CR exists that details the difference between 8.1 and 8.2 and is attached to this issue and #2537732: PluginFormInterface must have access to the complete $form_state (introduce SubFormState for embedded forms).
Comment #38
xjm@Xano created https://www.drupal.org/node/2774077 back in July and I published it in October. Are there updates we need to add to it?
Comment #40
xjmWay back @effulgentsia, @catch, @alexpott, and I (and maybe other committers) discussed this bug, as per #37, and agreed to downgrade it. As it's been a month now, I'm doing so. Thanks!
Comment #41
kienan CreditAttribution: kienan commentedThis affected using the contrib module bootstrap_layout (with layout_discovery) as well. Patch #25 no longer applies (8.3.4), but patch #22 applied and worked around the issues I faced.
Comment #44
bkosborneI'm trying to build a block form that will have a dynamic aspect to it. If a user chooses one option from a specific select menu, the form will be rebuilt with new options available. Is this possible with this issue still present in core? I looked at ctools module and it's doing something with a #process callback that I can't quite figure out... but maybe that's how it worked around the problem.
Comment #45
Greenstack CreditAttribution: Greenstack commentedHas any progress been made with this recently?
Comment #46
oriol_e9g@bkosborne I'm doing the same, a block form that changes dynamically when users selects options, altering the form build with ajax calls. My solution to work around this issue is doing something like this:
Probably it's not a best practice but works.
Comment #48
alphawebgroupis it still actual?
Comment #49
berliner CreditAttribution: berliner commentedStill running into it in 8.7, using the same type of workaround as in #46.
Comment #51
ericpughI'm having the same issue in Drupal 8.8.5 trying to put a custom Block in Layout Builder.
Comment #53
camerongreen CreditAttribution: camerongreen commentedYep, still have same issue with 9.0.6.
I literally spent a couple of workdays trying to figure this out, until I stumbled across #46.
Comment #56
dmitrypro77 CreditAttribution: dmitrypro77 commentedComment #58
higherform CreditAttribution: higherform commentedThis bug (or an extremely similar one) occurs with:
When attempting to add more than one paragraph item to a node, the second and subsequent paragraphs fail to AJAX into existence. Then the WSOD / exception appears on node save.
If Behaviors -> Paragraphs Layout option is unchecked, everything proceeds as expected.
Exact exception message for this operation case is :
RuntimeException: The subform and parent form must contain the #parents property, which must be an array. Try calling this method from a #process callback instead. in Drupal\Core\Form\SubformState->getParents() (line 76 of core/lib/Drupal/Core/Form/SubformState.php).
Comment #59
higherform CreditAttribution: higherform commented