6

I have a multistep form and on each submit I send completed fields off to an external API which returns data about the next step of the form.

The problem is, if I use form_set_error in the submit handler, the form_storage is cleared and so the form resets to the first page.

Here's a arbitrary example;

function example_form($form, &$form_state) {
  if(!isset($form_state['step'])){
    $form_state['step'] = 1;
  }
  $form['test'] = array('#markup' => '<p>Step: ' . $form_state['step'] . '</p>');

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => 'go'
  );
  return $form;
}

function example_form_submit($form, &$form_state) {
  $form_state['rebuild'] = TRUE;
  if($form_state['step'] > 5){
    form_set_error('test', 'error');
    return;
  }
  $form_state['step']++;
}

So this create a form with just a text block displaying the step number and a submit button that takes you to the next step. Until you go past the 5th step at which point it should start to error.

For some reason in my testing this breaks the step count, and on error takes you back to step 2.

moving the form_set_error into the form callback, or into a validate callback fixes the issue, but it would be cleaner if I could call it in the submit handler.

4 Answers 4

5

In case it's useful to anyone else, I have a unsatisfactory work around;

Store the error in form_state, and look for them in the form callback.

function example_form($form, &$form_state) {
  // if we had an error in the submit handler, trigger it now
  if(isset($form_state['error']) && $form_state['error']){
    form_set_error('test', 'test');
  }
  if(!isset($form_state['step'])){
    $form_state['step'] = 1;
  }
  $form['test'] = array('#markup' => '<p>Step: ' . $form_state['step'] . '</p>');

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => 'go'
  );
  return $form;
}


function example_submit($form, &$form_state) {

  $form_state['rebuild'] = TRUE;
  if($form_state['step'] > 5){
    //store error and return before the step is increased
    $form_state['error'] = true;
    return;
  }


  $form_state['step']++;
}
3
  • 1
    I would call that pretty satisfactory to be honest. form_set_error() is never supposed to be used in a submit handler (or a form function itself) so passing data back over the $form_state would definitely be the 'Drupal' way to handle this
    – Clive
    Apr 17, 2013 at 12:02
  • 2
    Thanks Clive, that make me feel better about using it :)
    – Simon
    Apr 17, 2013 at 12:04
  • This is great, it was very useful to me in a similar case, and I do think it's a pretty elegant way of solving the problem.
    – cambraca
    Apr 22, 2013 at 16:18
3

form_set_error() isn't supposed to be used in a submit handler; it's part of the validation workflow. From the docs:

When a validation error is detected, the validator calls form_set_error() to indicate which element needs to be changed and provide an error message. This causes the Form API to not execute the form submit handlers, and instead to re-display the form to the user with the corresponding elements rendered with an 'error' CSS class (shown as red by default).

Emphasis mine.

The point is to use form_set_error() to stop the process from ever reaching the submit handler.

Don't forget, you're validating whether or not the current step is greater than 5. That logic needs to happen in a validation handler. :)

10
  • 1
    I understand that, but my problem is that it makes sense for this to happen in the submit handler. I have an API call that happens in the submit handler, if the API call fails, or returns an error, then I want to be able to set an error on the form and stay at the current step. I don't want to move the API call to a validation handler, because if successful the API call is a write action, so belongs at the submit stage. Also keeping it in the submit means other validators can prevent the API call from happening (eg honeypot).
    – Simon
    Apr 17, 2013 at 11:46
  • No, because all validators will run. So if my API call is in a validator it will always run, even if honeypot is triggered. An error message will display but the data will still be send to the API and stored, defeating the point of honeypot.
    – Simon
    Apr 17, 2013 at 11:48
  • Sorry yeah I read your comment too quickly. I'm not sure what to say I'm afraid; what you're doing is validation-specific logic and only makes sense to be in a validate handler to me. Look at the core image upload as an example - The expensive work (uploading and validating the file) is done in a validation handler, regardless of whether other validation handlers may run afterwards or not. Then the result of that validation is passed on to the submit handler. It mirrors your use-case quite closely
    – Clive
    Apr 17, 2013 at 11:49
  • 1
    It's just a catch 22 in my case, since the point of validators is to stop a write action occuring, moving my API call into the validator isn't feasible. Thanks for your help Clive.
    – Simon
    Apr 17, 2013 at 12:03
  • 1
    @Simon You could always implement hook_module_implements_order() to guarantee your own module's hooks will run last, but yeah it can get pretty messy like you say
    – Clive
    Apr 23, 2013 at 13:16
0
function example_form_validate($form, &$form_state) {
}

Use this function to validate your data when submitting a form

5
  • As I said to Clive; My problem is that it makes sense for the error to be triggered in the submit handler. I have an API call that happens in the submit handler, if the API call fails, or returns an error, then I want to be able to set an error on the form and stay at the current step. I don't want to move the API call to a validation handler, because if successful the API call is a write action, so belongs at the submit stage. Also keeping it in the submit means other validators can prevent the API call from happening (eg honeypot).
    – Simon
    Apr 17, 2013 at 11:51
  • @Simon unfortunately you're going to have to work around Drupal then (maybe patch the form submission process), as the form workflow isn't set up to handle such edge cases
    – Clive
    Apr 17, 2013 at 11:54
  • 1
    Then you need to rebuild the current form in submit function. Then only you can use that form state again.
    – Sibiraj PR
    Apr 17, 2013 at 11:54
  • Take a look at the more advanced examples in the Form Example in the drupal.org/project/examples.
    – Sibiraj PR
    Apr 17, 2013 at 11:55
  • As you can see in my example, I am rebuilding the form, but using form_set_error in the submit handler breaks this.
    – Simon
    Apr 17, 2013 at 11:59
0

form set error will clear your input, so do not use that function here. Instead, use the drupal_set_message. It is not perfect, but it will work.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.