Problem/Motivation

PluginFormInterface allows plugins to build their own forms. It accepts form elements and the form state.

The problem is that the interface does not clearly document the form elements are the plugin's own elements, and not the parent form the plugin form may be embedded in. The reason for this is that otherwise the plugin has no way to find its position within the complete form, or the form state's submitted values. This causes problems like #2532968: Block plugin forms assume they are top-level.

Proposed resolution

Document that the $form parameter accepts the plugin's own form elements.

Remaining tasks

None.

User interface changes

None.

API changes

None.

Data model changes

None.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Xano’s picture

Status: Active » Needs review
FileSize
1.37 KB
jhodgdon’s picture

Status: Needs review » Needs work

This change seems OK but I'm not convinced it solves the problem stated in the issue summary. Maybe in addition to calling it "the plugin form", you could add a few words to indicate it is possibly just a small piece of a larger form?

Actually though, why would that be the case?

And it seems to me that if $form_state->getValue* doesn't work, that is a larger problem than just plugin forms. I'm sure we have this assumption all over core for form builders. None of them will work if the form is embedded elsewhere, so it sounds like no form should make this assumption?

Xano’s picture

FormInterface exists for complete forms. PluginFormInterface–which does not have a form ID and therefore cannot be a form of its own–exists so plugins can provide embeddable form segments. It is technically impossible to use these as forms on their own; they must always be embedded in an actual form.

FormStateInterface::getValue() is fine to use, as long as you know where exactly in the values you need to look. Embeddable forms can never know, so they must use #array_parents and #parents to determine their place in the form and form state. This is known and designed behavior, and it's already partly documented in PluginFormInterface's docblock.

jhodgdon’s picture

OK, fair enough.

So in order to really document this well, so that this type of problem will not happen in the future, what I would suggest is:

a) Put something into the FormStateInterface methods where you need to pass in the parents that says something like:

If you are calling this from an embeddable form (a form meant to be embedded in a larger form), you must pass in (whatever the parents parameter is called).

b) In PluginFormInterface in the class docs header, document that plugin forms are meant to be embedded and that you need to be careful about assumptions when you get values out of the $form_state.

c) I still think that the patch you've submitted here doesn't do enough for those methods either... My suggestion would be instead of just changing the docs on those methods for the $form argument from saying "form" to "plugin form", let's say "embeddable plugin form", and then on $form_state, let's say "for the entire form (including the embedded plugin form)" or something like that to get across the point that $form is just the plugin portion, and $form_state is the entire form state.

Does that make sense?

Xano’s picture

Status: Needs work » Needs review
FileSize
3.31 KB
4.04 KB

a) Put something into the FormStateInterface methods where you need to pass in the parents that says something like:

If you are calling this from an embeddable form (a form meant to be embedded in a larger form), you must pass in (whatever the parents parameter is called).

FormStateInterface does not support getting nested values. Besides, it's not the form state, but the form itself that is different, so documentation should remain there.

In PluginFormInterface in the class docs header, document that plugin forms are meant to be embedded and that you need to be careful about assumptions when you get values out of the $form_state.

This was already documented, but may have been unclear because the documentation said plugin forms were usually embedded. The patch changes the wording so it's clear they are always embedded.

Status: Needs review » Needs work

The last submitted patch, 5: drupal_2532970_5.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
3.29 KB

That looks like a rebase error.

Xano’s picture

FileSize
1.49 KB
3.89 KB

Now with code suggestions.

jhodgdon’s picture

Status: Needs review » Needs work

Thanks for the new patches! I think this still needs some work though:

  1. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -10,18 +10,22 @@
    + * Provides an interface for a plugin that contains a embeddable form.
    

    a embeddable -> an embeddable

    Also this one line doc just seems wrong. As it reads now, it says it is an interface for a plugin, but I think it is actually an interface for a plugin *form* right? Or maybe it's a plugin configuration form really? I think that is what it actually is.

    So the first line needs a rewrite.

  2. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -10,18 +10,22 @@
    + * to have these properties available when building the plugin form's elements,
    + * let buildConfigurationForm() return a form element that has a #process
    + * callback and build the rest of the form in the callback. By the time the
    + * callback is executed, the element's #parents and #array_parents properties
    

    A code example would be helpful here, or it could go into the buildConfigurationForm() method. Actually, most of this documentation is specific to buildConfigurationForm() so maybe it should go there instead?

  3. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -31,7 +35,7 @@
        * Form constructor.
        *
        * @param array $form
    -   *   An associative array containing the structure of the form.
    +   *   An associative array containing the initial structure of the plugin form.
        * @param \Drupal\Core\Form\FormStateInterface $form_state
    

    Maybe in this method, make sure that again it is stressed that #array_parents and #parents are not set in $form?

  4. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -31,7 +35,7 @@
        * @param \Drupal\Core\Form\FormStateInterface $form_state
        *   The current state of the form.
    

    Why would it be so terrible on the $form_state docs here to add the word "entire" so that it is clearer that $form_state and $form are not referring to the same thing?

  5. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -44,9 +48,12 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
    +   *   An associative array containing the structure of the plugin form as built
    +   *   by static::buildForm().
        * @param \Drupal\Core\Form\FormStateInterface $form_state
    

    Um, is it really buildForm() or buildConfigurationForm()?

  6. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -44,9 +48,12 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
    +   *   The current state of the form. The values submitted for the plugin form
    

    Again, could we say "state of the entire form" here?

  7. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -44,9 +48,12 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
    +   *   \Drupal\Component\Utility\NestedArray::getValue($form_state->getValues(), $form['#parents']);
    

    Any way to split this line to less than 80 characters? Also probably should use @code ... @endcode since this is not just one function name but a code example.

  8. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -54,9 +61,12 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
    +   *   by static::buildForm().
    

    again, is this really from buildForm() or buildConfigurationForm()?

  9. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -54,9 +61,12 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
    +   *   The current state of the form. The values submitted for the plugin form
    

    Again, could we say "entire form" here?

  10. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -54,9 +61,12 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
    +   *   \Drupal\Component\Utility\NestedArray::getValue($form_state->getValues(), $form['#parents']);
    

    Again, use @code / @endcode

jhodgdon’s picture

Also, just as a note, our coding standards for docs say that when referring to a class/method in docs, you put in the entire namespace and class name (or in this case, interface name) rather than using shortcuts like static::methodName().

Xano’s picture

Thanks for the extensive review! I will roll a new patch.

Also, just as a note, our coding standards for docs say that when referring to a class/method in docs, you put in the entire namespace and class name (or in this case, interface name) rather than using shortcuts like static::methodName().

self would be a shortcut, but static definitely isn't (see late static bindings). Besides that, I am all in favor of being explicit, but using static or self here has the added advantage that we don't have to change the code example whenever the class is renamed or moved. Even if this does indeed violate our current standards, I'd argue that the standards should be relaxed as these references do not point to something external.

Xano’s picture

Status: Needs work » Needs review
FileSize
4.32 KB
4.2 KB

Why would it be so terrible on the $form_state docs here to add the word "entire" so that it is clearer that $form_state and $form are not referring to the same thing?

That's a good idea. Thank you. I did opt for complete rather than entire, because that's what the form array contained by form states are called.

Xano’s picture

As we discussed on IRC I did not bluntly ignore the feedback about static versus FQNs. I created #2536670: Relax class/interface reference documentation standards to explicitly allow these forms of documentation.

jhodgdon’s picture

Status: Needs review » Needs work

I like it! Just a couple of nitpicks and one substantive question/comment...

  1. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -30,10 +19,25 @@
    +   * to have these properties available when building the plugin form's elements,
    

    nitpick: this crosses 80 characters.

  2. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -30,10 +19,25 @@
    +   * let buildConfigurationForm() return a form element that has a #process
    

    If this is staying here (see next comment), maybe say "this method" instead of "buildConfigurationForm()" ?

  3. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -30,10 +19,25 @@
    +   * During validation and submission, the plugin form's elements' #parents and
    +   * #array_parents properties have already been populated and can be used
    +   * directly to find the location of the submitted form values in the form's
    +   * state.
    +   *
    

    Well, the problem with moving this whole block of docs to the buildConfigurationForm() method doc block is that this part is not related to this method, so it doesn't belong on this method doc... not sure what to do? I guess on the validate/submit methods, you could say something like "Unlike on the build method, ..." or something?

  4. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -44,9 +48,14 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
    +   *   The current state of the complete form. The values submitted for the plugin form
    

    over 80 chars, rewrap

  5. +++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
    @@ -54,9 +63,14 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
    +   *   The current state of the complete form. The values submitted for the plugin form
    

    80 chars

Xano’s picture

Status: Needs work » Needs review
FileSize
2.81 KB
3.94 KB
jhodgdon’s picture

Status: Needs review » Reviewed & tested by the community

Now, this looks good and I think it accomplishes the goals of this issue: getting across to the developer that they need to be careful with this stuff. !! Great!

tim.plunkett’s picture

Component: documentation » plugin system
+++ b/core/lib/Drupal/Core/Plugin/PluginFormInterface.php
@@ -44,9 +44,14 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
+   *   The current state of the complete form. The values submitted for the
+   *   plugin form can be retrieved through
+   *   @code
+   *   \Drupal\Component\Utility\NestedArray::getValue($form_state->getValues(), $form['#parents']);
+   *   @endcode

@@ -54,9 +59,14 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
+   *   The current state of the complete form. The values submitted for the
+   *   plugin form can be retrieved through
+   *   @code
+   *   \Drupal\Component\Utility\NestedArray::getValue($form_state->getValues(), $form['#parents']);
+   *   @endcode

Please leave this out until #2536646: [meta] PluginFormInterface implementations assume they are top-level and friends are decided upon.

Also putting this into documentation prevents the subsystem maintainers from easily finding their issues.

tim.plunkett’s picture

Status: Reviewed & tested by the community » Needs work
jhodgdon’s picture

Sorry, didn't realize it was controversial or I definitely would have put it into that component.

Xano’s picture

@tim.plunkett: if you feel this needs more consideration, couls you update either issue with the reason why?

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
3.54 KB
1.61 KB

I meant this.

jhodgdon’s picture

Status: Needs review » Reviewed & tested by the community

This seems acceptable to me. Provisionally setting to RTBC unless Xano objects.

Xano’s picture

I'm okay with it. Let's tackle the form state issue in #2536646: [meta] PluginFormInterface implementations assume they are top-level.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Docs are not frozen in beta. Committed b116b58 and pushed to 8.0.x. Thanks!

  • alexpott committed b116b58 on 8.0.x
    Issue #2532970 by Xano, tim.plunkett, jhodgdon: PluginFormInterface must...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.