public function FormBuilder::buildForm in Drupal 10
Same name and namespace in other branches
- 8 core/lib/Drupal/Core/Form/FormBuilder.php \Drupal\Core\Form\FormBuilder::buildForm()
- 9 core/lib/Drupal/Core/Form/FormBuilder.php \Drupal\Core\Form\FormBuilder::buildForm()
File
- core/
lib/ Drupal/ Core/ Form/ FormBuilder.php, line 225
Class
- FormBuilder
- Provides form building and processing.
Namespace
Drupal\Core\FormCode
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.
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;
}