I need to add an extra function to a node form after it has submitted. In drupal 7 all I had to do was add a submit handler. Everywhere I look the same thing was said about drupal 8, however I can't get the submit handler to trigger.

Here is the code I tried:

// Implements HOOK_form_BASE_FORM_ID_alter
function mymodule_form_node_form_alter(&$form, FormStateInterface $form_state) {
    $form['actions']['submit']['#submit'][]  = 'mymodule_node_form_submit';
}

function mymodule_node_form_submit($form, FormStateInterface $form_state) {
  
  // mymodule_do_something($node);
 \Drupal::logger(' mymodule')->notice('mymodule submit ') ;
  
}

I'm stumped on this. The alter is working, 'mymodule_node_form_submit' is being added to the submit array, its just not executing the function.

Anyone have any ideas?

Thanks.

Comments

tarnus’s picture

Figures I would post this and then solve the problem on my own.

After searching through modules for drupal 8, I finally found one that had a submit handler. Here is updated code for those of you that have the same issues I had.

function mymodule_form_node_form_alter(&$form, FormStateInterface $form_state) {
 
    foreach (array_keys($form['actions']) as $action) {
      if ($action != 'preview' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') {
        $form['actions'][$action]['#submit'][] = 'mymodule_node_form_submit';
      }
    }
}
 
function mymodule_node_form_submit($form, FormStateInterface $form_state) {
   $node = $form_state->getFormObject()->getEntity();
  // mymodule_do_something($node);
 \Drupal::logger(' mymodule')->notice('mymodule submit ') ;
  
}
ryrye’s picture

Thank you @tarnus, I found myself in the exact same situation and this was very helpful.

makbul_khan8’s picture

Thanks, saved my day.

sagesolutions’s picture

Thanks, worked for me too!

rabithk’s picture

If I want to update any values in the form submit , how can I handle . Inside second submit I have tried with $form_state->setValue(['field_name',0,'value'], 10); , but this is not updating the field .

marcoscano’s picture

As far as I have understood the way to change the values in the FormState is using FormState::setValueForElement() in the validate function of your form, so the values will arrive to the main submit function already updated.

DYdave’s picture

On top of @marcoscano's reply, I would like to add two other methods I've been using in projects, and more specifically answering the question:

How can I update the field value in submit?

1 - Running submit callback before Core's and all others: array_unshift vs array_push
Basically, the solution suggested by @tarnus, above, adds a submit callback after all the existing ones:

$form['actions'][$action]['#submit'][] = 'mymodule_node_form_submit';

which pretty much corresponds to an array_push, which pushes the passed callback onto the end of the stack (running the callback after all others).

Using array_unshift instead prepends passed callback to the front of the array. In other words, the added callback runs before all others, including Core's, which takes care of saving the entity.

array_unshift($form['actions'][$action]['#submit'], 'mymodule_node_form_submit');

The idea is similar to what was suggested above by @marcoscano: Altering form's or entity's values before they are saved through Core's regular process/flow.

2 - Saving the form/entity in the submit callback
Sometimes, there's no choice and the custom callback has to run after all others, in which case array_push ($array[] = $var) has to be used.
In this case, the form/entity has already been saved (through Core's submit callbacks), thus leaving the saving process at our charge:

Here is an example of how things could potentially be handled:

function mymodule_[SOME_ENTITY]_form_submit($form, FormStateInterface $form_state) {
  // Get the submitted entity.
  if ($entity = $form_state->getFormObject()->getEntity()) {
    $entity->set('MY_FIELD_NAME', $value);
    $entity->save();
  }
}

Now, watchout how the values have to be set for entity's values, since the format is most likely different from the one used for the fields in the form.
 

I hope this comment will help answering some of your questions and further complete the initial post.

All this is still kind of new for me as well and I would be very happy to hear your feedbacks, comments or ideas on what could be a more ideal approach for this kind of problems.

Thanks in advance to everyone for your testing, reviews, reporting and feedback.
Many thanks to @tarnus for getting the ball rolling on this ;-)

sumanthkumarc’s picture

Does the core submit handler doesn't create another node since it will also save the entity??

Chetna_Negi’s picture

I was facing same issue, this helped me.
Thanks :)

arunkumark’s picture

Hi,

Currently using Drupal 8.1, in this version FormStateInterface no longer exist, instead of this we need to use \Drupal\Core\Form\FormStateInterface.

So am changing Form submit handler as,

// Implements HOOK_form_FORM_ID_alter
function example_form_FORM_ID_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
    $form['actions']['submit']['#submit'][]  = 'mymodule_form_submit_handler';
}

function example_form_submit_handler($form, \Drupal\Core\Form\FormStateInterface $form_state) {
  // Do your stuff
  print_r(form_state);
}
rabithk’s picture

I am able to get submitted values inside submit handler , only issue was I want to update any field value after a form submit .
But the entity save inside submit handler is working

$entity = $form_state->getFormObject()->getEntity()) {
$entity->set('MY_FIELD_NAME', $value);
$entity->save();

littlethoughts’s picture

I assume in OP's code the FormStateInterface's class was imported with a "use" statement.

It is not like the FormStateInterface doesn't exist anymore, but you can leave out the full namespace if you import it beforehand.

gaetanpralong’s picture

Yes, the class FormStateInterface still exists for any versions of Drupal 8 (thing is you ARE using it in your code), but since it lives in the \Drupal\Core\Form\ namespace, if you want to use it simply by its name, you need to declare the use of this particular class in its particular namespace at the beginning of your script.

// Declares the use of the class FormStateInterface in its full namespace.
use Drupal\Core\Form\FormStateInterface;

// Then you can use the class without its namespace, PHP will know of which class you are talking about.
function example_form_submit_handler($form, FormStateInterface $form_state) {
  print_r(form_state);
}

Hope it'll save you some time next time ;-)

dsteplight’s picture

In your example you have $form['actions']['submit']['#submit'][] = 'mymodule_form_submit_handler'; then have function example_form_submit_handler down below. This may be confusing to other and won't work, but I see what you meant to write. It should read " $form['actions']['submit']['#submit'][] = 'example_form_submit_handler'; ".

So if someone has a module named "FOO" and their form id is "apples_and_oranges_form" the the HOOK_form_FORM_ID_alter should read:

function foo_form_apples_and_oranges_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state)
{
.....your code
$form['actions']['submit']['#submit'][] = 'name_this_something_its_a_custom_function';
}

AND

function name_this_something_its_a_custom_function($form, \Drupal\Core\Form\FormStateInterface $form_state) {
//note: do yourself a favor and insert the devel module so you can use the kint method for pretty arrays.
kint(Rform_state);
}

Most importantly...i'm using Drupal 8.2.x and I had to use $form['#submit'][] = 'my_custom_function' vs $form['actions']['submit']['#submit'][] like in this example.

sumanthkumarc’s picture

When a node is submitted , i had to create multiple nodes depending on the values set in form. The code above(one by @DYdave) was straight cut answer for what i was searching.

khurrami’s picture

1) add following line at the top of your module
use Drupal\Core\Form\FormStateInterface;

2) add the following submit handler & custom function
a) Submit handler:-

function mymodule_mynodeformid_form_alter(&$form, FormStateInterface $form_state) {
    if($form['#form_id'] == 'mynodeformid') {
      foreach (array_keys($form['actions']) as $action) {
        if ($action != 'preview' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') {
          $form['actions']['submit']['#submit'][] = 'mymodule_node_form_submit';
      }
    }
  }
}

b) Custom function:-

function mymodule_node_form_submit($form, FormStateInterface $form_state) {
  drupal_set_message('I am here');
}
sumanthkumarc’s picture

There are chances you might get entity validation skipped error if you are using the button level handlers above.

Then maybe you can call validate method on entity in your handler or write form level handler.

Jaypan’s picture

All validation handlers are executed before any submission handlers are executed, so adding button level handlers will not cause any validation issues.

sumanthkumarc’s picture

foreach (array_keys($form['actions']) as $action) {
        if ($action != 'preview' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') {
          $form['actions']['submit']['#submit'][] = 'mymodule_node_form_submit';
          $form['actions']['submit']['#validate'][] = 'mymodule_node_form_validate';
}
}
Jaypan’s picture

**Edit** - the comment below is incorrect for Drupal 8.

In Drupal 6 and Drupal 7, #validate attached to a submit button is never called - only #element_validate attached to elements, and/or #validate attached to the root of the form are called. I haven't checked if it's changed in D8, but it probably hasn't, so I suspect that validate handler will never be called, and it has nothing to do with having added a submit handler to the button.

sumanthkumarc’s picture

The validation handlers added the above way are working in my case in D8.
Check this link out: https://www.drupal.org/node/2453175

Jaypan’s picture

You're right, I just did a test, and #validate does work on a submit button.

Now that that's straightened out, what's your question?

sumanthkumarc’s picture

When i add a validate handler to button, i'm getting a entity validation skipped error. If i shift that handler to form, the error is gone. I donno if this is the only reason, but just happened to get that.

Jaypan’s picture

That's the same as submit handlers attached to buttons. If/when a submit (and apparently validation) handler is attached to the button, the submit (and apparently validation) handlers attached to the form are not called. This is why you are seeing the issue that you have.

You have two options:

1) Also attach the default validation handler(s) to the button, or:
2) Add your validation handler to the form (which you already discovered)

sumanthkumarc’s picture

Thanks Jaypan.

dzy’s picture

I try to upgrade module to d8,
i used hook_form_alter() to hide mail field from register form, so user don't need email when they create new account. i want function automatic give new user fake email according the register name, otherwise there are error about "email field is required".

i added custom validation by

$form['account']['mail']['#required'] = FALSE;
$form['account']['mail']['#type'] = 'hidden';
array_unshift($form['#validate'], '_mymodule_validation2'); 

how to make _mymodule_validation2 function called as first validation function? this is the place i modify mail value.
my d7 module is working but in d8 is not.

priyanka12chavan’s picture

The code I tried:

my_theme_form_system_theme_settings_alter(&$form, Drupal\Core\Form\FormStateInterface $form_state) {
foreach (array_keys($form['actions']) as $action) {
      if ($action != 'preview' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') {
        $form['actions'][$action]['#submit'][] = 'my_theme_system_theme_settings_form_submit';
      }
    }
}

function my_theme_form_system_theme_settings_submit(&$form, Drupal\Core\Form\FormStateInterface $form_state){
    if(!empty($form_state->getValue('mainlogo'))){
    	$mainlogo = $form_state->getValue('mainlogo');
	   	$file = \Drupal\file\Entity\File::load($mainlogo[0]);
	   	$file->setPermanent();
	  	$file->save();
    }
}

But still it gives me error "Warning: call_user_func_array() expects parameter 1 to be a valid callback, function 'my_theme_form_system_theme_settings_submit' not found or invalid "

Using Drupal 8.2.5, Am I missing something here?

Jaypan’s picture

It's saying that my_theme_form_system_theme_settings_submit() cannot be found. In what file is this code located? It needs to be in the .module file, or the file in which it is located needs to be manually loaded ahead of time.

priyanka12chavan’s picture

It's in theme-settings.php

Jaypan’s picture

You'll need to move it to your .module file, or else call form_load_include() in your hook_form_alter() implementation, and include theme-settings.php there.

anthonyf’s picture

if using hook_form_BASE_FORM_ID_alter in Drupal 8.4.3, I found the ways of adding the custom submit handler listed above didn't work. This worked for adding the submit handler function name:

$form['#submit'][] = 'mymodule_submit_handler';