Issue #2257835 by tim.plunkett, sun, Jalandhar: Move form submission logic out of FormBuilder into a new class.

8.0.x
Nathaniel Catchpole 2014-05-14 21:18:01 +01:00
parent 5f79a75cbc
commit 120a1da34c
9 changed files with 686 additions and 440 deletions

View File

@ -119,10 +119,13 @@ services:
arguments: [default]
form_builder:
class: Drupal\Core\Form\FormBuilder
arguments: ['@form_validator', '@module_handler', '@keyvalue.expirable', '@event_dispatcher', '@url_generator', '@request_stack', '@?csrf_token', '@?http_kernel']
arguments: ['@form_validator', '@form_submitter', '@module_handler', '@keyvalue.expirable', '@event_dispatcher', '@request_stack', '@?csrf_token', '@?http_kernel']
form_validator:
class: Drupal\Core\Form\FormValidator
arguments: ['@request_stack', '@string_translation', '@csrf_token']
form_submitter:
class: Drupal\Core\Form\FormSubmitter
arguments: ['@request_stack', '@url_generator']
keyvalue:
class: Drupal\Core\KeyValueStore\KeyValueFactory
arguments: ['@service_container', '@settings']

View File

@ -334,27 +334,27 @@ function drupal_validate_form($form_id, &$form, &$form_state) {
* Redirects the user to a URL after a form has been processed.
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::formBuilder()->redirectForm().
* Use \Drupal::service('form_submitter')->redirectForm().
*
* @see \Drupal\Core\Form\FormBuilderInterface::redirectForm().
* @see \Drupal\Core\Form\FormSubmitterInterface::redirectForm().
*/
function drupal_redirect_form($form_state) {
return \Drupal::formBuilder()->redirectForm($form_state);
return \Drupal::service('form_submitter')->redirectForm($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 either \Drupal::formBuilder()->executeSubmitHandlers() or
* Use either \Drupal::service('form_submitter')->executeSubmitHandlers() or
* \Drupal::service('form_validator')->executeValidateHandlers().
*
* @see \Drupal\Core\Form\FormBuilderInterface::executeSubmitHandlers()
* @see \Drupal\Core\Form\FormSubmitterInterface::executeSubmitHandlers()
* @see \Drupal\Core\Form\FormValidatorInterface::executeValidateHandlers()
*/
function form_execute_handlers($type, &$form, &$form_state) {
if ($type == 'submit') {
\Drupal::formBuilder()->executeSubmitHandlers($form, $form_state);
\Drupal::service('form_submitter')->executeSubmitHandlers($form, $form_state);
}
elseif ($type == 'validate') {
\Drupal::service('form_validator')->executeValidateHandlers($form, $form_state);

View File

@ -15,11 +15,8 @@ use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\HttpKernel;
use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
@ -29,7 +26,7 @@ use Symfony\Component\HttpKernel\KernelEvents;
/**
* Provides form building and processing.
*/
class FormBuilder implements FormBuilderInterface, FormValidatorInterface {
class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormSubmitterInterface {
/**
* The module handler.
@ -52,13 +49,6 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface {
*/
protected $eventDispatcher;
/**
* The URL generator.
*
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;
/**
* The request stack.
*
@ -92,19 +82,24 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface {
*/
protected $formValidator;
/**
* @var \Drupal\Core\Form\FormSubmitterInterface
*/
protected $formSubmitter;
/**
* Constructs a new FormBuilder.
*
* @param \Drupal\Core\Form\FormValidatorInterface $form_validator
* The form validator.
* @param \Drupal\Core\Form\FormSubmitterInterface $form_submitter
* The form submission processor.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $key_value_expirable_factory
* The keyvalue expirable factory.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
* The URL generator.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
@ -112,12 +107,12 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface {
* @param \Drupal\Core\HttpKernel $http_kernel
* The HTTP kernel.
*/
public function __construct(FormValidatorInterface $form_validator, ModuleHandlerInterface $module_handler, KeyValueExpirableFactoryInterface $key_value_expirable_factory, EventDispatcherInterface $event_dispatcher, UrlGeneratorInterface $url_generator, RequestStack $request_stack, CsrfTokenGenerator $csrf_token = NULL, HttpKernel $http_kernel = NULL) {
public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, ModuleHandlerInterface $module_handler, KeyValueExpirableFactoryInterface $key_value_expirable_factory, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, CsrfTokenGenerator $csrf_token = NULL, HttpKernel $http_kernel = NULL) {
$this->formValidator = $form_validator;
$this->formSubmitter = $form_submitter;
$this->moduleHandler = $module_handler;
$this->keyValueExpirableFactory = $key_value_expirable_factory;
$this->eventDispatcher = $event_dispatcher;
$this->urlGenerator = $url_generator;
$this->requestStack = $request_stack;
$this->csrfToken = $csrf_token;
$this->httpKernel = $http_kernel;
@ -555,53 +550,9 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface {
$this->drupalStaticReset('drupal_html_id');
}
// @todo Move into a dedicated class in https://drupal.org/node/2257835.
if ($form_state['submitted'] && !$this->getAnyErrors() && !$form_state['rebuild']) {
// Execute form submit handlers.
$this->executeSubmitHandlers($form, $form_state);
// If batches were set in the submit handlers, we process them now,
// possibly ending execution. We make sure we do not react to the batch
// that is already being processed (if a batch operation performs a
// self::submitForm).
if ($batch = &$this->batchGet() && !isset($batch['current_set'])) {
// Store $form_state information in the batch definition.
// We need the full $form_state when either:
// - Some submit handlers were saved to be called during batch
// processing. See self::executeSubmitHandlers().
// - The form is multistep.
// In other cases, we only need the information expected by
// self::redirectForm().
if ($batch['has_form_submits'] || !empty($form_state['rebuild'])) {
$batch['form_state'] = $form_state;
}
else {
$batch['form_state'] = array_intersect_key($form_state, array_flip(array('programmed', 'rebuild', 'storage', 'no_redirect', 'redirect', 'redirect_route')));
}
$batch['progressive'] = !$form_state['programmed'];
$response = batch_process();
if ($batch['progressive']) {
return $response;
}
// Execution continues only for programmatic forms.
// For 'regular' forms, we get redirected to the batch processing
// page. Form redirection will be handled in _batch_finished(),
// after the batch is processed.
}
// Set a flag to indicate the the form has been processed and executed.
$form_state['executed'] = TRUE;
// If no response has been set, process the form redirect.
if (!isset($form_state['response']) && $redirect = $this->redirectForm($form_state)) {
$form_state['response'] = $redirect;
}
// If there is a response was set, return it instead of continuing.
if (isset($form_state['response']) && $form_state['response'] instanceof Response) {
return $form_state['response'];
if (!$form_state['rebuild'] && !$this->formValidator->getAnyErrors()) {
if ($submit_response = $this->formSubmitter->doSubmitForm($form, $form_state)) {
return $submit_response;
}
}
@ -793,78 +744,7 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface {
* {@inheritdoc}
*/
public function redirectForm($form_state) {
// Skip redirection for form submissions invoked via self::submitForm().
if (!empty($form_state['programmed'])) {
return;
}
// Skip redirection if rebuild is activated.
if (!empty($form_state['rebuild'])) {
return;
}
// Skip redirection if it was explicitly disallowed.
if (!empty($form_state['no_redirect'])) {
return;
}
// Allow using redirect responses directly if needed.
if (isset($form_state['redirect']) && $form_state['redirect'] instanceof RedirectResponse) {
return $form_state['redirect'];
}
// Check for a route-based redirection.
if (isset($form_state['redirect_route'])) {
// @todo Remove once all redirects are converted to Url.
if (!($form_state['redirect_route'] instanceof Url)) {
$form_state['redirect_route'] += array(
'route_parameters' => array(),
'options' => array(),
);
$form_state['redirect_route'] = new Url($form_state['redirect_route']['route_name'], $form_state['redirect_route']['route_parameters'], $form_state['redirect_route']['options']);
}
$form_state['redirect_route']->setAbsolute();
return new RedirectResponse($form_state['redirect_route']->toString());
}
// Only invoke a redirection if redirect value was not set to FALSE.
if (!isset($form_state['redirect']) || $form_state['redirect'] !== FALSE) {
if (isset($form_state['redirect'])) {
if (is_array($form_state['redirect'])) {
if (isset($form_state['redirect'][1])) {
$options = $form_state['redirect'][1];
}
else {
$options = array();
}
// Redirections should always use absolute URLs.
$options['absolute'] = TRUE;
if (isset($form_state['redirect'][2])) {
$status_code = $form_state['redirect'][2];
}
else {
$status_code = 302;
}
return new RedirectResponse($this->urlGenerator->generateFromPath($form_state['redirect'][0], $options), $status_code);
}
else {
// This function can be called from the installer, which guarantees
// that $redirect will always be a string, so catch that case here
// and use the appropriate redirect function.
if ($this->drupalInstallationAttempted()) {
install_goto($form_state['redirect']);
}
else {
return new RedirectResponse($this->urlGenerator->generateFromPath($form_state['redirect'], array('absolute' => TRUE)));
}
}
}
$request = $this->requestStack->getCurrentRequest();
$url = $this->urlGenerator->generateFromPath($request->attributes->get('_system_path'), array(
'query' => $request->query->all(),
'absolute' => TRUE,
));
return new RedirectResponse($url);
}
return $this->formSubmitter->redirectForm($form_state);
}
/**
@ -878,33 +758,14 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface {
* {@inheritdoc}
*/
public function executeSubmitHandlers(&$form, &$form_state) {
// If there was a button pressed, use its handlers.
if (isset($form_state['submit_handlers'])) {
$handlers = $form_state['submit_handlers'];
}
// Otherwise, check for a form-level handler.
elseif (isset($form['#submit'])) {
$handlers = $form['#submit'];
}
else {
$handlers = array();
}
$this->formSubmitter->executeSubmitHandlers($form, $form_state);
}
foreach ($handlers as $function) {
// Check if a previous _submit handler has set a batch, but make sure we
// do not react to a batch that is already being processed (for instance
// if a batch operation performs a self::submitForm()).
if (($batch = &$this->batchGet()) && !isset($batch['id'])) {
// Some previous submit handler has set a batch. To ensure correct
// execution order, store the call in a special 'control' batch set.
// See _batch_next_set().
$batch['sets'][] = array('form_submit' => $function);
$batch['has_form_submits'] = TRUE;
}
else {
call_user_func_array($function, array(&$form, &$form_state));
}
}
/**
* {@inheritdoc}
*/
public function doSubmitForm(&$form, &$form_state) {
throw new \LogicException('Use FormBuilderInterface::processForm() instead.');
}
/**
@ -1378,15 +1239,6 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface {
return element_info($type);
}
/**
* Wraps drupal_installation_attempted().
*
* @return bool
*/
protected function drupalInstallationAttempted() {
return drupal_installation_attempted();
}
/**
* Wraps drupal_html_class().
*
@ -1430,11 +1282,4 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface {
return $this->currentUser;
}
/**
* Wraps batch_get().
*/
protected function &batchGet() {
return batch_get();
}
}

View File

@ -385,83 +385,6 @@ interface FormBuilderInterface extends FormErrorInterface {
*/
public function prepareForm($form_id, &$form, &$form_state);
/**
* Redirects the user to a URL after a form has been processed.
*
* After a form is submitted and processed, normally the user should be
* redirected to a new destination page. This function figures out what that
* destination should be, based on the $form_state array and the 'destination'
* query string in the request URL, and redirects the user there.
*
* Usually (for exceptions, see below) $form_state['redirect'] determines
* where to redirect the user. This can be set either to a string (the path to
* redirect to), or an array of arguments for url(). If
* $form_state['redirect'] is missing, the user is usually (again, see below
* for exceptions) redirected back to the page they came from, where they
* should see a fresh, unpopulated copy of the form.
*
* Here is an example of how to set up a form to redirect to the path 'node':
* @code
* $form_state['redirect'] = 'node';
* @endcode
* And here is an example of how to redirect to 'node/123?foo=bar#baz':
* @code
* $form_state['redirect'] = array(
* 'node/123',
* array(
* 'query' => array(
* 'foo' => 'bar',
* ),
* 'fragment' => 'baz',
* ),
* );
* @endcode
*
* There are several exceptions to the "usual" behavior described above:
* - If $form_state['programmed'] is TRUE, the form submission was usually
* invoked via self::submitForm(), so any redirection would break the script
* that invoked self::submitForm() and no redirection is done.
* - If $form_state['rebuild'] is TRUE, the form is being rebuilt, and no
* redirection is done.
* - If $form_state['no_redirect'] is TRUE, redirection is disabled. This is
* set, for instance, by \Drupal\system\FormAjaxController::getForm() to
* prevent redirection in Ajax callbacks. $form_state['no_redirect'] should
* never be set or altered by form builder functions or form validation
* or submit handlers.
* - If $form_state['redirect'] is set to FALSE, redirection is disabled.
* - If none of the above conditions has prevented redirection, then the
* redirect is accomplished by returning a RedirectResponse, passing in the
* value of $form_state['redirect'] if it is set, or the current path if it
* is not. RedirectResponse preferentially uses the value of
* \Drupal::request->query->get('destination') (the 'destination' URL query
* string) if it is present, so this will override any values set by
* $form_state['redirect'].
*
* @param $form_state
* An associative array containing the current state of the form.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|null
*
* @see self::processForm()
* @see self::buildForm()
*/
public function redirectForm($form_state);
/**
* 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 $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 executeSubmitHandlers(&$form, &$form_state);
/**
* Builds and processes all elements in the structured form array.
*

View File

@ -0,0 +1,232 @@
<?php
/**
* @file
* Contains \Drupal\Core\Form\FormSubmitter.
*/
namespace Drupal\Core\Form;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Drupal\Core\Routing\UrlGeneratorInterface;
/**
* Provides submission processing for forms.
*/
class FormSubmitter implements FormSubmitterInterface {
/**
* The URL generator.
*
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* Constructs a new FormValidator.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
*/
public function __construct(RequestStack $request_stack, UrlGeneratorInterface $url_generator) {
$this->requestStack = $request_stack;
$this->urlGenerator = $url_generator;
}
/**
* {@inheritdoc}
*/
public function doSubmitForm(&$form, &$form_state) {
if (!$form_state['submitted']) {
return;
}
// Execute form submit handlers.
$this->executeSubmitHandlers($form, $form_state);
// If batches were set in the submit handlers, we process them now,
// possibly ending execution. We make sure we do not react to the batch
// that is already being processed (if a batch operation performs a
// \Drupal\Core\Form\FormBuilderInterface::submitForm).
if ($batch = &$this->batchGet() && !isset($batch['current_set'])) {
// Store $form_state information in the batch definition.
// We need the full $form_state when either:
// - Some submit handlers were saved to be called during batch
// processing. See self::executeSubmitHandlers().
// - The form is multistep.
// In other cases, we only need the information expected by
// self::redirectForm().
if ($batch['has_form_submits'] || !empty($form_state['rebuild'])) {
$batch['form_state'] = $form_state;
}
else {
$batch['form_state'] = array_intersect_key($form_state, array_flip(array('programmed', 'rebuild', 'storage', 'no_redirect', 'redirect', 'redirect_route')));
}
$batch['progressive'] = !$form_state['programmed'];
$response = batch_process();
if ($batch['progressive']) {
return $response;
}
// Execution continues only for programmatic forms.
// For 'regular' forms, we get redirected to the batch processing
// page. Form redirection will be handled in _batch_finished(),
// after the batch is processed.
}
// Set a flag to indicate the the form has been processed and executed.
$form_state['executed'] = TRUE;
// If no response has been set, process the form redirect.
if (!isset($form_state['response']) && $redirect = $this->redirectForm($form_state)) {
$form_state['response'] = $redirect;
}
// If there is a response was set, return it instead of continuing.
if (isset($form_state['response']) && $form_state['response'] instanceof Response) {
return $form_state['response'];
}
}
/**
* {@inheritdoc}
*/
public function executeSubmitHandlers(&$form, &$form_state) {
// If there was a button pressed, use its handlers.
if (isset($form_state['submit_handlers'])) {
$handlers = $form_state['submit_handlers'];
}
// Otherwise, check for a form-level handler.
elseif (isset($form['#submit'])) {
$handlers = $form['#submit'];
}
else {
$handlers = array();
}
foreach ($handlers as $function) {
// Check if a previous _submit handler has set a batch, but make sure we
// do not react to a batch that is already being processed (for instance
// if a batch operation performs a
// \Drupal\Core\Form\FormBuilderInterface::submitForm()).
if (($batch = &$this->batchGet()) && !isset($batch['id'])) {
// Some previous submit handler has set a batch. To ensure correct
// execution order, store the call in a special 'control' batch set.
// See _batch_next_set().
$batch['sets'][] = array('form_submit' => $function);
$batch['has_form_submits'] = TRUE;
}
else {
call_user_func_array($function, array(&$form, &$form_state));
}
}
}
/**
* {@inheritdoc}
*/
public function redirectForm($form_state) {
// Skip redirection for form submissions invoked via
// \Drupal\Core\Form\FormBuilderInterface::submitForm().
if (!empty($form_state['programmed'])) {
return;
}
// Skip redirection if rebuild is activated.
if (!empty($form_state['rebuild'])) {
return;
}
// Skip redirection if it was explicitly disallowed.
if (!empty($form_state['no_redirect'])) {
return;
}
// Allow using redirect responses directly if needed.
if (isset($form_state['redirect']) && $form_state['redirect'] instanceof RedirectResponse) {
return $form_state['redirect'];
}
// Check for a route-based redirection.
if (isset($form_state['redirect_route'])) {
// @todo Remove once all redirects are converted to Url.
if (!($form_state['redirect_route'] instanceof Url)) {
$form_state['redirect_route'] += array(
'route_parameters' => array(),
'options' => array(),
);
$form_state['redirect_route'] = new Url($form_state['redirect_route']['route_name'], $form_state['redirect_route']['route_parameters'], $form_state['redirect_route']['options']);
}
$form_state['redirect_route']->setAbsolute();
return new RedirectResponse($form_state['redirect_route']->toString());
}
// Only invoke a redirection if redirect value was not set to FALSE.
if (!isset($form_state['redirect']) || $form_state['redirect'] !== FALSE) {
if (isset($form_state['redirect'])) {
if (is_array($form_state['redirect'])) {
if (isset($form_state['redirect'][1])) {
$options = $form_state['redirect'][1];
}
else {
$options = array();
}
// Redirections should always use absolute URLs.
$options['absolute'] = TRUE;
if (isset($form_state['redirect'][2])) {
$status_code = $form_state['redirect'][2];
}
else {
$status_code = 302;
}
return new RedirectResponse($this->urlGenerator->generateFromPath($form_state['redirect'][0], $options), $status_code);
}
else {
// This function can be called from the installer, which guarantees
// that $redirect will always be a string, so catch that case here
// and use the appropriate redirect function.
if ($this->drupalInstallationAttempted()) {
install_goto($form_state['redirect']);
}
else {
return new RedirectResponse($this->urlGenerator->generateFromPath($form_state['redirect'], array('absolute' => TRUE)));
}
}
}
$request = $this->requestStack->getCurrentRequest();
$url = $this->urlGenerator->generateFromPath($request->attributes->get('_system_path'), array(
'query' => $request->query->all(),
'absolute' => TRUE,
));
return new RedirectResponse($url);
}
}
/**
* Wraps drupal_installation_attempted().
*
* @return bool
*/
protected function drupalInstallationAttempted() {
return drupal_installation_attempted();
}
/**
* Wraps batch_get().
*/
protected function &batchGet() {
return batch_get();
}
}

View File

@ -0,0 +1,106 @@
<?php
/**
* @file
* Contains \Drupal\Core\Form\FormSubmitterInterface.
*/
namespace Drupal\Core\Form;
/**
* Provides an interface for processing form submissions.
*/
interface FormSubmitterInterface {
/**
* Handles the submitted form, executing callbacks and processing responses.
*
* @param array $form
* An associative array containing the structure of the form.
* @param array $form_state
* An associative array containing the current state of the form.
*
* @return null|\Symfony\Component\HttpFoundation\Response
* If a response was set by a submit handler, or if the form needs to
* redirect, a Response object will be returned.
*/
public function doSubmitForm(&$form, &$form_state);
/**
* 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 $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 executeSubmitHandlers(&$form, &$form_state);
/**
* Redirects the user to a URL after a form has been processed.
*
* After a form is submitted and processed, normally the user should be
* redirected to a new destination page. This function figures out what that
* destination should be, based on the $form_state array and the 'destination'
* query string in the request URL, and redirects the user there.
*
* Usually (for exceptions, see below) $form_state['redirect'] determines
* where to redirect the user. This can be set either to a string (the path to
* redirect to), or an array of arguments for url(). If
* $form_state['redirect'] is missing, the user is usually (again, see below
* for exceptions) redirected back to the page they came from, where they
* should see a fresh, unpopulated copy of the form.
*
* Here is an example of how to set up a form to redirect to the path 'node':
* @code
* $form_state['redirect'] = 'node';
* @endcode
* And here is an example of how to redirect to 'node/123?foo=bar#baz':
* @code
* $form_state['redirect'] = array(
* 'node/123',
* array(
* 'query' => array(
* 'foo' => 'bar',
* ),
* 'fragment' => 'baz',
* ),
* );
* @endcode
*
* There are several exceptions to the "usual" behavior described above:
* - If $form_state['programmed'] is TRUE, the form submission was usually
* invoked via self::submitForm(), so any redirection would break the script
* that invoked self::submitForm() and no redirection is done.
* - If $form_state['rebuild'] is TRUE, the form is being rebuilt, and no
* redirection is done.
* - If $form_state['no_redirect'] is TRUE, redirection is disabled. This is
* set, for instance, by \Drupal\system\FormAjaxController::getForm() to
* prevent redirection in Ajax callbacks. $form_state['no_redirect'] should
* never be set or altered by form builder functions or form validation
* or submit handlers.
* - If $form_state['redirect'] is set to FALSE, redirection is disabled.
* - If none of the above conditions has prevented redirection, then the
* redirect is accomplished by returning a RedirectResponse, passing in the
* value of $form_state['redirect'] if it is set, or the current path if it
* is not. RedirectResponse preferentially uses the value of
* \Drupal::request->query->get('destination') (the 'destination' URL query
* string) if it is present, so this will override any values set by
* $form_state['redirect'].
*
* @param $form_state
* An associative array containing the current state of the form.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|null
*
* @see \Drupal\Core\Form\FormBuilderInterface::processForm()
* @see \Drupal\Core\Form\FormBuilderInterface::buildForm()
*/
public function redirectForm($form_state);
}

View File

@ -7,12 +7,9 @@
namespace Drupal\Tests\Core\Form {
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Tests the form builder.
@ -35,17 +32,6 @@ class FormBuilderTest extends FormTestBase {
);
}
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$container = new ContainerBuilder();
$container->set('url_generator', $this->urlGenerator);
\Drupal::setContainer($container);
}
/**
* Tests the getFormId() method with a string based form ID.
*/
@ -219,140 +205,6 @@ class FormBuilderTest extends FormTestBase {
}
$this->assertSame($response, $form_state['response']);
}
/**
* Tests the redirectForm() method when a redirect is expected.
*
* @param array $form_state
* An array of form state data to use for the redirect.
* @param string $result
* The URL the redirect is targeting.
* @param int $status
* (optional) The HTTP status code for the redirect.
*
* @dataProvider providerTestRedirectWithResult
*/
public function testRedirectWithResult($form_state, $result, $status = 302) {
$this->urlGenerator->expects($this->once())
->method('generateFromPath')
->will($this->returnValueMap(array(
array(NULL, array('query' => array(), 'absolute' => TRUE), '<front>'),
array('foo', array('absolute' => TRUE), 'foo'),
array('bar', array('query' => array('foo' => 'baz'), 'absolute' => TRUE), 'bar'),
array('baz', array('absolute' => TRUE), 'baz'),
))
);
$form_state += $this->formBuilder->getFormStateDefaults();
$redirect = $this->formBuilder->redirectForm($form_state);
$this->assertSame($result, $redirect->getTargetUrl());
$this->assertSame($status, $redirect->getStatusCode());
}
/**
* Tests the redirectForm() with redirect_route when a redirect is expected.
*
* @param array $form_state
* An array of form state data to use for the redirect.
* @param string $result
* The URL the redirect is targeting.
* @param int $status
* (optional) The HTTP status code for the redirect.
*
* @dataProvider providerTestRedirectWithRouteWithResult
*/
public function testRedirectWithRouteWithResult($form_state, $result, $status = 302) {
$this->urlGenerator->expects($this->once())
->method('generateFromRoute')
->will($this->returnValueMap(array(
array('test_route_a', array(), array('absolute' => TRUE), 'test-route'),
array('test_route_b', array('key' => 'value'), array('absolute' => TRUE), 'test-route/value'),
))
);
$form_state += $this->formBuilder->getFormStateDefaults();
$redirect = $this->formBuilder->redirectForm($form_state);
$this->assertSame($result, $redirect->getTargetUrl());
$this->assertSame($status, $redirect->getStatusCode());
}
/**
* Tests the redirectForm() method with a response object.
*/
public function testRedirectWithResponseObject() {
$redirect = new RedirectResponse('/example');
$form_state['redirect'] = $redirect;
$form_state += $this->formBuilder->getFormStateDefaults();
$result_redirect = $this->formBuilder->redirectForm($form_state);
$this->assertSame($redirect, $result_redirect);
}
/**
* Tests the redirectForm() method when no redirect is expected.
*
* @param array $form_state
* An array of form state data to use for the redirect.
*
* @dataProvider providerTestRedirectWithoutResult
*/
public function testRedirectWithoutResult($form_state) {
$this->urlGenerator->expects($this->never())
->method('generateFromPath');
$this->urlGenerator->expects($this->never())
->method('generateFromRoute');
$form_state += $this->formBuilder->getFormStateDefaults();
$redirect = $this->formBuilder->redirectForm($form_state);
$this->assertNull($redirect);
}
/**
* Provides test data for testing the redirectForm() method with a redirect.
*
* @return array
* Returns some test data.
*/
public function providerTestRedirectWithResult() {
return array(
array(array(), '<front>'),
array(array('redirect' => 'foo'), 'foo'),
array(array('redirect' => array('foo')), 'foo'),
array(array('redirect' => array('foo')), 'foo'),
array(array('redirect' => array('bar', array('query' => array('foo' => 'baz')))), 'bar'),
array(array('redirect' => array('baz', array(), 301)), 'baz', 301),
);
}
/**
* Provides test data for testing the redirectForm() method with a route name.
*
* @return array
* Returns some test data.
*/
public function providerTestRedirectWithRouteWithResult() {
return array(
array(array('redirect_route' => array('route_name' => 'test_route_a')), 'test-route'),
array(array('redirect_route' => array('route_name' => 'test_route_b', 'route_parameters' => array('key' => 'value'))), 'test-route/value'),
array(array('redirect_route' => new Url('test_route_b', array('key' => 'value'))), 'test-route/value'),
);
}
/**
* Provides test data for testing the redirectForm() method with no redirect.
*
* @return array
* Returns some test data.
*/
public function providerTestRedirectWithoutResult() {
return array(
array(array('programmed' => TRUE)),
array(array('rebuild' => TRUE)),
array(array('no_redirect' => TRUE)),
array(array('redirect' => FALSE)),
);
}
/**
* Tests the getForm() method with a string based form ID.
*/
@ -554,9 +406,6 @@ class FormBuilderTest extends FormTestBase {
$form_id = 'test_form_id';
$expected_form = $form_id();
$expected_form['test']['#required'] = TRUE;
$this->formValidator->expects($this->exactly(4))
->method('getAnyErrors')
->will($this->returnValue(TRUE));
// Mock a form object that will be built two times.
$form_arg = $this->getMock('Drupal\Core\Form\FormInterface');

View File

@ -0,0 +1,291 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Form\FormSubmitterTest.
*/
namespace Drupal\Tests\Core\Form;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Url;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Tests the form submission handler.
*
* @coversDefaultClass \Drupal\Core\Form\FormSubmitter
*
* @group Drupal
* @group Form
*/
class FormSubmitterTest extends UnitTestCase {
/**
* The mocked URL generator.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Form submission test',
'description' => 'Tests the form submission handler.',
'group' => 'Form API',
);
}
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
}
/**
* @covers ::doSubmitForm
*/
public function testHandleFormSubmissionNotSubmitted() {
$form_submitter = $this->getFormSubmitter();
$form = array();
$form_state = $this->getFormStateDefaults();
$return = $form_submitter->doSubmitForm($form, $form_state);
$this->assertFalse($form_state['executed']);
$this->assertNull($return);
}
/**
* @covers ::doSubmitForm
*/
public function testHandleFormSubmissionNoRedirect() {
$form_submitter = $this->getFormSubmitter();
$form = array();
$form_state = $this->getFormStateDefaults();
$form_state['submitted'] = TRUE;
$form_state['no_redirect'] = TRUE;
$return = $form_submitter->doSubmitForm($form, $form_state);
$this->assertTrue($form_state['executed']);
$this->assertNull($return);
}
/**
* @covers ::doSubmitForm
*
* @dataProvider providerTestHandleFormSubmissionWithResponses
*/
public function testHandleFormSubmissionWithResponses($class, $form_state_key) {
$response = $this->getMockBuilder($class)
->disableOriginalConstructor()
->getMock();
$response->expects($this->any())
->method('prepare')
->will($this->returnValue($response));
$form_state = $this->getFormStateDefaults();
$form_state['submitted'] = TRUE;
$form_state[$form_state_key] = $response;
$form_submitter = $this->getFormSubmitter();
$form = array();
$return = $form_submitter->doSubmitForm($form, $form_state);
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $return);
}
public function providerTestHandleFormSubmissionWithResponses() {
return array(
array('Symfony\Component\HttpFoundation\Response', 'response'),
array('Symfony\Component\HttpFoundation\RedirectResponse', 'redirect'),
);
}
/**
* Tests the redirectForm() method when a redirect is expected.
*
* @covers ::redirectForm
*
* @dataProvider providerTestRedirectWithResult
*/
public function testRedirectWithResult($form_state, $result, $status = 302) {
$form_submitter = $this->getFormSubmitter();
$this->urlGenerator->expects($this->once())
->method('generateFromPath')
->will($this->returnValueMap(array(
array(NULL, array('query' => array(), 'absolute' => TRUE), '<front>'),
array('foo', array('absolute' => TRUE), 'foo'),
array('bar', array('query' => array('foo' => 'baz'), 'absolute' => TRUE), 'bar'),
array('baz', array('absolute' => TRUE), 'baz'),
))
);
$form_state += $this->getFormStateDefaults();
$redirect = $form_submitter->redirectForm($form_state);
$this->assertSame($result, $redirect->getTargetUrl());
$this->assertSame($status, $redirect->getStatusCode());
}
/**
* Tests the redirectForm() with redirect_route when a redirect is expected.
*
* @covers ::redirectForm
*
* @dataProvider providerTestRedirectWithRouteWithResult
*/
public function testRedirectWithRouteWithResult($form_state, $result, $status = 302) {
$container = new ContainerBuilder();
$container->set('url_generator', $this->urlGenerator);
\Drupal::setContainer($container);
$form_submitter = $this->getFormSubmitter();
$this->urlGenerator->expects($this->once())
->method('generateFromRoute')
->will($this->returnValueMap(array(
array('test_route_a', array(), array('absolute' => TRUE), 'test-route'),
array('test_route_b', array('key' => 'value'), array('absolute' => TRUE), 'test-route/value'),
))
);
$form_state += $this->getFormStateDefaults();
$redirect = $form_submitter->redirectForm($form_state);
$this->assertSame($result, $redirect->getTargetUrl());
$this->assertSame($status, $redirect->getStatusCode());
}
/**
* Tests the redirectForm() method with a response object.
*
* @covers ::redirectForm
*/
public function testRedirectWithResponseObject() {
$form_submitter = $this->getFormSubmitter();
$redirect = new RedirectResponse('/example');
$form_state['redirect'] = $redirect;
$form_state += $this->getFormStateDefaults();
$result_redirect = $form_submitter->redirectForm($form_state);
$this->assertSame($redirect, $result_redirect);
}
/**
* Tests the redirectForm() method when no redirect is expected.
*
* @covers ::redirectForm
*
* @dataProvider providerTestRedirectWithoutResult
*/
public function testRedirectWithoutResult($form_state) {
$form_submitter = $this->getFormSubmitter();
$this->urlGenerator->expects($this->never())
->method('generateFromPath');
$this->urlGenerator->expects($this->never())
->method('generateFromRoute');
$form_state += $this->getFormStateDefaults();
$redirect = $form_submitter->redirectForm($form_state);
$this->assertNull($redirect);
}
/**
* Provides test data for testing the redirectForm() method with a redirect.
*
* @return array
* Returns some test data.
*/
public function providerTestRedirectWithResult() {
return array(
array(array(), '<front>'),
array(array('redirect' => 'foo'), 'foo'),
array(array('redirect' => array('foo')), 'foo'),
array(array('redirect' => array('foo')), 'foo'),
array(array('redirect' => array('bar', array('query' => array('foo' => 'baz')))), 'bar'),
array(array('redirect' => array('baz', array(), 301)), 'baz', 301),
);
}
/**
* Provides test data for testing the redirectForm() method with a route name.
*
* @return array
* Returns some test data.
*/
public function providerTestRedirectWithRouteWithResult() {
return array(
array(array('redirect_route' => array('route_name' => 'test_route_a')), 'test-route'),
array(array('redirect_route' => array('route_name' => 'test_route_b', 'route_parameters' => array('key' => 'value'))), 'test-route/value'),
array(array('redirect_route' => new Url('test_route_b', array('key' => 'value'))), 'test-route/value'),
);
}
/**
* Provides test data for testing the redirectForm() method with no redirect.
*
* @return array
* Returns some test data.
*/
public function providerTestRedirectWithoutResult() {
return array(
array(array('programmed' => TRUE)),
array(array('rebuild' => TRUE)),
array(array('no_redirect' => TRUE)),
array(array('redirect' => FALSE)),
);
}
/**
* @covers ::executeSubmitHandlers
*/
public function testExecuteSubmitHandlers() {
$form_submitter = $this->getFormSubmitter();
$mock = $this->getMock('stdClass', array('submit_handler', 'hash_submit'));
$mock->expects($this->once())
->method('submit_handler')
->with($this->isType('array'), $this->isType('array'));
$mock->expects($this->once())
->method('hash_submit')
->with($this->isType('array'), $this->isType('array'));
$form = array();
$form_state = $this->getFormStateDefaults();
$form_submitter->executeSubmitHandlers($form, $form_state);
$form['#submit'][] = array($mock, 'hash_submit');
$form_submitter->executeSubmitHandlers($form, $form_state);
// $form_state submit handlers will supersede $form handlers.
$form_state['submit_handlers'][] = array($mock, 'submit_handler');
$form_submitter->executeSubmitHandlers($form, $form_state);
}
/**
* @return array()
*/
protected function getFormStateDefaults() {
$form_builder = $this->getMockBuilder('Drupal\Core\Form\FormBuilder')
->disableOriginalConstructor()
->setMethods(NULL)
->getMock();
return $form_builder->getFormStateDefaults();
}
/**
* @return \Drupal\Core\Form\FormSubmitterInterface
*/
protected function getFormSubmitter() {
$request_stack = new RequestStack();
$request_stack->push(new Request());
return $this->getMockBuilder('Drupal\Core\Form\FormSubmitter')
->setConstructorArgs(array($request_stack, $this->urlGenerator))
->setMethods(array('batchGet', 'drupalInstallationAttempted'))
->getMock();
}
}

View File

@ -34,6 +34,11 @@ abstract class FormTestBase extends UnitTestCase {
*/
protected $formValidator;
/**
* @var \Drupal\Core\Form\FormSubmitterInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $formSubmitter;
/**
* The mocked URL generator.
*
@ -83,6 +88,13 @@ abstract class FormTestBase extends UnitTestCase {
*/
protected $request;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The event dispatcher.
*
@ -117,8 +129,6 @@ abstract class FormTestBase extends UnitTestCase {
array('form_state', $this->formStateCache),
)));
$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->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
->disableOriginalConstructor()
@ -126,10 +136,22 @@ abstract class FormTestBase extends UnitTestCase {
$this->httpKernel = $this->getMockBuilder('Drupal\Core\HttpKernel')
->disableOriginalConstructor()
->getMock();
$this->request = new Request();
$this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->request = new Request();
$this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->requestStack = new RequestStack();
$this->requestStack->push($this->request);
$this->formValidator = $this->getMockBuilder('Drupal\Core\Form\FormValidator')
->setConstructorArgs(array($this->requestStack, $this->getStringTranslationStub(), $this->csrfToken))
->setMethods(array('drupalSetMessage'))
->getMock();
$this->formSubmitter = $this->getMockBuilder('Drupal\Core\Form\FormSubmitter')
->setConstructorArgs(array($this->requestStack, $this->urlGenerator))
->setMethods(array('batchGet', 'drupalInstallationAttempted'))
->getMock();
$this->setupFormBuilder();
$this->formBuilder = new TestFormBuilder($this->formValidator, $this->formSubmitter, $this->moduleHandler, $this->keyValueExpirableFactory, $this->eventDispatcher, $this->requestStack, $this->csrfToken, $this->httpKernel);
$this->formBuilder->setCurrentUser($this->account);
}
/**
@ -139,16 +161,6 @@ abstract class FormTestBase extends UnitTestCase {
$this->formBuilder->drupalStaticReset();
}
/**
* Sets up a new form builder object to test.
*/
protected function setupFormBuilder() {
$request_stack = new RequestStack();
$request_stack->push($this->request);
$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);
}
/**
* Provides a mocked form object.
*
@ -276,13 +288,6 @@ class TestFormBuilder extends FormBuilder {
return $types[$type];
}
/**
* {@inheritdoc}
*/
protected function drupalInstallationAttempted() {
return FALSE;
}
/**
* {@inheritdoc}
*/
@ -310,14 +315,6 @@ class TestFormBuilder extends FormBuilder {
static::$seenIds = array();
}
/**
* {@inheritdoc}
*/
protected function &batchGet() {
$batch = array();
return $batch;
}
}
}