3 namespace Drupal\Core\Form;
5 use Drupal\Component\Utility\NestedArray;
6 use Drupal\Core\Render\Element;
11 class FormErrorHandler implements FormErrorHandlerInterface {
16 public function handleFormErrors(array &$form, FormStateInterface $form_state) {
17 // After validation check if there are errors.
18 if ($errors = $form_state->getErrors()) {
19 // Display error messages for each element.
20 $this->displayErrorMessages($form, $form_state);
22 // Loop through and assign each element its errors.
23 $this->setElementErrorsFromFormState($form, $form_state);
30 * Loops through and displays all form errors.
33 * An associative array containing the structure of the form.
34 * @param \Drupal\Core\Form\FormStateInterface $form_state
35 * The current state of the form.
37 protected function displayErrorMessages(array $form, FormStateInterface $form_state) {
38 $errors = $form_state->getErrors();
40 // Loop through all form errors and set an error message.
41 foreach ($errors as $error) {
42 $this->drupalSetMessage($error, 'error');
47 * Stores errors and a list of child element errors directly on each element.
49 * Grouping elements like containers, details, fieldgroups and fieldsets may
50 * need error info from their child elements to be able to accessibly show
51 * form error messages to a user. For example, a details element should be
52 * opened when child elements have errors.
55 * Assume you have a 'street' element somewhere in a form, which is displayed
56 * in a details element 'address'. It might be:
59 * '#type' => 'textfield',
60 * '#title' => $this->t('Street'),
61 * '#group' => 'address',
62 * '#required' => TRUE,
64 * $form['address'] = [
65 * '#type' => 'details',
66 * '#title' => $this->t('Address'),
70 * When submitting an empty street field, the generated error is available to
71 * the different render elements like so:
73 * // The street textfield element.
75 * '#errors' => {Drupal\Core\StringTranslation\TranslatableMarkup},
76 * '#children_errors' => [],
78 * // The address detail element.
81 * '#children_errors' => [
82 * 'street' => {Drupal\Core\StringTranslation\TranslatableMarkup}
87 * The list of child element errors of an element is an associative array. A
88 * child element error is keyed with the #array_parents value of the
89 * respective element. The key is formed by imploding this value with '][' as
90 * glue. For example, a value ['contact_info', 'name'] becomes
91 * 'contact_info][name'.
94 * An associative array containing a reference to the complete structure of
96 * @param \Drupal\Core\Form\FormStateInterface $form_state
97 * The current state of the form.
98 * @param array $elements
99 * An associative array containing the part of the form structure that will
100 * be processed while traversing up the tree. For recursion only; leave
101 * empty when calling this method.
103 protected function setElementErrorsFromFormState(array &$form, FormStateInterface $form_state, array &$elements = []) {
104 // At the start of traversing up the form tree set the to be processed
105 // elements to the complete form structure by reference so that we can
106 // modify the original form. When processing grouped elements a reference to
107 // the complete form is needed.
108 if (empty($elements)) {
112 // Recurse through all element children.
113 foreach (Element::children($elements) as $key) {
114 if (!empty($elements[$key])) {
115 // Get the child by reference so that we can update the original form.
116 $child = &$elements[$key];
118 // Call self to traverse up the form tree. The current element's child
119 // contains the next elements to be processed.
120 $this->setElementErrorsFromFormState($form, $form_state, $child);
122 $children_errors = [];
124 // Inherit all recorded "children errors" of the direct child.
125 if (!empty($child['#children_errors'])) {
126 $children_errors = $child['#children_errors'];
129 // Additionally store the errors of the direct child itself, keyed by
130 // it's parent elements structure.
131 if (!empty($child['#errors'])) {
132 $child_parents = implode('][', $child['#array_parents']);
133 $children_errors[$child_parents] = $child['#errors'];
136 if (!empty($elements['#children_errors'])) {
137 $elements['#children_errors'] += $children_errors;
140 $elements['#children_errors'] = $children_errors;
143 // If this direct child belongs to a group populate the grouping element
144 // with the children errors.
145 if (!empty($child['#group'])) {
146 $parents = explode('][', $child['#group']);
147 $group_element = NestedArray::getValue($form, $parents);
148 if (isset($group_element['#children_errors'])) {
149 $group_element['#children_errors'] = $group_element['#children_errors'] + $children_errors;
152 $group_element['#children_errors'] = $children_errors;
154 NestedArray::setValue($form, $parents, $group_element);
159 // Store the errors for this element on the element directly.
160 $elements['#errors'] = $form_state->getError($elements);
164 * Wraps drupal_set_message().
166 * @codeCoverageIgnore
168 protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
169 drupal_set_message($message, $type, $repeat);