diff --git a/core/lib/Drupal/Core/Render/Element/MachineName.php b/core/lib/Drupal/Core/Render/Element/MachineName.php index 9e0532646861ca54931f8095ff5ef41760f10075..555a044c0e29f56cfd4db0d896141e179a44d954 100644 --- a/core/lib/Drupal/Core/Render/Element/MachineName.php +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php @@ -44,7 +44,8 @@ * - error: (optional) A custom form error message string to show, if the * machine name contains disallowed characters. * - standalone: (optional) Whether the live preview should stay in its own - * form element rather than in the suffix of the source element. Defaults + * form element rather than in the suffix of the source element. The source + * element must appear in the form structure before this element. Defaults * to FALSE. * - #maxlength: (optional) Maximum allowed length of the machine name. Defaults * to 64. @@ -183,6 +184,13 @@ public static function processMachineName(&$element, FormStateInterface $form_st return $element; } + // The source element must be defined before the machine name element. + if (!isset($source['#id'])) { + $element_parents = implode('][', $element['#array_parents']); + $source_parents = implode('][', $element['#machine_name']['source']); + throw new \LogicException(sprintf('The machine name element "%s" is defined before the source element "%s", it must be defined after or the source element must specify an id.', $element_parents, $source_parents)); + } + $suffix_id = $source['#id'] . '-machine-name-suffix'; $element['#machine_name']['suffix'] = '#' . $suffix_id; diff --git a/core/tests/Drupal/KernelTests/Core/Render/Element/MachineNameTest.php b/core/tests/Drupal/KernelTests/Core/Render/Element/MachineNameTest.php new file mode 100644 index 0000000000000000000000000000000000000000..74c3e95a963b5b4794f16e6c651050deefa62d28 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Render/Element/MachineNameTest.php @@ -0,0 +1,74 @@ +<?php + +namespace Drupal\KernelTests\Core\Render\Element; + +use Drupal\Core\Form\FormInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\KernelTests\KernelTestBase; + +/** + * @coversDefaultClass \Drupal\Core\Render\Element\MachineName + * @group Render + */ +class MachineNameTest extends KernelTestBase implements FormInterface { + + /** + * {@inheritdoc} + */ + protected static $modules = ['system']; + + /** + * {@inheritdoc} + */ + public function getFormId() { + return __CLASS__; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $element = [ + '#id' => 'test', + '#type' => 'machine_name', + '#machine_name' => [ + 'source' => [ + 'test_source', + ], + ], + '#name' => 'test_machine_name', + '#default_value' => NULL, + ]; + + $complete_form = [ + 'test_machine_name' => $element, + 'test_source' => [ + '#type' => 'textfield', + ], + ]; + return $complete_form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + } + + /** + * Tests the order of the machine name field and the source. + */ + public function testMachineNameOrderException() { + $this->expectException(\LogicException::class); + $this->expectErrorMessage('The machine name element "test_machine_name" is defined before the source element "test_source", it must be defined after or the source element must specify an id.'); + $form = \Drupal::formBuilder()->getForm($this); + $this->render($form); + } + +}