function FormBuilder::buildForm

Same name in other branches
  1. 9 core/lib/Drupal/Core/Form/FormBuilder.php \Drupal\Core\Form\FormBuilder::buildForm()
  2. 10 core/lib/Drupal/Core/Form/FormBuilder.php \Drupal\Core\Form\FormBuilder::buildForm()
  3. 11.x core/lib/Drupal/Core/Form/FormBuilder.php \Drupal\Core\Form\FormBuilder::buildForm()

Overrides FormBuilderInterface::buildForm

1 call to FormBuilder::buildForm()
FormBuilder::getForm in core/lib/Drupal/Core/Form/FormBuilder.php
Gets a renderable form array.

File

core/lib/Drupal/Core/Form/FormBuilder.php, line 225

Class

FormBuilder
Provides form building and processing.

Namespace

Drupal\Core\Form

Code

public function buildForm($form_arg, FormStateInterface &$form_state) {
    // Ensure the form ID is prepared.
    $form_id = $this->getFormId($form_arg, $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 && !$request->request
        ->has('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.
    // Only do this when the form ID matches, since there is no guarantee from
    // $ajax_form_request that it's an AJAX request for this particular form.
    if ($ajax_form_request && $form_state->isProcessingInput() && $request->request
        ->get('form_id') == $form_id) {
        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;
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.