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?