Commit f710a6c9 authored by alexpott's avatar alexpott

Issue #2209977 by tim.plunkett: Move form validation logic out of FormBuilder into a new class.

parent 02eb3d3f
......@@ -126,7 +126,10 @@ services:
arguments: [default]
form_builder:
class: Drupal\Core\Form\FormBuilder
arguments: ['@module_handler', '@keyvalue.expirable', '@event_dispatcher', '@url_generator', '@string_translation', '@request_stack', '@?csrf_token', '@?http_kernel']
arguments: ['@form_validator', '@module_handler', '@keyvalue.expirable', '@event_dispatcher', '@url_generator', '@request_stack', '@?csrf_token', '@?http_kernel']
form_validator:
class: Drupal\Core\Form\FormValidator
arguments: ['@request_stack', '@string_translation', '@csrf_token']
keyvalue:
class: Drupal\Core\KeyValueStore\KeyValueFactory
arguments: ['@service_container', '@settings']
......
......@@ -11,6 +11,7 @@
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Database\Database;
use Drupal\Core\Form\OptGroup;
use Drupal\Core\Language\Language;
use Drupal\Core\Render\Element;
use Drupal\Core\Template\Attribute;
......@@ -323,7 +324,7 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::formBuilder()->validateForm().
*
* @see \Drupal\Core\Form\FormBuilderInterface::validateForm().
* @see \Drupal\Core\Form\FormValidatorInterface::validateForm().
*/
function drupal_validate_form($form_id, &$form, &$form_state) {
\Drupal::formBuilder()->validateForm($form_id, $form, $form_state);
......@@ -345,12 +346,19 @@ function drupal_redirect_form($form_state) {
* Executes custom validation and submission handlers for a given form.
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::formBuilder()->executeHandlers().
* Use either \Drupal::formBuilder()->executeSubmitHandlers() or
* \Drupal::service('form_validator')->executeValidateHandlers().
*
* @see \Drupal\Core\Form\FormBuilderInterface::executeHandlers().
* @see \Drupal\Core\Form\FormBuilderInterface::executeSubmitHandlers()
* @see \Drupal\Core\Form\FormValidatorInterface::executeValidateHandlers()
*/
function form_execute_handlers($type, &$form, &$form_state) {
\Drupal::formBuilder()->executeHandlers($type, $form, $form_state);
if ($type == 'submit') {
\Drupal::formBuilder()->executeSubmitHandlers($form, $form_state);
}
elseif ($type == 'validate') {
\Drupal::service('form_validator')->executeValidateHandlers($form, $form_state);
}
}
/**
......@@ -359,7 +367,7 @@ function form_execute_handlers($type, &$form, &$form_state) {
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::formBuilder()->setErrorByName().
*
* @see \Drupal\Core\Form\FormBuilderInterface::setErrorByName().
* @see \Drupal\Core\Form\FormErrorInterface::setErrorByName().
*/
function form_set_error($name, array &$form_state, $message = '') {
\Drupal::formBuilder()->setErrorByName($name, $form_state, $message);
......@@ -383,7 +391,7 @@ function form_clear_error(array &$form_state) {
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::formBuilder()->getErrors().
*
* @see \Drupal\Core\Form\FormBuilderInterface::getErrors().
* @see \Drupal\Core\Form\FormErrorInterface::getErrors()
*/
function form_get_errors(array &$form_state) {
return \Drupal::formBuilder()->getErrors($form_state);
......@@ -395,7 +403,7 @@ function form_get_errors(array &$form_state) {
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::formBuilder()->getError().
*
* @see \Drupal\Core\Form\FormBuilderInterface::getError().
* @see \Drupal\Core\Form\FormErrorInterface::getError().
*/
function form_get_error($element, array &$form_state) {
return \Drupal::formBuilder()->getError($element, $form_state);
......@@ -407,7 +415,7 @@ function form_get_error($element, array &$form_state) {
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::formBuilder()->setError().
*
* @see \Drupal\Core\Form\FormBuilderInterface::setError().
* @see \Drupal\Core\Form\FormErrorInterface::setError().
*/
function form_error(&$element, array &$form_state, $message = '') {
\Drupal::formBuilder()->setError($element, $form_state, $message);
......@@ -844,12 +852,10 @@ function form_set_value($element, $value, &$form_state) {
* An array with all hierarchical elements flattened to a single array.
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::formBuilder()->flattenOptions().
*
* @see \Drupal\Core\Form\FormBuilderInterface::flattenOptions().
* Use \Drupal\Core\Form\OptGroup::flattenOptions().
*/
function form_options_flatten($array) {
return \Drupal::formBuilder()->flattenOptions($array);
return OptGroup::flattenOptions($array);
}
/**
......
This diff is collapsed.
......@@ -7,8 +7,6 @@
namespace Drupal\Core\Form;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides an interface for form building and processing.
*/
......@@ -387,33 +385,6 @@ public function processForm($form_id, &$form, &$form_state);
*/
public function prepareForm($form_id, &$form, &$form_state);
/**
* Validates user-submitted form data in the $form_state array.
*
* @param $form_id
* A unique string identifying the form for validation, submission,
* theming, and hook_form_alter functions.
* @param $form
* An associative array containing the structure of the form, which is
* passed by reference. Form validation handlers are able to alter the form
* structure (like #process and #after_build callbacks during form building)
* in case of a validation error. If a validation handler alters the form
* structure, it is responsible for validating the values of changed form
* elements in $form_state['values'] to prevent form submit handlers from
* receiving unvalidated values.
* @param $form_state
* A keyed array containing the current state of the form. The current
* user-submitted data is stored in $form_state['values'], though
* form validation functions are passed an explicit copy of the
* values for the sake of simplicity. Validation handlers can also use
* $form_state to pass information on to submit handlers. For example:
* $form_state['data_for_submission'] = $data;
* This technique is useful when validation requires file parsing,
* web service requests, or other expensive requests that should
* not be repeated in the submission step.
*/
public function validateForm($form_id, &$form, &$form_state);
/**
* Redirects the user to a URL after a form has been processed.
*
......@@ -477,14 +448,11 @@ public function validateForm($form_id, &$form, &$form_state);
public function redirectForm($form_state);
/**
* Executes custom validation and submission handlers for a given form.
* Executes custom submission handlers for a given form.
*
* Button-specific handlers are checked first. If none exist, the function
* falls back to form-level handlers.
*
* @param $type
* The type of handler to execute. 'validate' or 'submit' are the
* defaults used by Form API.
* @param $form
* An associative array containing the structure of the form.
* @param $form_state
......@@ -492,7 +460,7 @@ public function redirectForm($form_state);
* submitted the form by clicking a button with custom handler functions
* defined, those handlers will be stored here.
*/
public function executeHandlers($type, &$form, &$form_state);
public function executeSubmitHandlers(&$form, &$form_state);
/**
* Builds and processes all elements in the structured form array.
......@@ -620,19 +588,4 @@ public function doBuildForm($form_id, &$element, &$form_state);
*/
public function setValue($element, $value, &$form_state);
/**
* Allows PHP array processing of multiple select options with the same value.
*
* Used for form select elements which need to validate HTML option groups
* and multiple options which may return the same value. Associative PHP
* arrays cannot handle these structures, since they share a common key.
*
* @param array $array
* The form options array to process.
*
* @return array
* An array with all hierarchical elements flattened to a single array.
*/
public function flattenOptions(array $array);
}
This diff is collapsed.
<?php
/**
* @file
* Contains \Drupal\Core\Form\FormValidatorInterface.
*/
namespace Drupal\Core\Form;
/**
* Provides an interface for validating form submissions.
*/
interface FormValidatorInterface extends FormErrorInterface {
/**
* Executes custom validation handlers for a given form.
*
* Button-specific handlers are checked first. If none exist, the function
* falls back to form-level handlers.
*
* @param $form
* An associative array containing the structure of the form.
* @param $form_state
* A keyed array containing the current state of the form. If the user
* submitted the form by clicking a button with custom handler functions
* defined, those handlers will be stored here.
*/
public function executeValidateHandlers(&$form, &$form_state);
/**
* Validates user-submitted form data in the $form_state array.
*
* @param $form_id
* A unique string identifying the form for validation, submission,
* theming, and hook_form_alter functions.
* @param $form
* An associative array containing the structure of the form, which is
* passed by reference. Form validation handlers are able to alter the form
* structure (like #process and #after_build callbacks during form building)
* in case of a validation error. If a validation handler alters the form
* structure, it is responsible for validating the values of changed form
* elements in $form_state['values'] to prevent form submit handlers from
* receiving unvalidated values.
* @param $form_state
* A keyed array containing the current state of the form. The current
* user-submitted data is stored in $form_state['values'], though
* form validation functions are passed an explicit copy of the
* values for the sake of simplicity. Validation handlers can also use
* $form_state to pass information on to submit handlers. For example:
* $form_state['data_for_submission'] = $data;
* This technique is useful when validation requires file parsing,
* web service requests, or other expensive requests that should
* not be repeated in the submission step.
*/
public function validateForm($form_id, &$form, &$form_state);
}
<?php
/**
* @file
* Contains \Drupal\Core\Form\OptGroup.
*/
namespace Drupal\Core\Form;
/**
* Provides helpers for HTML option groups.
*/
class OptGroup {
/**
* Allows PHP array processing of multiple select options with the same value.
*
* Used for form select elements which need to validate HTML option groups
* and multiple options which may return the same value. Associative PHP
* arrays cannot handle these structures, since they share a common key.
*
* @param array $array
* The form options array to process.
*
* @return array
* An array with all hierarchical elements flattened to a single array.
*/
public static function flattenOptions(array $array) {
$options = array();
static::doFlattenOptions($array, $options);
return $options;
}
/**
* Iterates over an array building a flat array with duplicate keys removed.
*
* This function also handles cases where objects are passed as array values.
*
* @param array $array
* The form options array to process.
* @param array $options
* The array of flattened options.
*/
protected static function doFlattenOptions(array $array, array &$options) {
foreach ($array as $key => $value) {
if (is_object($value)) {
static::doFlattenOptions($value->option, $options);
}
elseif (is_array($value)) {
static::doFlattenOptions($value, $options);
}
else {
$options[$key] = 1;
}
}
}
}
......@@ -10,6 +10,7 @@
use Drupal\Component\Utility\String;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Form\OptGroup;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\AllowedValuesInterface;
use Drupal\Core\TypedData\DataDefinition;
......@@ -69,7 +70,7 @@ public function getPossibleOptions(AccountInterface $account = NULL) {
public function getSettableValues(AccountInterface $account = NULL) {
// Flatten options first, because "settable options" may contain group
// arrays.
$flatten_options = \Drupal::formBuilder()->flattenOptions($this->getSettableOptions($account));
$flatten_options = OptGroup::flattenOptions($this->getSettableOptions($account));
return array_keys($flatten_options);
}
......
......@@ -8,6 +8,7 @@
namespace Drupal\options\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Form\OptGroup;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\AllowedValuesInterface;
......@@ -32,7 +33,7 @@ public static function defaultSettings() {
public function getPossibleValues(AccountInterface $account = NULL) {
// Flatten options firstly, because Possible Options may contain group
// arrays.
$flatten_options = \Drupal::formBuilder()->flattenOptions($this->getPossibleOptions($account));
$flatten_options = OptGroup::flattenOptions($this->getPossibleOptions($account));
return array_keys($flatten_options);
}
......@@ -49,7 +50,7 @@ public function getPossibleOptions(AccountInterface $account = NULL) {
public function getSettableValues(AccountInterface $account = NULL) {
// Flatten options firstly, because Settable Options may contain group
// arrays.
$flatten_options = \Drupal::formBuilder()->flattenOptions($this->getSettableOptions($account));
$flatten_options = OptGroup::flattenOptions($this->getSettableOptions($account));
return array_keys($flatten_options);
}
......
......@@ -9,6 +9,7 @@
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Form\OptGroup;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\AllowedValuesInterface;
......@@ -48,7 +49,7 @@ public static function defaultSettings() {
public function getPossibleValues(AccountInterface $account = NULL) {
// Flatten options firstly, because Possible Options may contain group
// arrays.
$flatten_options = \Drupal::formBuilder()->flattenOptions($this->getPossibleOptions($account));
$flatten_options = OptGroup::flattenOptions($this->getPossibleOptions($account));
return array_keys($flatten_options);
}
......@@ -65,7 +66,7 @@ public function getPossibleOptions(AccountInterface $account = NULL) {
public function getSettableValues(AccountInterface $account = NULL) {
// Flatten options firstly, because Settable Options may contain group
// arrays.
$flatten_options = \Drupal::formBuilder()->flattenOptions($this->getSettableOptions($account));
$flatten_options = OptGroup::flattenOptions($this->getSettableOptions($account));
return array_keys($flatten_options);
}
......
......@@ -29,6 +29,11 @@ abstract class FormTestBase extends UnitTestCase {
*/
protected $formBuilder;
/**
* @var \Drupal\Core\Form\FormValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $formValidator;
/**
* The mocked URL generator.
*
......@@ -92,11 +97,6 @@ abstract class FormTestBase extends UnitTestCase {
*/
protected $keyValueExpirableFactory;
/**
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\StringTranslation\TranslationInterface
*/
protected $translationManager;
/**
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\HttpKernel
*/
......@@ -118,8 +118,8 @@ public function setUp() {
)));
$this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->formValidator = $this->getMock('Drupal\Core\Form\FormValidatorInterface');
$this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
$this->translationManager = $this->getStringTranslationStub();
$this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
->disableOriginalConstructor()
->getMock();
......@@ -145,7 +145,7 @@ protected function tearDown() {
protected function setupFormBuilder() {
$request_stack = new RequestStack();
$request_stack->push($this->request);
$this->formBuilder = new TestFormBuilder($this->moduleHandler, $this->keyValueExpirableFactory, $this->eventDispatcher, $this->urlGenerator, $this->translationManager, $request_stack, $this->csrfToken, $this->httpKernel);
$this->formBuilder = new TestFormBuilder($this->formValidator, $this->moduleHandler, $this->keyValueExpirableFactory, $this->eventDispatcher, $this->urlGenerator, $request_stack, $this->csrfToken, $this->httpKernel);
$this->formBuilder->setCurrentUser($this->account);
}
......@@ -283,18 +283,6 @@ protected function drupalInstallationAttempted() {
return FALSE;
}
/**
* {@inheritdoc}
*/
protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
}
/**
* {@inheritdoc}
*/
protected function watchdog($type, $message, array $variables = NULL, $severity = WATCHDOG_NOTICE, $link = NULL) {
}
/**
* {@inheritdoc}
*/
......
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Form\FormValidationTest.
*/
namespace Drupal\Tests\Core\Form;
/**
* Tests various form element validation mechanisms.
*
* @group Drupal
* @group Form
*/
class FormValidationTest extends FormTestBase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Form element validation',
'description' => 'Tests various form element validation mechanisms.',
'group' => 'Form API',
);
}
public function testUniqueHtmlId() {
$form_id = 'test_form_id';
$expected_form = $form_id();
$expected_form['test']['#required'] = TRUE;
// Mock a form object that will be built three times.
$form_arg = $this->getMockForm($form_id, $expected_form, 2);
$form_state = array();
$this->formBuilder->getFormId($form_arg, $form_state);
$form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
$this->assertSame($form_id, $form['#id']);
$form_state = array();
$form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
$this->assertSame("$form_id--2", $form['#id']);
}
}
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment