Problem/Motivation

Currently, any implementations of \Drupal\Core\Entity\EntityForm::copyFormValuesToEntity(), or any #entity_builders callback will be run twice on form submit.

This is usually not a problem, as they copy things from $form_state->getValues() to the $entity.

It becomes problematic when your code checks the old values in comparison to the new values. In this case, on the second run, different code paths will be followed.

This is due to an incorrect check in \Drupal\Core\Entity\EntityForm::afterBuild() introduced in #2448357: Config entity forms need to have access to an updated entity object at all times.
It uses $form_state->isProcessingInput(), but should use $form_state->isSubmitted().

This is purposeful, because content entities must be updated with the form values in order to be validated.

Proposed resolution

Document it.

Remaining tasks

Document it.

User interface changes

N/A

API changes

N/A

Data model changes

N/A

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tim.plunkett created an issue. See original summary.

tim.plunkett’s picture

Status: Active » Needs review
FileSize
694 bytes

Status: Needs review » Needs work

The last submitted patch, 2: 2799637-entityform-2.patch, failed testing.

tim.plunkett’s picture

Assigned: Unassigned » tim.plunkett

The more I write tests, the less I can reproduce. Hmmm.

Wim Leers’s picture

(Hoping to indirectly trigger some additional insight or jolt a memory by providing my experience with this.)

I know I've struggled with this one too. I forget in which exact context — it was either:

  1. in relation to the Quick Edit module
  2. in relation to the work I did on making forms not store their state always by default (which you also worked on)
  3. while working on debugging some AJAXy things: the entire form state is being rebuilt always, nothing is actually ever retrieved from form state

So, from what I remember: there is something extremely fishy going on with EntityForm::init() and EntityForm::prepareEntity(). Basically, this is a lie:

  /**
   * Prepares the entity object before the form is built first.
   */
  protected function prepareEntity() {}

It definitely doesn't run only when the form is first built.

EDIT: I think I remember where I saw the AJAXy fishiness: while debugging/patching something related to the "Text formats & editors" configuration UI: there is an AJAXy text editor <select>, which causes an AJAX form to be loaded. There have been multiple issues related to that, and there's one remaining one: #2701393: Switching between editors on the format configuration causes errors upon save.


Again, sorry if this is a distraction. Just trying to help you make the connection to other issues.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
909 bytes

Thanks Wim!
Trying something here.

Status: Needs review » Needs work

The last submitted patch, 6: 2799637-entityform-6.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
910 bytes

Stupid typo.

Status: Needs review » Needs work

The last submitted patch, 8: 2799637-entityform-8.patch, failed testing.

tim.plunkett’s picture

Okay, so after a conversation with Berdir in IRC, I'm not 100% convinced this is a bug.

But it sure is confusing!

For content entities, whenever they are rebuilt they need the form values applied, so that non-form-based validation like Constraints can run.
So, any code called by buildEntity (copyFormValuesToEntity and #entity_builders) MUST be idempotent.

I guess this issue should become about documenting that, preferably without the word "idempotent".

tim.plunkett’s picture

Title: Non-idempotent #entity_builders fail due to incorrect $form_state check in EntityForm::afterBuild » Document that #entity_builders and overrides of EntityForm::copyFormValuesToEntity() must be idempotent
Category: Bug report » Task
Issue summary: View changes
Issue tags: -Needs tests +Documentation
Berdir’s picture

what if...

Kind of expecting everything to explode, but maybe not?

Status: Needs review » Needs work

The last submitted patch, 12: validate-build-once-2675010-12.patch, failed testing.

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
1.3 KB

Well, I guess because of the clone reference problem, arguably there is not that much difference at the moment between setting $this->entity or not, because the field values will be the same anyway.

So any bug will be masked in the same way the form language problem is currently masked.

Trying out the fix for the cloning plus the patch from #12 (the build-twice one).

hchonov’s picture

The problem with the cloning only occurs when the entity has more than one translation otherwise not, so the problem will not be masked for each case.

tstoeckler’s picture

Having thought about this a lot for the past couple of days, I now relaize the issue title reveals quite precisely why ContentEntityForm::updateFormLangcode() is problematic (as it is an #entity_builder: it's not idempotent! It sets a value into the form state. So regardless of whether the built entity gets thrown away or set into $this->entity, the form state now has a different language code. That is a problem IMO.

Berdir’s picture

See \Drupal\user\AccountForm::buildEntity() for an interesting example. We are changing form state there as well, we just have to prevent that we do the same multiple times and break it the second time.

And yes, that is the only way I could find to do what we need to to there. Would be much easier if roles were a widget.

tim.plunkett’s picture

\Drupal\field_ui\Form\EntityDisplayFormBase::processFieldUpdates() used $form_state->getTemporaryValue('entity_display_components_updated') to make the tricky parts idempotent.

hchonov’s picture

@berdir - \Drupal\user\AccountForm::buildEntity() only transforms the user submitted values for the roles in a acceptable way for the entity "roles" field and this form state value is only used later to set it on the entity field. So it basically does not alter the state of the form. It basically does what WidgetBase::extractFormValues would do.

However as you've said it would be easier if roles were a widget. So why don't we provide a widget for the roles field or am I missing something?

tim.plunkett’s picture

Assigned: tim.plunkett » Unassigned

Not actively working on this.

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.

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.

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.

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.

smustgrave’s picture

Priority: Major » Normal
Status: Needs review » Needs work
Issue tags: +Needs Review Queue Initiative

This issue is being reviewed by the kind folks in Slack, #needs-review-queue-initiative. We are working to keep the size of Needs Review queue [2700+ issues] to around 400 (1 month or less), following Review a patch or merge request as a guide.

Lowering to normal after 6 years not sure if it's a major issue.

The last remaining task is to document this but I don't see any documentation?

Could someone please take a look

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.