Problem/Motivation

If the EntityFormDisplay::validateFormValues finds any violations then it does :
$form_state->setErrorByName('', $violation->getMessage());.
However the form element is provided with the parameter $form and it is better to use it instead of setting an error in the form state by name with an empty name string.

Proposed resolution

Replace $form_state->setErrorByName('', $violation->getMessage()); with
$form_state->setError($form, $violation->getMessage());.

FormState::setError calls setErrorByName and builds the name by imploding the #parents of the given element. For simple entity forms there will be no change as $form['#parents'] will be always an empty array, but complex and nested form widgets will profit from the change by correctly setting the error on the element caused validation failure.

Remaining tasks

Review.

User interface changes

For nested and complex widgets on validation the element, for which the validation has failed, will now be highlighted with an error.

API changes

none.

Data model changes

none.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

hchonov created an issue. See original summary.

hchonov’s picture

Version: 8.1.x-dev » 8.2.x-dev
FileSize
794 bytes

Status: Needs review » Needs work

The last submitted patch, 2: 2767125-2.patch, failed testing.

hchonov’s picture

Status: Needs work » Needs review

Unfortunatelly there no complex and nested inline entity forms in core and so I cannot think of a suitable test for this issue where the parents will be set in the given form array for any module in core. If anyone could think of such a case please explain and I will write the test.

amateescu’s picture

Status: Needs review » Reviewed & tested by the community

I did a bit of digging around this code and it seems that the ->setErrorByName() call with an empty string as the first argument was introduced in #2395831-102: Entity forms skip validation of fields that are not in the EntityFormDisplay, where it replaced a call on an actual element path: $form_state->setErrorByName(str_replace('.', '][', $violation->getPropertyPath()), $violation);.

So I think that the patch is correct by assigning the error to the $form element instead, because this indeed takes care of complex use cases and allows embedded entity forms to report errors correctly.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs tests

I think it is worth having test coverage here because

this indeed takes care of complex use cases and allows embedded entity forms to report errors correctly.

hchonov’s picture

Added test coverage.

Status: Needs review » Needs work

The last submitted patch, 7: 2767125-7-test-with-fix.patch, failed testing.

The last submitted patch, 7: 2767125-7-failing-test.patch, failed testing.

hchonov’s picture

Cleared some oversights.. Should be better now.

The last submitted patch, 10: 2767125-10-failing-test.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.

amateescu’s picture

The test looks great! Just a few minor things that should be fixed before RTBC:

  1. +++ b/core/modules/field/src/Tests/NestedFormTest.php
    @@ -166,4 +166,35 @@ function testNestedFieldForm() {
    +  function testNestedEntityFormEntityLevelValidation() {
    

    Missing member visibility, i.e. 'public' :)

  2. +++ b/core/modules/field/src/Tests/NestedFormTest.php
    @@ -166,4 +166,35 @@ function testNestedFieldForm() {
    +      ->create(['id' => 1]);
    +    $entity_1->enforceIsNew();
    ...
    +      ->create(['id' => 2]);
    +    $entity_2->enforceIsNew();
    

    This enforceIsNew() is not really needed, you can just call the id() method on both entities when you need it below.

  3. +++ b/core/modules/field/src/Tests/NestedFormTest.php
    @@ -166,4 +166,35 @@ function testNestedFieldForm() {
    +    $this->assertFieldByName('entity_2[changed]', 0, 'Entity 2: changed value appears correctly is the form.');
    

    "in" the form :)

  4. +++ b/core/modules/field/tests/modules/field_test/src/Form/NestedEntityTestForm.php
    @@ -33,16 +34,26 @@ public function buildForm(array $form, FormStateInterface $form_state, EntityInt
    +      // @see Drupal\field\Tests\NestedFormTest::testNestedEntityFormEntityLevelValidation.
    
    @@ -65,7 +76,16 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    +    // @see Drupal\field\Tests\NestedFormTest::testNestedEntityFormEntityLevelValidation.
    

    I think our coding standards say that we need to put () at the end of method names in code references.

hchonov’s picture

@amateescu Thank you for the review. I've addressed all your remarks.

amateescu’s picture

Status: Needs review » Reviewed & tested by the community

Awesome, looks great now :)

The last submitted patch, 14: 2767125-14-failing-test.patch, failed testing.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 14: 2767125-14-test-with-fix.patch, failed testing.

amateescu’s picture

Status: Needs work » Reviewed & tested by the community

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 14: 2767125-14-test-with-fix.patch, failed testing.

tim.plunkett’s picture

Component: forms system » entity system
hchonov’s picture

Status: Needs work » Reviewed & tested by the community

It has been a random failure, so moving back to RTBC as it has been done by @amteescu in #15.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed 505eb55 to 8.3.x and 6633476 to 8.2.x. Thanks!

  1. +++ b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
    @@ -236,7 +236,7 @@ public function validateFormValues(FieldableEntityInterface $entity, array &$for
         foreach ($violations->getEntityViolations() as $violation) {
           /** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */
    -      $form_state->setErrorByName('', $violation->getMessage());
    +      $form_state->setError($form, $violation->getMessage());
         }
    

    This is not introduced here but you only set one error per element - see \Drupal\Core\Form\FormState::setErrorByName(). So I think this code is flawed but it is less flawed than before.

  2. +++ b/core/modules/field/src/Tests/NestedFormTest.php
    @@ -52,7 +52,7 @@ protected function setUp() {
    -  function testNestedFieldForm() {
    +  public function testNestedFieldForm() {
    

    Whilst correct, this change is out of scope. Reverted on commit.

  • alexpott committed 505eb55 on 8.3.x
    Issue #2767125 by hchonov, amateescu: EntityFormDisplay::...

  • alexpott committed 6633476 on 8.2.x
    Issue #2767125 by hchonov, amateescu: EntityFormDisplay::...
alexpott’s picture

We should have a follow-up for #22.1

hchonov’s picture

This is not introduced here but you only set one error per element - see \Drupal\Core\Form\FormState::setErrorByName(). So I think this code is flawed but it is less flawed than before.

alexpott, do you mean that we need a follow-up issue for extending FormStateInterface::setError and ::setErrorByName to be able of setting multiple errors on an element instead of only one?

hchonov’s picture

Status: Fixed » Closed (fixed)

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