diff --git a/core/lib/Drupal/Core/Form/FormValidator.php b/core/lib/Drupal/Core/Form/FormValidator.php index dbefe00a846d1b96a532a5300626cd90797e38ca..d4f3ee564014182499ac7e06c125fe32c281d754 100644 --- a/core/lib/Drupal/Core/Form/FormValidator.php +++ b/core/lib/Drupal/Core/Form/FormValidator.php @@ -229,8 +229,12 @@ protected function finalizeValidation(&$form, FormStateInterface &$form_state, $ * theming, and hook_form_alter functions. */ protected function doValidateForm(&$elements, FormStateInterface &$form_state, $form_id = NULL) { - // Recurse through all children. - foreach (Element::children($elements) as $key) { + // Recurse through all children, sorting the elements so that the order of + // error messages displayed to the user matches the order of elements in + // the form. Use a copy of $elements so that it is not modified by the + // sorting itself. + $elements_sorted = $elements; + foreach (Element::children($elements_sorted, TRUE) as $key) { if (isset($elements[$key]) && $elements[$key]) { $this->doValidateForm($elements[$key], $form_state); } diff --git a/core/tests/Drupal/KernelTests/Core/Form/FormValidationMessageOrderTest.php b/core/tests/Drupal/KernelTests/Core/Form/FormValidationMessageOrderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..33ff22b02e706c6b6b239b2d71f86740fc438660 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Form/FormValidationMessageOrderTest.php @@ -0,0 +1,92 @@ +<?php + +namespace Drupal\KernelTests\Core\Form; + +use Drupal\Core\Form\FormInterface; +use Drupal\Core\Form\FormState; +use Drupal\Core\Form\FormStateInterface; +use Drupal\KernelTests\KernelTestBase; + +/** + * Tests form validation mesages are displayed in the same order as the fields. + * + * @group Form + */ +class FormValidationMessageOrderTest extends KernelTestBase implements FormInterface { + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'form_validation_error_message_order_test'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + // Prepare fields with weights specified. + $form['one'] = [ + '#type' => 'textfield', + '#title' => 'One', + '#required' => TRUE, + '#weight' => 40, + ]; + $form['two'] = [ + '#type' => 'textfield', + '#title' => 'Two', + '#required' => TRUE, + '#weight' => 30, + ]; + $form['three'] = [ + '#type' => 'textfield', + '#title' => 'Three', + '#required' => TRUE, + '#weight' => 10, + ]; + $form['four'] = [ + '#type' => 'textfield', + '#title' => 'Four', + '#required' => TRUE, + '#weight' => 20, + ]; + $form['actions'] = [ + '#type' => 'actions', + 'submit' => [ + '#type' => 'submit', + '#value' => 'Submit', + ], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + } + + /** + * Tests that fields validation messages are sorted in the fields order. + */ + function testLimitValidationErrors() { + $form_state = new FormState(); + $form_builder = $this->container->get('form_builder'); + $form_builder->submitForm($this, $form_state); + + $messages = drupal_get_messages(); + $this->assertTrue(isset($messages['error'])); + $error_messages = $messages['error']; + $this->assertEqual($error_messages[0], 'Three field is required.'); + $this->assertEqual($error_messages[1], 'Four field is required.'); + $this->assertEqual($error_messages[2], 'Two field is required.'); + $this->assertEqual($error_messages[3], 'One field is required.'); + } + +}