Version 1
[yaffs-website] / web / core / lib / Drupal / Core / Form / FormErrorHandler.php
1 <?php
2
3 namespace Drupal\Core\Form;
4
5 use Drupal\Component\Utility\NestedArray;
6 use Drupal\Core\Render\Element;
7
8 /**
9  * Handles form errors.
10  */
11 class FormErrorHandler implements FormErrorHandlerInterface {
12
13   /**
14    * {@inheritdoc}
15    */
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);
21
22       // Loop through and assign each element its errors.
23       $this->setElementErrorsFromFormState($form, $form_state);
24     }
25
26     return $this;
27   }
28
29   /**
30    * Loops through and displays all form errors.
31    *
32    * @param array $form
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.
36    */
37   protected function displayErrorMessages(array $form, FormStateInterface $form_state) {
38     $errors = $form_state->getErrors();
39
40     // Loop through all form errors and set an error message.
41     foreach ($errors as $error) {
42       $this->drupalSetMessage($error, 'error');
43     }
44   }
45
46   /**
47    * Stores errors and a list of child element errors directly on each element.
48    *
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.
53    *
54    * Grouping example:
55    * Assume you have a 'street' element somewhere in a form, which is displayed
56    * in a details element 'address'. It might be:
57    * @code
58    * $form['street'] = [
59    *   '#type' => 'textfield',
60    *   '#title' => $this->t('Street'),
61    *   '#group' => 'address',
62    *   '#required' => TRUE,
63    * ];
64    * $form['address'] = [
65    *   '#type' => 'details',
66    *   '#title' => $this->t('Address'),
67    * ];
68    * @endcode
69    *
70    * When submitting an empty street field, the generated error is available to
71    * the different render elements like so:
72    * @code
73    * // The street textfield element.
74    * $element = [
75    *   '#errors' => {Drupal\Core\StringTranslation\TranslatableMarkup},
76    *   '#children_errors' => [],
77    * ];
78    * // The address detail element.
79    * $element = [
80    *   '#errors' => null,
81    *   '#children_errors' => [
82    *      'street' => {Drupal\Core\StringTranslation\TranslatableMarkup}
83    *   ],
84    * ];
85    * @endcode
86    *
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'.
92    *
93    * @param array $form
94    *   An associative array containing a reference to the complete structure of
95    *   the form.
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.
102    */
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)) {
109       $elements = &$form;
110     }
111
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];
117
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);
121
122         $children_errors = [];
123
124         // Inherit all recorded "children errors" of the direct child.
125         if (!empty($child['#children_errors'])) {
126           $children_errors = $child['#children_errors'];
127         }
128
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'];
134         }
135
136         if (!empty($elements['#children_errors'])) {
137           $elements['#children_errors'] += $children_errors;
138         }
139         else {
140           $elements['#children_errors'] = $children_errors;
141         }
142
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;
150           }
151           else {
152             $group_element['#children_errors'] = $children_errors;
153           }
154           NestedArray::setValue($form, $parents, $group_element);
155         }
156       }
157     }
158
159     // Store the errors for this element on the element directly.
160     $elements['#errors'] = $form_state->getError($elements);
161   }
162
163   /**
164    * Wraps drupal_set_message().
165    *
166    * @codeCoverageIgnore
167    */
168   protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
169     drupal_set_message($message, $type, $repeat);
170   }
171
172 }