5

I am writing a form in Drupal 8 that provides user values to a web service, which will either act on them or send back an error.

How can I display that error message to the user?

Here's the situation in code:

class FooForm extends BarForm implements FormInterface {

  protected $fooClient;

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state {

    $result = $this->fooClient->post($form_state->getValue('bar'));

    if ($result->hasProblems()) {
      // This doesn't seem correct: Posting to a web service is not idempotent.  
      $form_state->setErrorByName('bar', $this->t('Problems.'));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {

    $result = $this->fooClient->post($form_state->getValue('bar'));

    if ($result->hasProblems()) {
      // This throws a LogicException.
      $form_state->setErrorByName('bar', $this->t('Problems.'));
    }
  }
}

Drupal treats 'validate' and 'submit' as two separate steps, but the web service doesn't. The only way to determine if the values are fully 'valid' is to get the web service to look at them, which is an operation that potentially has side effects.

validateForm() and other form validation handlers are not supposed to contain business logic, since they may get called multiple times during the life of the form, particularly if AJAX is involved.

However, all errors are meant to be resolved before submitForm() and any other submission handlers are called. FormState::setError() enforces this by throwing a LogicException if you try to create any. Also, submitForm() clears away the user's values, and the next page builds the form from scratch.

I've seen this similar question for Drupal 7. Back then, it was possible (if discouraged and ugly) to use form_set_error() in the submit handler or the form builder. As far as I can tell, that flat-out doesn't work in Drupal 8.

How am I supposed to interact with RESTful web services? Is there a way to pass errors up from a form submit handler? Or is there a way to ensure a validation handler runs once, only once, and only if all other validation passes? Or is there some way to do this with multi-step forms?

2
  • 1
    Add multiple validation handlers. Submit handler has nothing to do with validation(hence the name). $form['#validate'] = ['validateSomething', 'validateSomethingElse', 'validateSomeCompletelyDifferentThing'];
    – user21641
    Mar 24, 2016 at 7:20
  • I'm aware that I can add a validation handler that just contacts the web service. However, it's still a validation handler: It's not supposed to contain business logic or have side effects. Contacting the web service might result in an action on their side. I've edited the question, I hope it's clearer now?
    – Sean C.
    Mar 24, 2016 at 7:44

2 Answers 2

8

The Drupal form system, isn't really built for what you are trying to do. I see two ways of going about this:

  1. Webservice validation

    If the webservice can do a validation of the data or such a method can be added, you can use the form system as is, and use ::validateForm to validate via webservice.

  2. Rebuild form

    What you can do, instead of doing a "validation" is to delete your ::validateForm method entirely and just send the data to the webservice in the ::submitForm. If the operation fails, you can rebuild the form and display a message to the user. You can make it appear as a validation error even though it technically isn't. Drupal does something like this as well. Fx in NodeForm::save

    public function save(array $form, FormStateInterface $form_state) {
      ...
      $node->save();
      ...
      if ($node->id()) {
        ...
      else {
        // In the unlikely case something went wrong on save, the node will be
        // rebuilt and node form redisplayed the same way as in preview.
        drupal_set_message(t('The post could not be saved.'), 'error');
        $form_state->setRebuild();
      }
    }
    

    So basically, you can use $form_state->setRebuild() and drupal_set_message to make something that mimics validation in the submit handler. You can also save data in the store ($form_state->setStorate($data)) which you can use the form method to display error message or something else to the user.

0

@googletorp answer works well. I just want to add one more thing: you cannot use setError() in buildForm(), therefore, in order to visually indicate which element has problem, you want to set

$form['THE_PROBLEM_ELEMENT']['#attributes']['class'][] = 'error'; 

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.