Updated: Comment #12

When EntityFormController was first written, it had both submit() and save() methods, and they were attached directly to each button.
Then when EntityFormController was moved to implement FormInterface, it gained a submitForm() method.

Proposal

We tried moving all submit and save logic to submitForm(), and remove save() and submit() but this did not work because preview and save often require common code to run before they work. For an example see CommentFormController.

The current approach is to remove submit() and refactor into save() and submitForm() where appropriate. This means that for entity confirm forms the actions have been changed to not call the save() submit handler and only call submitForm().

CommentFileSizeAuthor
#69 drupal_2022875_69.patch60.63 KBBerdir
#68 drupal_2022875_68-interdiff.txt2.85 KBBerdir
#68 drupal_2022875_68.patch60.62 KBBerdir
#66 drupal_2022875_66-interdiff.txt1.79 KBBerdir
#66 drupal_2022875_66.patch60.4 KBBerdir
#64 drupal_2022875_64-interdiff.txt611 bytesBerdir
#64 drupal_2022875_64.patch60.28 KBBerdir
#62 drupal_2022875_62-interdiff.txt7.66 KBBerdir
#62 drupal_2022875_62.patch60.29 KBBerdir
#56 drupal_2022875_56-interdiff.txt564 bytesBerdir
#56 drupal_2022875_56.patch56.9 KBBerdir
#54 drupal_2022875_54.patch56.9 KBBerdir
#53 drupal_2022875_53.patch56.76 KBXano
#53 interdiff.txt3.71 KBXano
#51 drupal_2022875_51.patch54.14 KBXano
#47 drupal_2022875_47.patch72.51 KBXano
#45 drupal_2022875_45.patch57.51 KBBerdir
#42 drupal_2022875_42-interdiff.txt1.21 KBBerdir
#42 drupal_2022875_42.patch60.45 KBBerdir
#40 drupal_2022875_40.patch60.39 KBBerdir
#37 interdiff.txt691 bytesswentel
#37 drupal_2022875_37.patch61.06 KBswentel
#35 interdiff.txt2.89 KBswentel
#35 drupal_2022875_35.patch60.39 KBswentel
#33 drupal_2022875_33.patch57.5 KBBerdir
#29 drupal_2022875_29.patch57.56 KBXano
#25 entity-form-2022875-25.patch59.25 KBtim.plunkett
#22 2022875.22.patch59.3 KBalexpott
#22 20-22-interdiff.txt598 bytesalexpott
#20 12-20-interdiff.txt2.06 KBalexpott
#20 2022875.20.patch58.58 KBalexpott
#17 2022875.17.patch57.76 KBvladan.me
#12 2022875.12.patch58.21 KBalexpott
#12 10-12-interdiff.txt4.33 KBalexpott
#10 8-10-interdiff.txt33.42 KBalexpott
#10 2022875.10.patch56.44 KBalexpott
#8 2022875.8.patch53.08 KBalexpott
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tim.plunkett’s picture

Issue tags: +Entity forms, +FormInterface

Tagging.

fago’s picture

ok, so what would be the usual submit-form behaviour of an entity form then? I guess it would just update $form_state['entity']?

plach’s picture

Yep, we had the submit() method to recreate an entity object based on the submitted value ans update the wrapped entity object. The idea was that every action would need the submit() and then provide a specific submission handler for its task.

tim.plunkett’s picture

It's a really nice distinction that hasn't been used much, at least on the ConfigEntity side. Everything is crammed into submit().

plach’s picture

We could make the submit() method final and enforce a new submit handler for each action :)

tim.plunkett’s picture

No, nothing in Drupal should be final, we just need to be consistent and provide a good example.

alexpott’s picture

Talking to @tim.plunkett on IRC

alexpott: i think save() should be renamed submitForm (the current submitForm is empty), and we should remove submit()
alexpott: yeah and anyone doing something special can specify it with #submit like normal forms

I'm +1 to this. Three methods become 1 :)

alexpott’s picture

Priority: Normal » Major
Status: Active » Needs review
Issue tags: +DX (Developer Experience)
FileSize
53.08 KB

So here's a first take on make the 3 methods one. As you can see from the patch there has been confusion about which method to use - some people preferring save() and other submit(). So bumping this to major.

This patch changes everything to submitForm() which means every save method now calls parent::submitForm(). Perhaps we still want to have a save method but we should actively discourage people from overriding the EntityFormController::submitForm() by making EntityFormController abstract and moving save() to the interface but not implementing it in EntityFormController.

So lets see what's broken :)

Status: Needs review » Needs work

The last submitted patch, 2022875.8.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
FileSize
56.44 KB
33.42 KB

So it appears that there is some value in having a submitForm and save method on EntityFormController since this allow for common actions for preview and save - see CommentFormController for an example.

The issue though is that EntityConfirmFormBase and ContentEntityConfirmFormBase both extend EntityFormController and on these having the submit handler call both submitForm() and save() makes no sense. Plus current head would make it impossible to provide any default code in the save() method as it is getting called on every entity confirm form submission :)

The patch attached tries to rectify this by not calling parent::actions() in EntityConfirmFormBase and ContentEntityConfirmFormBase.

It also adds documentation indicating that if you are extending EntityFormController you probably do not need to override submitForm() but the specialised submit handler methods for the actions - delete() and save().

Status: Needs review » Needs work

The last submitted patch, 2022875.10.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
FileSize
4.33 KB
58.21 KB

So it appears the EntityFormController::submitForm breaks the add view wizard.

Patch should fix remaining fails.

alexpott’s picture

Issue summary: View changes

Update

alexpott’s picture

Issue tags: +API change

Tagging appropiately

catch’s picture

Issue tags: +Approved API change

Tagging.

catch’s picture

Issue summary: View changes

Update

Berdir’s picture

12: 2022875.12.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, 12: 2022875.12.patch, failed testing.

vladan.me’s picture

Issue summary: View changes
FileSize
57.76 KB

Re-roll #12

vladan.me’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 17: 2022875.17.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
FileSize
58.58 KB
2.06 KB

Rerolled again, from #12 and fixed some spelling mistakes

Status: Needs review » Needs work

The last submitted patch, 20: 2022875.20.patch, failed testing.

alexpott’s picture

FileSize
598 bytes
59.3 KB

New form ImageStyleFlushForm...

alexpott’s picture

Status: Needs work » Needs review
tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

I think going from 3 to 2 methods is a big enough win here, especially since the differences between submitForm() and save() are better explained now.

tim.plunkett’s picture

Dries’s picture

+++ b/core/lib/Drupal/Core/Entity/ContentEntityConfirmFormBase.php
@@ -81,14 +81,33 @@ public function form(array $form, array &$form_state) {
+  /**
+   * {@inheritdoc}
+   *
+   * The save method makes no sense on EntityConfirmFormBase. Form submissions
+   * should be processed by overriding the submitForm method.
+   */
+  public function save(array $form, array &$form_state) {}
...
+  /**
+   * {@inheritdoc}
+   *
+   * The delete method makes no sense on EntityConfirmFormBase. Form submissions
+   * should be processed by overriding the submitForm method.
+   */
+  public function delete(array $form, array &$form_state) {}

I'm rather confused by this phpDoc. I'm not sure what to make of it as a developer reading the API documentation. It reads as a '@todo: remove me' or something.

catch’s picture

Status: Reviewed & tested by the community » Needs review
areke’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

The patch no longer applies; it should be re-rolled.

Xano’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
57.56 KB

Re-roll.

Status: Needs review » Needs work

The last submitted patch, 29: drupal_2022875_29.patch, failed testing.

The last submitted patch, 29: drupal_2022875_29.patch, failed testing.

jibran’s picture

Issue tags: +Needs reroll

Needs reroll.

Berdir’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
57.5 KB

Re-rolled. That wasn't too ugly for a 2 month old patch of this size, let's see what the testbot thinks.

Status: Needs review » Needs work

The last submitted patch, 33: drupal_2022875_33.patch, failed testing.

swentel’s picture

Status: Needs work » Needs review
FileSize
60.39 KB
2.89 KB

Should be green, we still need to address #26.

Status: Needs review » Needs work

The last submitted patch, 35: drupal_2022875_35.patch, failed testing.

swentel’s picture

Status: Needs work » Needs review
FileSize
61.06 KB
691 bytes

Oh book ...

Berdir’s picture

37: drupal_2022875_37.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, 37: drupal_2022875_37.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
60.39 KB

Let's see how bad I messed up this re-roll.

Status: Needs review » Needs work

The last submitted patch, 40: drupal_2022875_40.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
60.45 KB
1.21 KB

Strange.

Berdir queued 42: drupal_2022875_42.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, 42: drupal_2022875_42.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
57.51 KB

Re-roll. How are we going to get this in?

Status: Needs review » Needs work

The last submitted patch, 45: drupal_2022875_45.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
72.51 KB

Re-roll.

// Edit: not sure if I need some more re-rolling practice or if the patch should really be 150% as big as before the re-roll.

tim.plunkett’s picture

+++ b/core/modules/contact/src/CategoryForm.php
@@ -0,0 +1,131 @@
+class CategoryForm extends EntityForm {

+++ b/core/modules/contact/src/Form/CategoryDeleteForm.php
@@ -0,0 +1,50 @@
+class CategoryDeleteForm extends EntityConfirmFormBase {

+++ b/core/modules/menu_link/src/MenuLinkForm.php
@@ -0,0 +1,310 @@
+class MenuLinkForm extends EntityForm {

+++ b/core/modules/menu_ui/src/Form/MenuLinkDeleteForm.php
@@ -0,0 +1,45 @@
+class MenuLinkDeleteForm extends EntityConfirmFormBase {

Nope, you're reintroducing deleted files.

I would double check to see that we're converting whatever code was moved out of those.

Xano’s picture

Ugh. Will go through this tomorrow morning. I didn't notice the file size until after the upload, that's why it's even in this issue.

Status: Needs review » Needs work

The last submitted patch, 47: drupal_2022875_47.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
54.14 KB

Forget I ever uploaded #47. This should be a better/proper re-roll.

Status: Needs review » Needs work

The last submitted patch, 51: drupal_2022875_51.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
3.71 KB
56.76 KB
Berdir’s picture

Status: Needs review » Needs work

The last submitted patch, 54: drupal_2022875_54.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
56.9 KB
564 bytes

Wrong merge.

tim.plunkett’s picture

Should we have save() on EntityFormInterface?

Other than that, I think this is fine.

sun’s picture

Hm. Not sure whether we've arrived at a clean improvement over status quo just yet — the currently proposed patch introduces new inconsistencies:

  1. +        '#validate' => array(
    +          array($this, 'validate'),
    +        ),
    +        '#submit' => array(
    +          array($this, 'submitForm'),
    +        ),
    

    Why is validate not validateForm?

  2. It's no longer clear whether submitForm() performs the final operation or not:

    +++ b/core/lib/Drupal/Core/Entity/EntityForm.php
    @@ -204,12 +198,14 @@ protected function actionsElement(array $form, FormStateInterface $form_state) {
    +      '#submit' => array('::submitForm', '::save'),
    

    This submitForm() does not perform the final operation.

    +++ b/core/modules/aggregator/src/Form/FeedItemsDeleteForm.php
    @@ -40,7 +40,7 @@ public function getConfirmText() {
    +  public function submitForm(array &$form, FormStateInterface $form_state) {
         $this->entity->deleteItems();
    

    This submitForm() does perform the final operation.

    +++ b/core/modules/comment/src/CommentForm.php
    @@ -245,7 +245,7 @@ protected function actions(array $form, FormStateInterface $form_state) {
    +      '#submit' => array('::submitForm', '::preview'),
    

    This submitForm() does not perform the final operation.

Berdir’s picture

I was wondering about validate too.

I think it's an improvement over HEAD, we got rid of submit() and submitForm() isn't the one that is final step, but it's a step that is almost always called (operation/confirmation forms are sometimes a bit different).

Berdir queued 56: drupal_2022875_56.patch for re-testing.

dawehner’s picture

Great work!

  1. +++ b/core/lib/Drupal/Core/Entity/ContentEntityConfirmFormBase.php
    @@ -82,14 +82,34 @@ public function form(array $form, FormStateInterface $form_state) {
    +   * {@inheritdoc}
    +   *
    +   * The save method makes no sense on EntityConfirmFormBase. Form submissions
    +   * should be processed by overriding the submitForm method.
    +   */
    +  public function save(array $form, FormStateInterface $form_state) {}
    +
    +  /**
    +   * {@inheritdoc}
    +   *
    +   * The delete method makes no sense on EntityConfirmFormBase. Form submissions
    +   * should be processed by overriding the submitForm method.
    +   */
    +  public function delete(array $form, FormStateInterface $form_state) {}
    
    +++ b/core/lib/Drupal/Core/Entity/EntityConfirmFormBase.php
    @@ -77,13 +77,35 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +  /**
    +   * {@inheritdoc}
    +   *
    +   * The save method makes no sense on EntityConfirmFormBase. Form submissions
    +   * should be processed by overriding the submitForm method.
    +   */
    +  public function save(array $form, FormStateInterface $form_state) {}
    +
    +  /**
    +   * {@inheritdoc}
    +   *
    +   * The delete method makes no sense on EntityConfirmFormBase. Form submissions
    +   * should be processed by overriding the submitForm method.
    +   */
    +  public function delete(array $form, FormStateInterface $form_state) {}
    

    This might be a usecase for final, just guessing though.

  2. +++ b/core/modules/block_content/src/BlockContentForm.php
    @@ -175,23 +175,15 @@ public function form(array $form, FormStateInterface $form_state) {
    +    /** @var \Drupal\block_content\BlockContentInterface $block */
    +    $block = $this->entity;
    

    Did you considered to alternative document $this->entity itself? It could be available in more places then

  3. +++ b/core/modules/system/src/Form/DateFormatFormBase.php
    @@ -173,12 +173,23 @@ public function validate(array $form, FormStateInterface $form_state) {
    +    $status = $this->entity->save();
    

    Did we considered that the parent save method returns the result of $this->entity->save() so we can easily call the parent?

  4. +++ b/core/modules/views_ui/src/ViewDeleteForm.php
    --- a/core/modules/views_ui/src/ViewDuplicateForm.php
    +++ b/core/modules/views_ui/src/ViewDuplicateForm.php
    
    +++ b/core/modules/views_ui/src/ViewDuplicateForm.php
    @@ -58,23 +58,26 @@ protected function actions(array $form, FormStateInterface $form_state) {
         $actions['submit'] = array(
           '#type' => 'submit',
           '#value' => $this->t('Duplicate'),
    ...
    +   * Form submission handler for the 'clone' action.
    +   *
    +   * @param array $form
    +   *   An associative array containing the structure of the form.
    +   * @param \Drupal\Core\Form\FormStateInterface $form_state
    +   *   A reference to a keyed array containing the current state of the form.
        */
    ...
    +  public function cloneView(array $form, FormStateInterface $form_state) {
    +    $this->entity = $this->entity->createDuplicate();
         $this->entity->set('id', $form_state->getValue('id'));
         $this->entity->save();
     
         // Redirect the user to the view admin form.
         $form_state->setRedirectUrl($this->entity->urlInfo('edit-form'));
    

    Don't we call it duplicate now everywhere?

Berdir’s picture

Thanks for the review.

#56:
1. Added to the interface.

#58: I would prefer to look into validate()/validateForm() in a separate issue. This has been re-rolled often enough.

#61:
1. Hm. Not sure. Maybe, but that could be messy with unit tests if someone wants to unit test (parts of) a form.
2. Yes, documented the property now, I think that's a pretty good pattern. Also noticed some bogus documentation there, there is no Next button or wizard there anywhere.
3. Yes, I thought that before as well, did that now. Not too many overrides call into the parent, but what's funny is that ImageStyleEditForm actually expected it to work like that but it didn't. And now that it actually worked, there were duplicated save messages. Cleaned that form up a bit.
4. renamed to submitForm(), like most (all?) other confirm forms.

Status: Needs review » Needs work

The last submitted patch, 62: drupal_2022875_62.patch, failed testing.

Berdir’s picture

Status: Needs work » Needs review
FileSize
60.28 KB
611 bytes

This should fix the fails. Was a temporary state while cleaning up edit forms, no longer necessary to pass it through.

swentel’s picture

+++ b/core/modules/image/src/Form/ImageStyleEditForm.php
@@ -249,17 +236,25 @@ public function effectSave($form, FormStateInterface $form_state) {
   }
 
+
   /**
    * {@inheritdoc}
    */

Extreme nitpick: superfluous new line

I'm wondering whether we still need to address #26

Berdir’s picture

Removed that. Tried to improve those descriptions.

swentel’s picture

+++ b/core/lib/Drupal/Core/Entity/ContentEntityConfirmFormBase.php
@@ -100,16 +100,20 @@ protected function actions(array $form, FormStateInterface $form_state) {
+   * The save() method is not used EntityConfirmFormBase. This overrides the

Hmm, this should be delete() right ?

Berdir’s picture

Yep and I missed one form.

Berdir’s picture

FileSize
60.63 KB

Re-roll.

swentel’s picture

Status: Needs review » Reviewed & tested by the community

Checked this twice now, let's move forward here.

catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.0.x, thanks!

  • catch committed 4c8576c on 8.0.x
    Issue #2022875 by Berdir, alexpott, Xano, swentel, tim.plunkett, vladan....
Arla’s picture

Could we get a change record for this?

Arla’s picture

Submitted a draft change record at https://www.drupal.org/node/2336747

Status: Fixed » Closed (fixed)

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

effulgentsia’s picture

I would prefer to look into validate()/validateForm() in a separate issue. This has been re-rolled often enough.

See #2453175-59: Remove EntityFormInterface::validate() and stop using button-level validation by default in entity forms. Note: that issue title doesn't currently match the patch; not sure yet if we'll want to retitle that issue or revert to that issue's earlier direction.

quietone’s picture

Published the CR