Problem/Motivation

This has been copied from a comment by TR in #2509268-6: Inline errors repeated on child elements in module uninstall form.

The documentation for FormStateInterface::setErrorByName($name, $message) says that $name holds the name of the form element, but it doesn't specify what should happen if $name is empty or $name holds an invalid form element name.

In core, it seems that $name = '' is used as a shortcut to set an error for the entire form, and NOT for just one specific form element as the function was designed to do. ag "setErrorByName\(\'\'" core
finds 13 instances of this in core.

Proposed resolution

Making the undocumented feature official by documenting it and provide a test.

Do not actually make a default of '' and mark it (optional) in the docs. Because then, using both defaults... results in an empty error on the whole form. And, we dont want to make it too easy or make it appear that is the preferred way (and it would be an API change).

But, maybe this is taking advantage of an undocumented/unintentional side-effect of the setErrorByName() function. If we don't have a name, maybe we shouldn't be using setErrorByName(). Follow up: #2780343: Add a method to FormStateInterface to set the error message on the whole form

Remaining tasks

  • (done in #14) document grep command for finding occurrences in core (can we look in contrib?)
Contributor tasks needed
Task Novice task? Contributor instructions Complete?
Add automated tests/verify test coverage Instructions
Review patch to ensure that it fixes the issue, stays within scope, is properly documented, and follows coding standards Instructions

User interface changes

No.

API changes

No.

Data model changes

No.

Comments

pfrenssen created an issue. See original summary.

pfrenssen’s picture

pfrenssen’s picture

Issue summary: View changes
pfrenssen’s picture

Personally I favor solution #2 - to provide a new method. I share @TR's sentiment that we shouldn't call setErrorByName() if we don't have a name.

dmsmidt’s picture

A big plus one for addressing this. Currently it's such a confusing way to mark the form as having an error without targeting a single element. I strongly prefer solution #2, otherwise we make something confusing official.

Thinking aloud:
Hopefully such a new method won't be misused in the way that people will just skip marking elements as erroneous when it becomes a bit difficult.

For example:
There are cases in which a combination of inputs causes an error but you don't want to show error messages for all input fields.
Then someone can just use this new method and show the user a generic error message, however the problematic fields aren't highlighted in this case (bad accessibility).
By the way, if you would do it right and mark elements as erroneous without adding a message, you will see there is some rendering cleanup to do (Found and fixed in this issue: #2455933-45: Error highlighting and reporting problems for the current password on the user profile form).

So if we add a new method I'd like to see a note in the docs that people should not forget marking the problematic fields as having an error.

avpaderno’s picture

This is not different from what done in Drupal 7 core, which used code like form_set_error('', t('This book has been modified by another user, the changes could not be saved.'));. I don't see the need of having a new method, when it is enough to document what happens when $name is an empty string.

KarlKedrovsky’s picture

It looks to me like a simple documentation update would be the right thing to do. I've created a simple patch that adds the documentation to the $name parameter.

KarlKedrovsky’s picture

Status: Active » Needs review
avpaderno’s picture

I would rather put it at the beginning.

yesct’s picture

I'm at drupalcorn sprint. I'm going to review this.

yesct’s picture

my inclination is that in this issue we can do the documentation, and make a different issue for making a new method. [edit: new issue #2780343: Add a method to FormStateInterface to set the error message on the whole form ]

"Making the undocumented feature official by documenting it, marking the $name as optional, and providing a test."

But I would interpret it as "documenting the current behavior".

While talking to @KarlKedrovsky they had a good point, that if it was actually made optional and defaulted to the empty string (behavior), then it would be possible for both arguments to be the empty defaults, and there would be an empty error on the whole form, which is not particularly useful... so making that behavior too easy, might not be what we want to encourage. making it a default is also an API change (well kinda). I think documenting the current behavior will also make an other issue to change it easier to do.

yesct’s picture

Category: Feature request » Bug report
Issue tags: +Needs tests

do we know for sure there are no tests already for the empty name?

I'll look and see if I can find (related) test coverage.

----

about the order in the docs, we might want to put them in the order of "most common usage" to least? We might be able to use formatting to make it easier to read.

I'll take a stab at that.

yesct’s picture

Issue summary: View changes
Issue tags: +Documentation
StatusFileSize
new1.21 KB
new1.12 KB

I checked coding standards https://www.drupal.org/node/1354#lists ... didn't do the "usual" colon. hm.

patch and interdiff https://drupal.org/documentation/git/interdiff (even though the interdiff is mostly like the patch)

gonna look for tests next.

yesct’s picture

Issue summary: View changes

looked at ag -B6 "setErrorByName\(\\\$name" core results
(to see if maybe $name might have been set to be '' before the call and didn't (quickly) see any)

ag "setErrorByName\(\'\'" core
finds setErrorByName(''

ag "setErrorByName\(\'\'" core | wc -l
counts 13

---

ag "setErrorByName\(\'\'" core | grep -i test

yields

core/modules/system/tests/modules/form_test/src/Callbacks.php:44: $form_state->setErrorByName('');
in method validateName()

      // Trigger a form validation error to see our changes.
      $form_state->setErrorByName('');

Not testing what happens with '', just calling it to.... "trigger error". hm.

core/modules/system/tests/modules/form_test/src/Form/FormTestValidateForm.php:63: $form_state->setErrorByName('');
same. hm.

---

ag "@covers.*setErrorByName" core

core/tests/Drupal/Tests/Core/Form/FormStateTest.php
102:   * @covers ::setErrorByName

three calls, name is not empty

core/tests/Drupal/Tests/Core/Form/FormStateTest.php
134:   * @covers ::setErrorByName

one call, name not empty

core/tests/Drupal/Tests/Core/Form/FormStateDecoratorBaseTest.php
1310:   * @covers ::setErrorByName

name is 'foo'

core/tests/Drupal/Tests/Core/Form/SubformStateTest.php
297:   * @covers ::setErrorByName

has two calls, both not empty names.

---

ok. so.

suppose that confirms no test for the behavior of setting the error on the form when the $name is ''.
next: figure out where to place such a test.

yesct’s picture

StatusFileSize
new800 bytes
new1.9 KB

guessing...
and just a little tiny start at a test (not sure how to assert the "correct" behavior of the error being *on the whole form* and not on an element)

run with
../vendor/bin/phpunit tests/Drupal/Tests/Core/Form/FormStateTest.php --filter=testSetErrorByName

---
also updated the issue summary to not lose track of the little detail about "what does/should happen when the name is *invalid*"

yesct’s picture

StatusFileSize
new1.17 KB
new2.27 KB

make test pass, kinda. looks for the error that is "on the form" and not an element, but not sure how to assert the error is on the form and not on an element.
not sure how to test for an invalid name for a form element... taking that out for now.

I'm probably done with this for a while, so open to reviews.

The last submitted patch, 15: 2780209-15.patch, failed testing.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

tim.plunkett’s picture

Copying my comment from #2780343: Add a method to FormStateInterface to set the error message on the whole form

Given this code:

$form_state->setError($form, 'This error is for the whole form');

Internally that calls:

$form_state->setErrorByName('', 'This error is for the whole form');

The first approach is much clearer, IMO.
But both work, and because one calls the other, they both have to work.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

ranjith_kumar_k_u’s picture

StatusFileSize
new2.23 KB

Re-rolled for 9.2

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

This patch is the same as #28 but with a tests-only patch. If the tests fails I'll mark RTBC

smustgrave’s picture

StatusFileSize
new1.11 KB
new2.24 KB

Would help if I uploaded the files

smustgrave’s picture

Status: Needs review » Needs work

Moving back to needs work as the tests patch should have failed.

tr’s picture

As it says in the issue summary, I raised this issue seven years ago as part of #2509268-6: Inline errors repeated on child elements in module uninstall form. My comments there were not completely transcribed, and it appears that an important point I made there has been lost:

To be clear, this is not just a documentation issue; if '' is supposed to produce a known result, then setErrorByName() should explicitly check $name == '' rather than fall-through to some non-specified behavior.

It's not sufficient to just put a comment in setErrorByName() and call it a day. The fundamental assumption in FormState is that the internal array $errors will only hold errors keyed by the form element those errors belong to. (That's also not documented, but that's how the code is written). Specifically, there is no indication in any of the FormState source code that the keys of the $errors array can be anything other than form element names, and there is no indication or awareness in the code that the empty string is a "special" key used for a special purpose. The get/set methods for this internal array are supposed to be the API we use to manage that internal array, but they don't deal with this special key at all and (relevant to this issue) do not document, even after the patch, that the empty string is a special value in the $errors array.

The fact that passing $name = '' to the setErrorByName() method currently works is an accident, it is NOT by design. Documentation doesn't fix the design. And the current patch doesn't even document all the methods that need to know that this $name key can be an empty string.

This is one of the reasons I suggested new methods to get/set an error on the entire form - that suggestion was spawned off into yet another issue #2780343: Add a method to FormStateInterface to set the error message on the whole form which has not made any progress either. There are also other ways this can be handled, but just documenting one of the many methods affected is not one of those ways.

If you look at the source code for setErrorByName(), if $name is an empty string there is no explicit behavior - the method only works because all the other code in FormState is ignorant of what to do with an empty string key, not because there is an intention to use an empty string key for a specific purpose.

avpaderno’s picture

The assumption is that $form_state->setError($form, "Form error") is always equivalent to $form_state->setErrorByName('', "Form error"), while that is true IIF $form['#parents'] is an empty array, because in that case implode('][', $element['#parents']) (used by FormState::setError()) will return an empty string.
That is a fragile assumption: $form['#parents'] isn't required to be an empty array. For a sub-form, it would make sense for $form['#parents'] to contain at least an item.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.