public FormBuilder::buildForm($form_id, FormStateInterface &$form_state)
Builds and processes a form for a given form ID.
The form may also be retrieved from the cache if the form was built in a previous page load. The form is then passed on for processing, validation, and submission if there is proper input.
\Drupal\Core\Form\FormInterface|string $form_id: The value must be one of the following:
\Drupal\Core\Form\FormStateInterface $form_state: The current state of the form.
array The rendered form. This function may also perform a redirect and hence may not return at all depending upon the $form_state flags that were set.
\Drupal\Core\Form\FormAjaxException Thrown when a form is triggered via an AJAX submission. It will be handled by \Drupal\Core\Form\EventSubscriber\FormAjaxSubscriber.
\Drupal\Core\Form\EnforcedResponseException Thrown when a form builder returns a response directly, usually a \Symfony\Component\HttpFoundation\RedirectResponse. It will be handled by \Drupal\Core\EventSubscriber\EnforcedFormResponseSubscriber.
Overrides FormBuilderInterface::buildForm
self::redirectForm()
public function buildForm($form_id, FormStateInterface &$form_state) { // Ensure the form ID is prepared. $form_id = $this->getFormId($form_id, $form_state); $request = $this->requestStack->getCurrentRequest(); // Inform $form_state about the request method that's building it, so that // it can prevent persisting state changes during HTTP methods for which // that is disallowed by HTTP: GET and HEAD. $form_state->setRequestMethod($request->getMethod()); // Initialize the form's user input. The user input should include only the // input meant to be treated as part of what is submitted to the form, so // we base it on the form's method rather than the request's method. For // example, when someone does a GET request for // /node/add/article?destination=foo, which is a form that expects its // submission method to be POST, the user input during the GET request // should be initialized to empty rather than to ['destination' => 'foo']. $input = $form_state->getUserInput(); if (!isset($input)) { $input = $form_state->isMethodType('get') ? $request->query->all() : $request->request->all(); $form_state->setUserInput($input); } if (isset($_SESSION['batch_form_state'])) { // We've been redirected here after a batch processing. The form has // already been processed, but needs to be rebuilt. See _batch_finished(). $form_state = $_SESSION['batch_form_state']; unset($_SESSION['batch_form_state']); return $this->rebuildForm($form_id, $form_state); } // If the incoming input contains a form_build_id, we'll check the cache for // a copy of the form in question. If it's there, we don't have to rebuild // the form to proceed. In addition, if there is stored form_state data from // a previous step, we'll retrieve it so it can be passed on to the form // processing code. $check_cache = isset($input['form_id']) && $input['form_id'] == $form_id && !empty($input['form_build_id']); if ($check_cache) { $form = $this->getCache($input['form_build_id'], $form_state); } // If the previous bit of code didn't result in a populated $form object, we // are hitting the form for the first time and we need to build it from // scratch. if (!isset($form)) { // If we attempted to serve the form from cache, uncacheable $form_state // keys need to be removed after retrieving and preparing the form, except // any that were already set prior to retrieving the form. if ($check_cache) { $form_state_before_retrieval = clone $form_state; } $form = $this->retrieveForm($form_id, $form_state); $this->prepareForm($form_id, $form, $form_state); // self::setCache() removes uncacheable $form_state keys (see properties // in \Drupal\Core\Form\FormState) in order for multi-step forms to work // properly. This means that form processing logic for single-step forms // using $form_state->isCached() may depend on data stored in those keys // during self::retrieveForm()/self::prepareForm(), but form processing // should not depend on whether the form is cached or not, so $form_state // is adjusted to match what it would be after a // self::setCache()/self::getCache() sequence. These exceptions are // allowed to survive here: // - always_process: Does not make sense in conjunction with form caching // in the first place, since passing form_build_id as a GET parameter is // not desired. // - temporary: Any assigned data is expected to survives within the same // page request. if ($check_cache) { $cache_form_state = $form_state->getCacheableArray(); $cache_form_state['always_process'] = $form_state->getAlwaysProcess(); $cache_form_state['temporary'] = $form_state->getTemporary(); $form_state = $form_state_before_retrieval; $form_state->setFormState($cache_form_state); } } // If this form is an AJAX request, disable all form redirects. $request = $this->requestStack->getCurrentRequest(); if ($ajax_form_request = $request->query->has(static::AJAX_FORM_REQUEST)) { $form_state->disableRedirect(); } // Now that we have a constructed form, process it. This is where: // - Element #process functions get called to further refine $form. // - User input, if any, gets incorporated in the #value property of the // corresponding elements and into $form_state->getValues(). // - Validation and submission handlers are called. // - If this submission is part of a multistep workflow, the form is rebuilt // to contain the information of the next step. // - If necessary, the form and form state are cached or re-cached, so that // appropriate information persists to the next page request. // All of the handlers in the pipeline receive $form_state by reference and // can use it to know or update information about the state of the form. $response = $this->processForm($form_id, $form, $form_state); // In case the post request exceeds the configured allowed size // (post_max_size), the post request is potentially broken. Add some // protection against that and at the same time have a nice error message. if ($ajax_form_request && !isset($form_state->getUserInput()['form_id'])) { throw new BrokenPostRequestException($this->getFileUploadMaxSize()); } // After processing the form, if this is an AJAX form request, interrupt // form rendering and return by throwing an exception that contains the // processed form and form state. This exception will be caught by // \Drupal\Core\Form\EventSubscriber\FormAjaxSubscriber::onException() and // then passed through // \Drupal\Core\Form\FormAjaxResponseBuilderInterface::buildResponse() to // build a proper AJAX response. if ($ajax_form_request && $form_state->isProcessingInput()) { throw new FormAjaxException($form, $form_state); } // If the form returns a response, skip subsequent page construction by // throwing an exception. // @see Drupal\Core\EventSubscriber\EnforcedFormResponseSubscriber // // @todo Exceptions should not be used for code flow control. However, the // Form API does not integrate with the HTTP Kernel based architecture of // Drupal 8. In order to resolve this issue properly it is necessary to // completely separate form submission from rendering. // @see https://www.drupal.org/node/2367555 if ($response instanceof Response) { throw new EnforcedResponseException($response); } // If this was a successful submission of a single-step form or the last // step of a multi-step form, then self::processForm() issued a redirect to // another page, or back to this page, but as a new request. Therefore, if // we're here, it means that this is either a form being viewed initially // before any user input, or there was a validation error requiring the form // to be re-displayed, or we're in a multi-step workflow and need to display // the form's next step. In any case, we have what we need in $form, and can // return it for rendering. return $form; }
© 2001–2016 by the original authors
Licensed under the GNU General Public License, version 2 and later.
Drupal is a registered trademark of Dries Buytaert.
https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Form!FormBuilder.php/function/FormBuilder::buildForm/8.1.x