Problem/Motivation

In several separate issues like #2318087: Replace $form_state['input'] with FormState::getUserInput(), we've been adding methods to FormState. With beta imminent, it seems like a better idea just to solidify the interface now, and convert things second.

Proposed resolution

Add getters and setters for all appropriate properties on FormState
Alter get/set/has to work only on custom properties, not internal ones.
Remove FormState::__construct() in favor of setFormState()
Use new methods in Drupal\Core\Form and Drupal\Tests\Core\Form to ensure they satisfy core's needs

Remaining tasks

N/A

User interface changes

API changes

Adding methods to FormStateInterface

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tim.plunkett’s picture

Status: Active » Needs review
FileSize
42.59 KB
sun’s picture

Nice start :)

  1. +++ b/core/lib/Drupal/Core/Form/FormState.php
    @@ -432,11 +426,357 @@ public function setFormState(array $form_state_additions) {
    +  public function setAlwaysProcess($always_process = TRUE) {
    +    $this->always_process = $always_process;
    ...
    +    $this->cache = $cache;
    ...
    +    $this->has_file_element = $has_file_element;
    ...
    +    $this->must_validate = $must_validate;
    ...
    +    $this->no_redirect = $no_redirect;
    ...
    +    $this->process_input = $process_input;
    ...
    +    $this->programmed = $programmed;
    ...
    +    $this->programmed_bypass_access_check = $programmed_bypass_access_check;
    ...
    +    $this->submitted = $submitted;
    ...
    +    $this->validation_complete = $validation_complete;
    

    1. All Boolean setters should force-cast the passed value to (bool), IMO.

    2. The class properties should use $lowerCamelCasing.

  2. +++ b/core/lib/Drupal/Core/Form/FormState.php
    @@ -432,11 +426,357 @@ public function setFormState(array $form_state_additions) {
    +  public function setBuildInfo($build_info) {
    ...
    +  public function setButtons($buttons) {
    ...
    +  public function setGroups($groups) {
    ...
    +  public function setInput($input) {
    ...
    +  public function setRebuildInfo($rebuild_info) {
    ...
    +  public function setSubmitHandlers($submit_handlers) {
    ...
    +  public function setTriggeringElement($triggering_element) {
    ...
    +  public function setValidateHandlers($validate_handlers) {
    

    Type-hint array (interface change)

  3. +++ b/core/lib/Drupal/Core/Form/FormState.php
    @@ -432,11 +426,357 @@ public function setFormState(array $form_state_additions) {
    +  public function forceUncached($no_cache = TRUE) {
    

    1. disableCache() or forceDisableCache()?

    2. This method should not accept a value; the value is always TRUE and it cannot be reverted (because doing so would break the code that originally asked for it to be disabled).

  4. +++ b/core/lib/Drupal/Core/Form/FormState.php
    @@ -432,11 +426,357 @@ public function setFormState(array $form_state_additions) {
    +  public function setExecuted($executed = TRUE) {
    

    Like 'no_cache', 'executed' is also immutable, once it is TRUE.

  5. +++ b/core/lib/Drupal/Core/Form/FormState.php
    @@ -432,11 +426,357 @@ public function setFormState(array $form_state_additions) {
    +  public function getMustValidate() {
    

    Boolean: isValidationEnforced() ?

  6. +++ b/core/lib/Drupal/Core/Form/FormState.php
    @@ -432,11 +426,357 @@ public function setFormState(array $form_state_additions) {
    +  public function getProcessInput() {
    

    Boolean: isProcessingInput() ?

  7. +++ b/core/lib/Drupal/Core/Form/FormState.php
    @@ -432,11 +426,357 @@ public function setFormState(array $form_state_additions) {
    +  public function getProgrammedBypassAccessCheck() {
    

    Boolean: isBypassingProgrammedAccessChecks() ?

Status: Needs review » Needs work

The last submitted patch, 1: 2332389-form_state-1.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
17.28 KB
47.77 KB

We can't use lowerCamelCasing until ArrayAccess is gone (without adding Yet Another Legacy layer), but I addressed the rest of your feedback, and fixed the fails.

Status: Needs review » Needs work

The last submitted patch, 4: 2332389-form_state-4.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
70.14 KB
23.98 KB
tim.plunkett’s picture

FileSize
105.82 KB

More progress.

Only thing left is filling in the @todos, and making sure we're good with the naming.

tim.plunkett’s picture

Issue summary: View changes
FileSize
120.28 KB
tim.plunkett’s picture

FileSize
1.49 KB
120.53 KB

Did a review myself, and noticed I'd forgotten to add these to the interface.

dawehner’s picture

  1. +++ b/core/lib/Drupal/Core/Form/FormBuilder.php
    @@ -188,7 +188,7 @@ public function buildForm($form_id, FormStateInterface &$form_state) {
    +      $input = $form_state->getMethod()== 'get' ? $request->query->all() : $request->request->all();
    

    suggested to add isMethod('get') which allows you to ensure that 'GET' and 'get' works. @see Request Note: Here is also one space missing.

  2. +++ b/core/lib/Drupal/Core/Form/FormBuilder.php
    @@ -239,7 +239,9 @@ public function buildForm($form_id, FormStateInterface &$form_state) {
    -        $cache_form_state = $form_state->getCacheableArray(array('always_process', 'temporary'));
    +        $cache_form_state = $form_state->getCacheableArray();
    +        $cache_form_state['always_process'] = $form_state->getAlwaysProcess();
    +        $cache_form_state['temporary'] = $form_state->getTemporary();
    

    What is the reason why this worked before?

  3. +++ b/core/lib/Drupal/Core/Form/FormBuilder.php
    @@ -280,6 +282,8 @@ public function buildForm($form_id, FormStateInterface &$form_state) {
         $form = $this->retrieveForm($form_id, $form_state);
    +    // All rebuilt forms will be cached.
    +    $form_state->setCached();
    

    Is this a functional change? Just changing behavior here could be really fragile, for example for the views UI.

  4. +++ b/core/lib/Drupal/Core/Form/FormBuilder.php
    @@ -376,9 +382,10 @@ public function retrieveForm($form_id, FormStateInterface &$form_state) {
    -    $callback = array($form_state['build_info']['callback_object'], 'buildForm');
    +    $callback = [$form_state->getFormObject(), 'buildForm'];
    

    <3

  5. +++ b/core/lib/Drupal/Core/Form/FormBuilder.php
    @@ -462,35 +470,35 @@ public function processForm($form_id, &$form, FormStateInterface &$form_state) {
    -      if (!$form_state['rebuild'] && !FormState::hasAnyErrors()) {
    +      if (!$form_state->getRebuild() && !FormState::hasAnyErrors()) {
    

    Isn't rebuild a boolean, so isRebuild?

  6. +++ b/core/lib/Drupal/Core/Form/FormState.php
    @@ -432,11 +421,359 @@ public function setFormState(array $form_state_additions) {
    +    $this->programmed = (bool) $programmed;
    ...
    +    $this->programmed_bypass_access_check = (bool) $programmed_bypass_access_check;
    

    Did we considered to throw an exception instead if you don't pass in a boolean? Just curious.

  7. +++ b/core/lib/Drupal/Core/Form/FormState.php
    @@ -525,44 +873,24 @@ public function offsetUnset($offset) {
    +  /**g
    

    ...

  8. +++ b/core/lib/Drupal/Core/Form/FormState.php
    @@ -692,19 +1048,19 @@ public function setRedirectUrl(Url $url) {
    +    if ($this->no_redirect) {
    

    Why is there no method for that? Can't you disable the redirection from outside?

  9. +++ b/core/lib/Drupal/Core/Form/FormStateInterface.php
    @@ -167,8 +166,11 @@ public function getRedirect();
    +   *   $property is a string, it will return $storage[$property]. If $property is
    

    80 chars

  10. +++ b/core/lib/Drupal/Core/Form/FormStateInterface.php
    @@ -167,8 +166,11 @@ public function getRedirect();
    +   *   an array, each element of the array will be used as a nested key. If
    +   *   $property = ['foo', 'bar'] it will return $storage['foo']['bar'].
    

    What actually happened with the 'foo][syntax'? Did we dropped them at some point?

  11. +++ b/core/lib/Drupal/Core/Form/FormStateInterface.php
    @@ -509,4 +564,406 @@ public function prepareCallback($callback);
    +   * @see \Drupal\Core\Form\FormState::$limit_validation_errors
    ...
    +   * @see \Drupal\Core\Form\FormState::$limit_validation_errors
    

    I wonder why you have added those @see statements but just for some of them. Doesn't those bits of documentation makes more sense on the interface? Otherwise they would make more sense on the actual implementation, if this is an implementation detail.

  12. +++ b/core/lib/Drupal/Core/Form/FormStateInterface.php
    @@ -509,4 +564,406 @@ public function prepareCallback($callback);
    +   *  If TRUE, the form will not redirect.
    ...
    +   *  If TRUE, the form will not redirect.
    

    Nitpick alarm: Not enough spaces

  13. +++ b/core/lib/Drupal/Core/Form/FormStateInterface.php
    @@ -509,4 +564,406 @@ public function prepareCallback($callback);
    +   */
    +  public function isRedirectDisabled();
    

    So there is a method, see above.

  14. +++ b/core/lib/Drupal/Core/Form/FormStateInterface.php
    @@ -509,4 +564,406 @@ public function prepareCallback($callback);
    +   * @param array|null $submit_handlers
    +   *   An array of submit handlers, or NULL if there are none.
    ...
    +   * @return array|null
    +   *   An array of submit handlers, or NULL if there are none.
    +   */
    +  public function getSubmitHandlers();
    

    I don't see a point in differentiating NULL from [] here.

  15. +++ b/core/modules/views/includes/ajax.inc
    @@ -17,12 +17,14 @@
    -  $form_state->setIfNotExists('build_info', array(
    -    'args' => array(),
    -  ));
    +  if (!$form_state->has('rerender')) {
    +    $form_state->set('rerender', FALSE);
    +  }
    +  // Do not overwrite if the redirect has been disabled.
    +  if (!$form_state->isRedirectDisabled()) {
    +    $form_state->disableRedirect(!empty($form_state['ajax']));
    +  }
    +  $form_state->disableCache();
    

    What happens with updating the args?

  16. +++ b/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php
    @@ -129,14 +129,13 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    -      'display' => &$this->view->display_handler->display,
    ...
    +      ->set('display', $this->view->display_handler->display)
    

    Mhh,, are we sure we don't introduce a regression here? display is not an object, it is just an array.

  17. BaseFormIdInterface mentions $form_state->getBuildInfo()['base_form_id'], did you considered adding a dedicated methdo for that?
+++ b/core/modules/user/src/Tests/UserAccountFormFieldsTest.php
@@ -31,9 +31,8 @@ class UserAccountFormFieldsTest extends DrupalUnitTestBase {
-    $install_state = install_state_defaults();
...
-    $form_state['build_info']['args'][] = &$install_state;
+    $form_state['build_info']['args'][] = install_state_defaults();

Doens't that change the behaviour? and why do you not use getBuildInfo?

tim.plunkett’s picture

FileSize
117.73 KB
10.61 KB

1) Fixed the space, but I'm not sure about changing the method. If it was named isMethod(), why would you assume it would compare to get/GET?
2) It used to work because getCacheableArray() took a parameter. But that was only used in this one place, and was weird.
3) It is not really a functional change, it just reflects reality. Previously, rebuildForm would ignore the $form_state['cache'] flag. Now it sets it. I ran this change past both @effulgentsia and @sun.

5) Fixed
6) I didn't consider it; @sun asked for the (bool) casts. I don't have a strong opinion.
7) :)
8) Fixed
9) Fixed
10) We still use that in some places, but this short array syntax is from #2316533: Add getValue/setValue/hasValue and isValueEmpty to FormState
11) I only added @see to the FormState properties when I felt the docs were best explained along with implementation details.
12) Fixed

14) Fixed
15) initializing the 'args' was something we had to do all over, but now FormState starts with them as an array by default, so this is not needed.
16) Really not sure what to do here.
17) No one needs that externally, I don't think its worth it.
18) I'm going to remove this change, and leave it for #2335659: Remove FormState ArrayAccess usage from core

Status: Needs review » Needs work

The last submitted patch, 11: 2332389-form_state-11.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
120 KB
2.27 KB

Fixing the newly added PathElementFormTest

dawehner’s picture

1) what about isMethodType maybe?
2) Okay cool
3) As long views preview, UI and exposed forms still works fine, cool!
6) Yeah just was throwing out some ideas from time to time
15) Lovely!
16) Mh, so can't we somehow get a reference here, maybe a set alternative with a reference?

tim.plunkett’s picture

FileSize
120.91 KB
4.95 KB

1) Works for me. Added a test.
16) This should work.

dawehner’s picture

Cool, IS?

tim.plunkett’s picture

Issue summary: View changes

Clarified with @dawehner that he meant a change record.
I think that https://www.drupal.org/node/2310411 should be expanded to cover this scope when it is committed, and a new final CR could be published after #2335659: Remove FormState ArrayAccess usage from core goes in...

jibran’s picture

+++ b/core/lib/Drupal/Core/Form/FormBuilderInterface.php
@@ -79,7 +79,7 @@ public function buildForm($form_id, FormStateInterface &$form_state);
+   * finished. If a validate or submit handler set $form_state->isRebuilding() to

@@ -98,7 +98,7 @@ public function buildForm($form_id, FormStateInterface &$form_state);
+   *   in $old_form and for which $form_state->getRebuildInfo()['copy'][PROPERTY]

@@ -293,7 +293,7 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state);
+   *   $form_state->getValues() and setting $form_state->isRebuilding(). The form

More then 80 chars.

tim.plunkett’s picture

FileSize
121.42 KB
2.16 KB

Thanks.

jibran’s picture

Status: Needs review » Reviewed & tested by the community

Thank you @tim.plunkett for fixing those issue. This is RTBC IMO.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Let's get them CRs updated :)

Committed ee6ddbe and pushed to 8.0.x. Thanks!

  • alexpott committed ee6ddbe on 8.0.x
    Issue #2332389 by tim.plunkett: Finish adding methods to...
tim.plunkett’s picture

Assigned: tim.plunkett » Unassigned

Thanks! Working on the CR now.
Found this minor issue, sorry about that: #2336405: Follow-up: FormStateInterface::getGroups() should return by reference

Status: Fixed » Closed (fixed)

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