From ee6ddbe8acc85103ad210ef0259fbe279067e0b5 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Wed, 10 Sep 2014 09:53:11 +0100
Subject: [PATCH] Issue #2332389 by tim.plunkett: Finish adding methods to
 FormStateInterface.

---
 core/includes/install.core.inc                |  11 +-
 core/lib/Drupal/Core/Block/BlockBase.php      |  10 +-
 .../Drupal/Core/Entity/EntityFormBuilder.php  |   6 +-
 .../Drupal/Core/Form/BaseFormIdInterface.php  |   2 +-
 core/lib/Drupal/Core/Form/FormBuilder.php     | 146 ++---
 .../Drupal/Core/Form/FormBuilderInterface.php |  22 +-
 core/lib/Drupal/Core/Form/FormCache.php       |  12 +-
 core/lib/Drupal/Core/Form/FormState.php       | 561 ++++++++++++++----
 .../Drupal/Core/Form/FormStateInterface.php   | 518 +++++++++++++++-
 core/lib/Drupal/Core/Form/FormSubmitter.php   |  19 +-
 core/lib/Drupal/Core/Form/FormValidator.php   |  47 +-
 .../Core/Form/FormValidatorInterface.php      |   2 +-
 .../AggregatorPluginSettingsBaseTest.php      |   5 +-
 core/modules/block/src/BlockForm.php          |   8 +-
 .../src/Form/ConfigSingleExportForm.php       |   4 +-
 .../image/src/Form/ImageEffectFormBase.php    |   8 +-
 core/modules/menu_ui/src/MenuForm.php         |   5 +-
 .../quickedit/src/QuickEditController.php     |  11 +-
 .../src/Form/SimpletestResultsForm.php        |   2 +-
 .../src/Tests/Element/PathElementFormTest.php |  21 +-
 .../Tests/Form/FormDefaultHandlersTest.php    |   2 +-
 .../src/Tests/Form/ProgrammaticTest.php       |   6 +-
 .../Tests/System/SystemConfigFormTestBase.php |   2 +-
 .../src/Controller/BatchTestController.php    |   6 +-
 core/modules/views/includes/ajax.inc          |  14 +-
 .../exposed_form/ExposedFormPluginBase.php    |  17 +-
 .../Tests/Wizard/WizardPluginBaseUnitTest.php |   4 +-
 .../views_ui/src/Form/Ajax/ViewsFormBase.php  |  24 +-
 .../Tests/Core/Form/FormBuilderTest.php       |  35 +-
 .../Drupal/Tests/Core/Form/FormCacheTest.php  |   2 +-
 .../Drupal/Tests/Core/Form/FormStateTest.php  | 143 ++++-
 .../Tests/Core/Form/FormSubmitterTest.php     |  28 +-
 .../Drupal/Tests/Core/Form/FormTestBase.php   |  17 +-
 .../Tests/Core/Form/FormValidatorTest.php     |  28 +-
 34 files changed, 1323 insertions(+), 425 deletions(-)

diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 67b7dffd1555..7d23bb9223bb 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -806,12 +806,9 @@ function install_tasks_to_display($install_state) {
 function install_get_form($form_id, array &$install_state) {
   // Ensure the form will not redirect, since install_run_tasks() uses a custom
   // redirection logic.
-  $form_state = new FormState(array(
-    'build_info' => array(
-      'args' => array(&$install_state),
-    ),
-    'no_redirect' => TRUE,
-  ));
+  $form_state = (new FormState())
+    ->addBuildInfo('args', [&$install_state])
+    ->disableRedirect();
   $form_builder = \Drupal::formBuilder();
   if ($install_state['interactive']) {
     $form = $form_builder->buildForm($form_id, $form_state);
@@ -827,7 +824,7 @@ function install_get_form($form_id, array &$install_state) {
     // values taken from the installation state.
     $install_form_id = $form_builder->getFormId($form_id, $form_state);
     if (!empty($install_state['forms'][$install_form_id])) {
-      $form_state->set('values', $install_state['forms'][$install_form_id]);
+      $form_state->setValues($install_state['forms'][$install_form_id]);
     }
     $form_builder->submitForm($form_id, $form_state);
 
diff --git a/core/lib/Drupal/Core/Block/BlockBase.php b/core/lib/Drupal/Core/Block/BlockBase.php
index 7d66cf4cac0d..c9db77a2054f 100644
--- a/core/lib/Drupal/Core/Block/BlockBase.php
+++ b/core/lib/Drupal/Core/Block/BlockBase.php
@@ -356,9 +356,8 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
 
     foreach ($this->getVisibilityConditions() as $condition_id => $condition) {
       // Allow the condition to validate the form.
-      $condition_values = new FormState(array(
-        'values' => $form_state->getValue(array('visibility', $condition_id)),
-      ));
+      $condition_values = (new FormState())
+        ->setValues($form_state->getValue(['visibility', $condition_id]));
       $condition->validateConfigurationForm($form, $condition_values);
       // Update the original form values.
       $form_state->setValue(array('visibility', $condition_id), $condition_values['values']);
@@ -389,9 +388,8 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
       $this->configuration['cache'] = $form_state->getValue('cache');
       foreach ($this->getVisibilityConditions() as $condition_id => $condition) {
         // Allow the condition to submit the form.
-        $condition_values = new FormState(array(
-          'values' => $form_state->getValue(array('visibility', $condition_id)),
-        ));
+        $condition_values = (new FormState())
+          ->setValues($form_state->getValue(['visibility', $condition_id]));
         $condition->submitConfigurationForm($form, $condition_values);
         // Update the original form values.
         $form_state->setValue(array('visibility', $condition_id), $condition_values['values']);
diff --git a/core/lib/Drupal/Core/Entity/EntityFormBuilder.php b/core/lib/Drupal/Core/Entity/EntityFormBuilder.php
index 3a825be7b1ab..e12d27905c07 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormBuilder.php
@@ -49,11 +49,7 @@ public function getForm(EntityInterface $entity, $operation = 'default', array $
     $form_object = $this->entityManager->getFormObject($entity->getEntityTypeId(), $operation);
     $form_object->setEntity($entity);
 
-    $form_state = new FormState($form_state_additions);
-    $form_state['build_info']['callback_object'] = $form_object;
-    $form_state['build_info']['base_form_id'] = $form_object->getBaseFormID();
-    $form_state['build_info'] += array('args' => array());
-
+    $form_state = (new FormState())->setFormState($form_state_additions);
     return $this->formBuilder->buildForm($form_object, $form_state);
   }
 
diff --git a/core/lib/Drupal/Core/Form/BaseFormIdInterface.php b/core/lib/Drupal/Core/Form/BaseFormIdInterface.php
index 3576758e84d3..c6710c2119da 100644
--- a/core/lib/Drupal/Core/Form/BaseFormIdInterface.php
+++ b/core/lib/Drupal/Core/Form/BaseFormIdInterface.php
@@ -10,7 +10,7 @@
 /**
  * Provides an interface for a Form that has a base form ID.
  *
- * This will become the $form_state['build_info']['base_form_id'] used to
+ * This will become the $form_state->getBaseInfo()['base_form_id'] used to
  * generate the name of hook_form_BASE_FORM_ID_alter().
  */
 interface BaseFormIdInterface extends FormInterface {
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index 3417549bb284..9fb4112b4cac 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -157,7 +157,7 @@ public function getFormId($form_arg, FormStateInterface &$form_state) {
     }
 
     // Add the $form_arg as the callback object and determine the form ID.
-    $form_state->addBuildInfo('callback_object', $form_arg);
+    $form_state->setFormObject($form_arg);
     if ($form_arg instanceof BaseFormIdInterface) {
       $form_state->addBuildInfo('base_form_id', $form_arg->getBaseFormID());
     }
@@ -188,7 +188,7 @@ public function buildForm($form_id, FormStateInterface &$form_state) {
     $input = $form_state->getUserInput();
     if (!isset($input)) {
       $request = $this->requestStack->getCurrentRequest();
-      $input = $form_state['method'] == 'get' ? $request->query->all() : $request->request->all();
+      $input = $form_state->isMethodType('get') ? $request->query->all() : $request->request->all();
       $form_state->setUserInput($input);
     }
 
@@ -227,7 +227,7 @@ public function buildForm($form_id, FormStateInterface &$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['cache'] may depend on data stored in those keys
+      // 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
@@ -239,7 +239,9 @@ public function buildForm($form_id, FormStateInterface &$form_state) {
       // - temporary: Any assigned data is expected to survives within the same
       //   page request.
       if ($check_cache) {
-        $cache_form_state = $form_state->getCacheableArray(array('always_process', 'temporary'));
+        $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);
       }
@@ -280,6 +282,8 @@ public function buildForm($form_id, FormStateInterface &$form_state) {
    */
   public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form = NULL) {
     $form = $this->retrieveForm($form_id, $form_state);
+    // All rebuilt forms will be cached.
+    $form_state->setCached();
 
     // If only parts of the form will be returned to the browser (e.g., Ajax or
     // RIA clients), re-use the old #build_id to not require client-side code to
@@ -288,7 +292,8 @@ public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form
     // build's data in the form cache; also allowing the user to go back to an
     // earlier build, make changes, and re-submit.
     // @see self::prepareForm()
-    if (isset($old_form['#build_id']) && !empty($form_state['rebuild_info']['copy']['#build_id'])) {
+    $rebuild_info = $form_state->getRebuildInfo();
+    if (isset($old_form['#build_id']) && !empty($rebuild_info['copy']['#build_id'])) {
       $form['#build_id'] = $old_form['#build_id'];
     }
     else {
@@ -298,7 +303,7 @@ public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form
     // #action defaults to request_uri(), but in case of Ajax and other partial
     // rebuilds, the form is submitted to an alternate URL, and the original
     // #action needs to be retained.
-    if (isset($old_form['#action']) && !empty($form_state['rebuild_info']['copy']['#action'])) {
+    if (isset($old_form['#action']) && !empty($rebuild_info['copy']['#action'])) {
       $form['#action'] = $old_form['#action'];
     }
 
@@ -308,13 +313,13 @@ public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form
     // cached is the $form structure before it passes through
     // self::doBuildForm(), so we need to do it here.
     // @todo For Drupal 8, find a way to avoid this code duplication.
-    if (empty($form_state['no_cache'])) {
+    if ($form_state->isCached()) {
       $this->setCache($form['#build_id'], $form, $form_state);
     }
 
     // Clear out all group associations as these might be different when
     // re-rendering the form.
-    $form_state->set('groups', array());
+    $form_state->setGroups([]);
 
     // Return a fully built form that is ready for rendering.
     return $this->doBuildForm($form_id, $form, $form_state);
@@ -338,7 +343,8 @@ public function setCache($form_build_id, $form, FormStateInterface $form_state)
    * {@inheritdoc}
    */
   public function submitForm($form_arg, FormStateInterface &$form_state) {
-    if (!isset($form_state['build_info']['args'])) {
+    $build_info = $form_state->getBuildInfo();
+    if (empty($build_info['args'])) {
       $args = func_get_args();
       // Remove $form and $form_state from the arguments.
       unset($args[0], $args[1]);
@@ -351,15 +357,15 @@ public function submitForm($form_arg, FormStateInterface &$form_state) {
     // there).
     $form_state->setUserInput($form_state->getValues());
 
-    $form_state->set('programmed', TRUE);
+    $form_state->setProgrammed();
 
     $form_id = $this->getFormId($form_arg, $form_state);
     $form = $this->retrieveForm($form_id, $form_state);
     // Programmed forms are always submitted.
-    $form_state->set('submitted', TRUE);
+    $form_state->setSubmitted();
 
     // Reset form validation.
-    $form_state->set('must_validate', TRUE);
+    $form_state->setValidationEnforced();
     $form_state->clearErrors();
 
     $this->prepareForm($form_id, $form, $form_state);
@@ -376,9 +382,10 @@ public function retrieveForm($form_id, FormStateInterface &$form_state) {
     // We save two copies of the incoming arguments: one for modules to use
     // when mapping form ids to constructor functions, and another to pass to
     // the constructor function itself.
-    $args = $form_state['build_info']['args'];
+    $build_info = $form_state->getBuildInfo();
+    $args = $build_info['args'];
 
-    $callback = array($form_state['build_info']['callback_object'], 'buildForm');
+    $callback = [$form_state->getFormObject(), 'buildForm'];
 
     $form = array();
     // Assign a default CSS class name based on $form_id.
@@ -386,8 +393,8 @@ public function retrieveForm($form_id, FormStateInterface &$form_state) {
     // form constructor function to override or remove the default class.
     $form['#attributes']['class'][] = Html::getClass($form_id);
     // Same for the base form ID, if any.
-    if (isset($form_state['build_info']['base_form_id'])) {
-      $form['#attributes']['class'][] = Html::getClass($form_state['build_info']['base_form_id']);
+    if (isset($build_info['base_form_id'])) {
+      $form['#attributes']['class'][] = Html::getClass($build_info['base_form_id']);
     }
 
     // We need to pass $form_state by reference in order for forms to modify it,
@@ -410,10 +417,10 @@ public function retrieveForm($form_id, FormStateInterface &$form_state) {
    * {@inheritdoc}
    */
   public function processForm($form_id, &$form, FormStateInterface &$form_state) {
-    $form_state->set('values', array());
+    $form_state->setValues([]);
 
     // With GET, these forms are always submitted if requested.
-    if ($form_state['method'] == 'get' && !empty($form_state['always_process'])) {
+    if ($form_state->isMethodType('get') && $form_state->getAlwaysProcess()) {
       $input = $form_state->getUserInput();
       if (!isset($input['form_build_id'])) {
         $input['form_build_id'] = $form['#build_id'];
@@ -435,7 +442,7 @@ public function processForm($form_id, &$form, FormStateInterface &$form_state) {
     $form = $this->doBuildForm($form_id, $form, $form_state);
 
     // Only process the input if we have a correct form submission.
-    if ($form_state['process_input']) {
+    if ($form_state->isProcessingInput()) {
       // Form constructors may explicitly set #token to FALSE when cross site
       // request forgery is irrelevant to the form, such as search forms.
       if (isset($form['#token']) && $form['#token'] === FALSE) {
@@ -447,8 +454,9 @@ public function processForm($form_id, &$form, FormStateInterface &$form_state) {
       // submit button is not taken account. Therefore, check whether there is
       // exactly one submit button in the form, and if so, automatically use it
       // as triggering_element.
-      if ($form_state['programmed'] && !isset($form_state['triggering_element']) && count($form_state['buttons']) == 1) {
-        $form_state->set('triggering_element', reset($form_state['buttons']));
+      $buttons = $form_state->getButtons();
+      if ($form_state->isProgrammed() && !$form_state->getTriggeringElement() && count($buttons) == 1) {
+        $form_state->setTriggeringElement(reset($buttons));
       }
       $this->formValidator->validateForm($form_id, $form, $form_state);
 
@@ -462,35 +470,35 @@ public function processForm($form_id, &$form, FormStateInterface &$form_state) {
         Html::resetSeenIds();
       }
 
-      if (!$form_state['rebuild'] && !FormState::hasAnyErrors()) {
+      if (!$form_state->isRebuilding() && !FormState::hasAnyErrors()) {
         if ($submit_response = $this->formSubmitter->doSubmitForm($form, $form_state)) {
           return $submit_response;
         }
       }
 
       // Don't rebuild or cache form submissions invoked via self::submitForm().
-      if (!empty($form_state['programmed'])) {
+      if ($form_state->isProgrammed()) {
         return;
       }
 
-      // If $form_state['rebuild'] has been set and input has been processed
+      // If $form_state->isRebuilding() has been set and input has been processed
       // without validation errors, we are in a multi-step workflow that is not
       // yet complete. A new $form needs to be constructed based on the changes
       // made to $form_state during this request. Normally, a submit handler
-      // sets $form_state['rebuild'] if a fully executed form requires another
-      // step. However, for forms that have not been fully executed (e.g., Ajax
-      // submissions triggered by non-buttons), there is no submit handler to
-      // set $form_state['rebuild']. It would not make sense to redisplay the
-      // identical form without an error for the user to correct, so we also
-      // rebuild error-free non-executed forms, regardless of
-      // $form_state['rebuild'].
+      // sets $form_state->isRebuilding() if a fully executed form requires
+      // another step. However, for forms that have not been fully executed
+      // (e.g., Ajax submissions triggered by non-buttons), there is no submit
+      // handler to set $form_state->isRebuilding(). It would not make sense to
+      // redisplay the identical form without an error for the user to correct,
+      // so we also rebuild error-free non-executed forms, regardless of
+      // $form_state->isRebuilding().
       // @todo Simplify this logic; considering Ajax and non-HTML front-ends,
       //   along with element-level #submit properties, it makes no sense to
       //   have divergent form execution based on whether the triggering element
       //   has #executes_submit_callback set to TRUE.
-      if (($form_state['rebuild'] || !$form_state['executed']) && !FormState::hasAnyErrors()) {
+      if (($form_state->isRebuilding() || !$form_state->isExecuted()) && !FormState::hasAnyErrors()) {
         // Form building functions (e.g., self::handleInputElement()) may use
-        // $form_state['rebuild'] to determine if they are running in the
+        // $form_state->isRebuilding() to determine if they are running in the
         // context of a rebuild, so ensure it is set.
         $form_state->setRebuild();
         $form = $this->rebuildForm($form_id, $form_state, $form);
@@ -498,13 +506,13 @@ public function processForm($form_id, &$form, FormStateInterface &$form_state) {
     }
 
     // After processing the form, the form builder or a #process callback may
-    // have set $form_state['cache'] to indicate that the form and form state
-    // shall be cached. But the form may only be cached if the 'no_cache'
-    // property is not set to TRUE. Only cache $form as it was prior to
-    // self::doBuildForm(), because self::doBuildForm() must run for each
-    // request to accommodate new user input. Rebuilt forms are not cached here,
-    // because self::rebuildForm() already takes care of that.
-    if (!$form_state['rebuild'] && $form_state['cache'] && empty($form_state['no_cache'])) {
+    // have called $form_state->setCached() to indicate that the form and form
+    // state shall be cached. But the form may only be cached if
+    // $form_state->disableCache() is not called. Only cache $form as it was
+    // prior to self::doBuildForm(), because self::doBuildForm() must run for
+    // each request to accommodate new user input. Rebuilt forms are not cached
+    // here, because self::rebuildForm() already takes care of that.
+    if (!$form_state->isRebuilding() && $form_state->isCached()) {
       $this->setCache($form['#build_id'], $unprocessed_form, $form_state);
     }
   }
@@ -516,10 +524,9 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
     $user = $this->currentUser();
 
     $form['#type'] = 'form';
-    $form_state->set('programmed', isset($form_state['programmed']) ? $form_state['programmed'] : FALSE);
 
     // Fix the form method, if it is 'get' in $form_state, but not in $form.
-    if ($form_state->get('method') == 'get' && !isset($form['#method'])) {
+    if ($form_state->isMethodType('get') && !isset($form['#method'])) {
       $form['#method'] = 'get';
     }
 
@@ -551,7 +558,7 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
     // since tokens are session-bound and forms displayed to anonymous users are
     // very likely cached, we cannot assign a token for them.
     // During installation, there is no $user yet.
-    if ($user && $user->isAuthenticated() && !$form_state['programmed']) {
+    if ($user && $user->isAuthenticated() && !$form_state->isProgrammed()) {
       // Form constructors may explicitly set #token to FALSE when cross site
       // request forgery is irrelevant to the form, such as search forms.
       if (isset($form['#token']) && $form['#token'] === FALSE) {
@@ -592,22 +599,23 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
     $form['#validate'][] = '::validateForm';
     $form['#submit'][] = '::submitForm';
 
+    $build_info = $form_state->getBuildInfo();
     // If no #theme has been set, automatically apply theme suggestions.
     // theme_form() itself is in #theme_wrappers and not #theme. Therefore, the
     // #theme function only has to care for rendering the inner form elements,
     // not the form itself.
     if (!isset($form['#theme'])) {
       $form['#theme'] = array($form_id);
-      if (isset($form_state['build_info']['base_form_id'])) {
-        $form['#theme'][] = $form_state['build_info']['base_form_id'];
+      if (isset($build_info['base_form_id'])) {
+        $form['#theme'][] = $build_info['base_form_id'];
       }
     }
 
     // Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and
     // hook_form_FORM_ID_alter() implementations.
     $hooks = array('form');
-    if (isset($form_state['build_info']['base_form_id'])) {
-      $hooks[] = 'form_' . $form_state['build_info']['base_form_id'];
+    if (isset($build_info['base_form_id'])) {
+      $hooks[] = 'form_' . $build_info['base_form_id'];
     }
     $hooks[] = 'form_' . $form_id;
     $this->moduleHandler->alter($hooks, $form, $form_state, $form_id);
@@ -690,11 +698,11 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
       // for programmed forms coming from self::submitForm(), or if the form_id
       // coming from the POST data is set and matches the current form_id.
       $input = $form_state->getUserInput();
-      if ($form_state['programmed'] || (!empty($input) && (isset($input['form_id']) && ($input['form_id'] == $form_id)))) {
-        $form_state->set('process_input', TRUE);
+      if ($form_state->isProgrammed() || (!empty($input) && (isset($input['form_id']) && ($input['form_id'] == $form_id)))) {
+        $form_state->setProcessInput();
       }
       else {
-        $form_state->set('process_input', FALSE);
+        $form_state->setProcessInput(FALSE);
       }
 
       // All form elements should have an #array_parents property.
@@ -791,14 +799,14 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
     // If there is a file element, we need to flip a flag so later the
     // form encoding can be set.
     if (isset($element['#type']) && $element['#type'] == 'file') {
-      $form_state->set('has_file_element', TRUE);
+      $form_state->setHasFileElement();
     }
 
     // Final tasks for the form element after self::doBuildForm() has run for
     // all other elements.
     if (isset($element['#type']) && $element['#type'] == 'form') {
       // If there is a file element, we set the form encoding.
-      if (isset($form_state['has_file_element'])) {
+      if ($form_state->hasFileElement()) {
         $element['#attributes']['enctype'] = 'multipart/form-data';
       }
 
@@ -808,24 +816,26 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
       // though the user clicked the first button. Therefore, to be as
       // consistent as we can be across browsers, if no 'triggering_element' has
       // been identified yet, default it to the first button.
-      if (!$form_state['programmed'] && !isset($form_state['triggering_element']) && !empty($form_state['buttons'])) {
-        $form_state->set('triggering_element', $form_state['buttons'][0]);
+      $buttons = $form_state->getButtons();
+      if (!$form_state->isProgrammed() && !$form_state->getTriggeringElement() && !empty($buttons)) {
+        $form_state->setTriggeringElement($buttons[0]);
       }
 
-      $triggering_element = $form_state->get('triggering_element');
+      $triggering_element = $form_state->getTriggeringElement();
       // If the triggering element specifies "button-level" validation and
       // submit handlers to run instead of the default form-level ones, then add
       // those to the form state.
-      foreach (array('validate', 'submit') as $type) {
-        if (isset($triggering_element['#' . $type])) {
-          $form_state->set($type . '_handlers', $triggering_element['#' . $type]);
-        }
+      if (isset($triggering_element['#validate'])) {
+        $form_state->setValidateHandlers($triggering_element['#validate']);
+      }
+      if (isset($triggering_element['#submit'])) {
+        $form_state->setSubmitHandlers($triggering_element['#submit']);
       }
 
       // If the triggering element executes submit handlers, then set the form
       // state key that's needed for those handlers to run.
       if (!empty($triggering_element['#executes_submit_callback'])) {
-        $form_state->set('submitted', TRUE);
+        $form_state->setSubmitted();
       }
 
       // Special processing if the triggering element is a button.
@@ -899,7 +909,7 @@ protected function handleInputElement($form_id, &$element, FormStateInterface &$
     // #access=FALSE on an element usually allow access for some users, so forms
     // submitted with self::submitForm() may bypass access restriction and be
     // treated as high-privilege users instead.
-    $process_input = empty($element['#disabled']) && (($form_state['programmed'] && $form_state['programmed_bypass_access_check']) || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access'])));
+    $process_input = empty($element['#disabled']) && (($form_state->isProgrammed() && $form_state->isBypassingProgrammedAccessChecks()) || ($form_state->isProcessingInput() && (!isset($element['#access']) || $element['#access'])));
 
     // Set the element's #value property.
     if (!isset($element['#value']) && !array_key_exists('#value', $element)) {
@@ -925,7 +935,7 @@ protected function handleInputElement($form_id, &$element, FormStateInterface &$
         // use default values for the latter, if required. Programmatically
         // submitted forms can submit explicit NULL values when calling
         // self::submitForm() so we do not modify FormState::$input for them.
-        if (!$input_exists && !$form_state['rebuild'] && !$form_state['programmed']) {
+        if (!$input_exists && !$form_state->isRebuilding() && !$form_state->isProgrammed()) {
           // Add the necessary parent keys to FormState::$input and sets the
           // element's input value to NULL.
           NestedArray::setValue($form_state->getUserInput(), $element['#parents'], NULL);
@@ -967,7 +977,7 @@ protected function handleInputElement($form_id, &$element, FormStateInterface &$
     if ($process_input) {
       // Detect if the element triggered the submission via Ajax.
       if ($this->elementTriggeredScriptedSubmission($element, $form_state)) {
-        $form_state->set('triggering_element', $element);
+        $form_state->setTriggeringElement($element);
       }
 
       // If the form was submitted by the browser rather than via Ajax, then it
@@ -979,11 +989,11 @@ protected function handleInputElement($form_id, &$element, FormStateInterface &$
         // form_state_values_clean() and for the self::doBuildForm() code that
         // handles a form submission containing no button information in
         // \Drupal::request()->request.
-        $buttons = $form_state->get('buttons');
+        $buttons = $form_state->getButtons();
         $buttons[] = $element;
-        $form_state->set('buttons', $buttons);
+        $form_state->setButtons($buttons);
         if ($this->buttonWasClicked($element, $form_state)) {
-          $form_state->set('triggering_element', $element);
+          $form_state->setTriggeringElement($element);
         }
       }
     }
@@ -1031,10 +1041,10 @@ protected function elementTriggeredScriptedSubmission($element, FormStateInterfa
    * textfield (self::doBuildForm() has extra code for that).
    *
    * Because this function contains only part of the logic needed to determine
-   * $form_state['triggering_element'], it should not be called from anywhere
+   * $form_state->getTriggeringElement(), it should not be called from anywhere
    * other than within the Form API. Form validation and submit handlers needing
    * to know which button was clicked should get that information from
-   * $form_state['triggering_element'].
+   * $form_state->getTriggeringElement().
    */
   protected function buttonWasClicked($element, FormStateInterface &$form_state) {
     // First detect normal 'vanilla' button clicks. Traditionally, all standard
diff --git a/core/lib/Drupal/Core/Form/FormBuilderInterface.php b/core/lib/Drupal/Core/Form/FormBuilderInterface.php
index 3e97b495e867..43d2e9fcfcc9 100644
--- a/core/lib/Drupal/Core/Form/FormBuilderInterface.php
+++ b/core/lib/Drupal/Core/Form/FormBuilderInterface.php
@@ -42,7 +42,7 @@ public function getFormId($form_arg, FormStateInterface &$form_state);
    *   function. For example, the node_edit form requires that a node object is
    *   passed in here when it is called. These are available to implementations
    *   of hook_form_alter() and hook_form_FORM_ID_alter() as the array
-   *   $form_state['build_info']['args'].
+   *   $form_state->getBuildInfo()['args'].
    *
    * @return array
    *   The form array.
@@ -79,10 +79,10 @@ public function buildForm($form_id, FormStateInterface &$form_state);
    * This is the key function for making multi-step forms advance from step to
    * step. It is called by self::processForm() when all user input processing,
    * including calling validation and submission handlers, for the request is
-   * finished. If a validate or submit handler set $form_state['rebuild'] to
-   * TRUE, and if other conditions don't preempt a rebuild from happening, then
-   * this function is called to generate a new $form, the next step in the form
-   * workflow, to be returned for rendering.
+   * finished. If a validate or submit handler set $form_state->isRebuilding()
+   * to TRUE, and if other conditions don't preempt a rebuild from happening,
+   * then this function is called to generate a new $form, the next step in the
+   * form workflow, to be returned for rendering.
    *
    * Ajax form submissions are almost always multi-step workflows, so that is
    * one common use-case during which form rebuilding occurs. See
@@ -98,7 +98,7 @@ public function buildForm($form_id, FormStateInterface &$form_state);
    *   (optional) A previously built $form. Used to retain the #build_id and
    *   #action properties in Ajax callbacks and similar partial form rebuilds.
    *   The only properties copied from $old_form are the ones which both exist
-   *   in $old_form and for which $form_state['rebuild_info']['copy'][PROPERTY]
+   *   in $old_form and for which $form_state->getRebuildInfo()['copy'][PROPERTY]
    *   is TRUE. If $old_form is not passed, the entire $form is rebuilt freshly.
    *   'rebuild_info' needs to be a separate top-level property next to
    *   'build_info', since the contained data must not be cached.
@@ -148,8 +148,8 @@ public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form
    *   @endcode
    *   would be called via self::submitForm() as follows:
    *   @code
-   *   $form_state->set('values', $my_form_values);
-   *   $form_state['build_info']['args'] = array(&$object);
+   *   $form_state->setValues($my_form_values);
+   *   $form_state->addBuildInfo('args', [&$object]);
    *   drupal_form_submit('mymodule_form', $form_state);
    *   @endcode
    * For example:
@@ -161,7 +161,7 @@ public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form
    * $values['pass']['pass1'] = 'password';
    * $values['pass']['pass2'] = 'password';
    * $values['op'] = t('Create new account');
-   * $form_state->set('values', $values);
+   * $form_state->setValues($values);
    * drupal_form_submit('user_register_form', $form_state);
    * @endcode
    */
@@ -293,8 +293,8 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state);
    *   the next submission needs to be processed, a multi-step workflow is
    *   needed. This is most commonly implemented with a submit handler setting
    *   persistent data within $form_state based on *validated* values in
-   *   $form_state->getValues() and setting $form_state['rebuild']. The form
-   *   building functions must then be implemented to use the $form_state data
+   *   $form_state->getValues() and checking $form_state->isRebuilding(). The
+   *   form building functions must then be implemented to use the $form_state
    *   to rebuild the form with the structure appropriate for the new state.
    * - Where user input must affect the rendering of the form without affecting
    *   its structure, the necessary conditional rendering logic should reside
diff --git a/core/lib/Drupal/Core/Form/FormCache.php b/core/lib/Drupal/Core/Form/FormCache.php
index 9ba893de278a..93da48c45702 100644
--- a/core/lib/Drupal/Core/Form/FormCache.php
+++ b/core/lib/Drupal/Core/Form/FormCache.php
@@ -95,8 +95,9 @@ protected function loadCachedFormState($form_build_id, FormStateInterface $form_
 
       // If the original form is contained in include files, load the files.
       // @see \Drupal\Core\Form\FormStateInterface::loadInclude()
-      $form_state['build_info'] += array('files' => array());
-      foreach ($form_state['build_info']['files'] as $file) {
+      $build_info = $form_state->getBuildInfo();
+      $build_info += ['files' => []];
+      foreach ($build_info['files'] as $file) {
         if (is_array($file)) {
           $file += array('type' => 'inc', 'name' => $file['module']);
           $this->moduleHandler->loadInclude($file['module'], $file['type'], $file['name']);
@@ -109,9 +110,10 @@ protected function loadCachedFormState($form_build_id, FormStateInterface $form_
       // for this request.
       // @todo Ensure we are not storing an excessively large string list
       //   in: https://www.drupal.org/node/2295823
-      $form_state['build_info'] += array('safe_strings' => array());
-      SafeMarkup::setMultiple($form_state['build_info']['safe_strings']);
-      unset($form_state['build_info']['safe_strings']);
+      $build_info += ['safe_strings' => []];
+      SafeMarkup::setMultiple($build_info['safe_strings']);
+      unset($build_info['safe_strings']);
+      $form_state->setBuildInfo($build_info);
     }
   }
 
diff --git a/core/lib/Drupal/Core/Form/FormState.php b/core/lib/Drupal/Core/Form/FormState.php
index 5bed64a8070d..557ac0f7a7f8 100644
--- a/core/lib/Drupal/Core/Form/FormState.php
+++ b/core/lib/Drupal/Core/Form/FormState.php
@@ -25,13 +25,6 @@ class FormState implements FormStateInterface, \ArrayAccess {
    */
   protected static $anyErrors = FALSE;
 
-  /**
-   * The internal storage of the form state.
-   *
-   * @var array
-   */
-  protected $internalStorage = array();
-
   /**
    * The complete form structure.
    *
@@ -92,12 +85,12 @@ class FormState implements FormStateInterface, \ArrayAccess {
    * re-submit the form). However, if 'rebuild' has been set to TRUE, then a new
    * copy of the form is immediately built and sent to the browser, instead of a
    * redirect. This is used for multi-step forms, such as wizards and
-   * confirmation forms. Normally, $form_state['rebuild'] is set by a submit
-   * handler, since its is usually logic within a submit handler that determines
-   * whether a form is done or requires another step. However, a validation
-   * handler may already set $form_state['rebuild'] to cause the form processing
-   * to bypass submit handlers and rebuild the form instead, even if there are
-   * no validation errors.
+   * confirmation forms. Normally, self::$rebuild is set by a submit handler,
+   * since its is usually logic within a submit handler that determines whether
+   * a form is done or requires another step. However, a validation handler may
+   * already set self::$rebuild to cause the form processing to bypass submit
+   * handlers and rebuild the form instead, even if there are no validation
+   * errors.
    *
    * This property is uncacheable.
    *
@@ -217,10 +210,6 @@ class FormState implements FormStateInterface, \ArrayAccess {
   /**
    * If TRUE and the method is GET, a form_id is not necessary.
    *
-   * This should only be used on RESTful GET forms that do NOT write data, as
-   * this could lead to security issues. It is useful so that searches do not
-   * need to have a form_id in their query arguments to trigger the search.
-   *
    * This property is uncacheable.
    *
    * @var bool
@@ -383,6 +372,13 @@ class FormState implements FormStateInterface, \ArrayAccess {
   /**
    * Stores which errors should be limited during validation.
    *
+   * An array of "sections" within which user input must be valid. If the
+   * element is within one of these sections, the error must be recorded.
+   * Otherwise, it can be suppressed. self::$limit_validation_errors can be an
+   * empty array, in which case all errors are suppressed. For example, a
+   * "Previous" button might want its submit action to be triggered even if none
+   * of the submitted values are valid.
+   *
    * This property is uncacheable.
    *
    * @var array|null
@@ -394,41 +390,367 @@ class FormState implements FormStateInterface, \ArrayAccess {
    *
    * This property is uncacheable.
    *
-   * @var array|null
+   * @var array
    */
-  protected $validate_handlers;
+  protected $validate_handlers = [];
 
   /**
    * Stores the gathered submission handlers.
    *
    * This property is uncacheable.
    *
-   * @var array|null
+   * @var array
    */
-  protected $submit_handlers;
+  protected $submit_handlers = [];
 
   /**
-   * Constructs a \Drupal\Core\Form\FormState object.
-   *
-   * @param array $form_state_additions
-   *   (optional) An associative array used to build the current state of the
-   *   form. Use this to pass additional information to the form, such as the
-   *   langcode. Defaults to an empty array.
+   * {@inheritdoc}
    */
-  public function __construct(array $form_state_additions = array()) {
-    $this->setFormState($form_state_additions);
+  public function setFormState(array $form_state_additions) {
+    foreach ($form_state_additions as $key => $value) {
+      if (property_exists($this, $key)) {
+        $this->{$key} = $value;
+      }
+      else {
+        $this->set($key, $value);
+      }
+    }
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function setFormState(array $form_state_additions) {
-    foreach ($form_state_additions as $key => $value) {
-      $this->set($key, $value);
-    }
+  public function setAlwaysProcess($always_process = TRUE) {
+    $this->always_process = (bool) $always_process;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAlwaysProcess() {
+    return $this->always_process;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setButtons(array $buttons) {
+    $this->buttons = $buttons;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getButtons() {
+    return $this->buttons;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCached($cache = TRUE) {
+    $this->cache = (bool) $cache;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isCached() {
+    return empty($this->no_cache) && $this->cache;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function disableCache() {
+    $this->no_cache = TRUE;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setExecuted() {
+    $this->executed = TRUE;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isExecuted() {
+    return $this->executed;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setGroups(array $groups) {
+    $this->groups = $groups;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &getGroups() {
+    return $this->groups;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setHasFileElement($has_file_element = TRUE) {
+    $this->has_file_element = (bool) $has_file_element;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasFileElement() {
+    return $this->has_file_element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setLimitValidationErrors($limit_validation_errors) {
+    $this->limit_validation_errors = $limit_validation_errors;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLimitValidationErrors() {
+    return $this->limit_validation_errors;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setMethod($method) {
+    $this->method = strtoupper($method);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isMethodType($method_type) {
+    return $this->method === strtoupper($method_type);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setValidationEnforced($must_validate = TRUE) {
+    $this->must_validate = (bool) $must_validate;
     return $this;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isValidationEnforced() {
+    return $this->must_validate;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function disableRedirect($no_redirect = TRUE) {
+    $this->no_redirect = (bool) $no_redirect;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isRedirectDisabled() {
+   return $this->no_redirect;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setProcessInput($process_input = TRUE) {
+    $this->process_input = (bool) $process_input;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isProcessingInput() {
+    return $this->process_input;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setProgrammed($programmed = TRUE) {
+    $this->programmed = (bool) $programmed;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isProgrammed() {
+    return $this->programmed;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setProgrammedBypassAccessCheck($programmed_bypass_access_check = TRUE) {
+    $this->programmed_bypass_access_check = (bool) $programmed_bypass_access_check;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isBypassingProgrammedAccessChecks() {
+    return $this->programmed_bypass_access_check;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRebuildInfo(array $rebuild_info) {
+    $this->rebuild_info = $rebuild_info;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRebuildInfo() {
+    return $this->rebuild_info;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addRebuildInfo($property, $value) {
+    $rebuild_info = $this->getRebuildInfo();
+    $rebuild_info[$property] = $value;
+    $this->setRebuildInfo($rebuild_info);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setStorage(array $storage) {
+    $this->storage = $storage;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &getStorage() {
+    return $this->storage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setSubmitHandlers(array $submit_handlers) {
+    $this->submit_handlers = $submit_handlers;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSubmitHandlers() {
+    return $this->submit_handlers;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setSubmitted() {
+    $this->submitted = TRUE;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isSubmitted() {
+    return $this->submitted;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTemporary(array $temporary) {
+    $this->temporary = $temporary;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTemporary() {
+    return $this->temporary;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTriggeringElement($triggering_element) {
+    $this->triggering_element = $triggering_element;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &getTriggeringElement() {
+    return $this->triggering_element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setValidateHandlers(array $validate_handlers) {
+    $this->validate_handlers = $validate_handlers;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getValidateHandlers() {
+    return $this->validate_handlers;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setValidationComplete($validation_complete = TRUE) {
+    $this->validation_complete = (bool) $validation_complete;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isValidationComplete() {
+    return $this->validation_complete;
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -436,7 +758,7 @@ public function loadInclude($module, $type, $name = NULL) {
     if (!isset($name)) {
       $name = $module;
     }
-    $build_info = $this->get('build_info');
+    $build_info = $this->getBuildInfo();
     if (!isset($build_info['files']["$module:$name.$type"])) {
       // Only add successfully included files to the form state.
       if ($result = $this->moduleLoadInclude($module, $type, $name)) {
@@ -445,7 +767,7 @@ public function loadInclude($module, $type, $name = NULL) {
           'module' => $module,
           'name' => $name,
         );
-        $this->set('build_info', $build_info);
+        $this->setBuildInfo($build_info);
         return $result;
       }
     }
@@ -455,22 +777,20 @@ public function loadInclude($module, $type, $name = NULL) {
   /**
    * {@inheritdoc}
    */
-  public function getCacheableArray($allowed_keys = array()) {
-    $cacheable_array = array(
-      'build_info' => $this->build_info,
-      'response' => $this->response,
+  public function getCacheableArray() {
+    return [
+      'build_info' => $this->getBuildInfo(),
+      'response' => $this->getResponse(),
+      'programmed' => $this->isProgrammed(),
+      'programmed_bypass_access_check' => $this->isBypassingProgrammedAccessChecks(),
+      'process_input' => $this->isProcessingInput(),
+      'has_file_element' => $this->hasFileElement(),
+      'storage' => $this->getStorage(),
+      // Use the properties directly, since self::isCached() combines them and
+      // cannot be relied upon.
       'cache' => $this->cache,
       'no_cache' => $this->no_cache,
-      'programmed' => $this->programmed,
-      'programmed_bypass_access_check' => $this->programmed_bypass_access_check,
-      'process_input' => $this->process_input,
-      'has_file_element' => $this->has_file_element,
-      'storage' => $this->storage,
-    ) + $this->internalStorage;
-    foreach ($allowed_keys as $allowed_key) {
-      $cacheable_array[$allowed_key] = $this->get($allowed_key);
-    }
-    return $cacheable_array;
+    ];
   }
 
   /**
@@ -494,7 +814,7 @@ public function &getCompleteForm() {
    * @deprecated in Drupal 8.0.x, might be removed before Drupal 8.0.0.
    */
   public function offsetExists($offset) {
-    return isset($this->{$offset}) || isset($this->internalStorage[$offset]);
+    return isset($this->{$offset}) || isset($this->storage[$offset]);
   }
 
   /**
@@ -503,7 +823,15 @@ public function offsetExists($offset) {
    * @deprecated in Drupal 8.0.x, might be removed before Drupal 8.0.0.
    */
   public function &offsetGet($offset) {
-    $value = &$this->get($offset);
+    if (property_exists($this, $offset)) {
+      $value = &$this->{$offset};
+    }
+    else {
+      if (!isset($this->storage[$offset])) {
+        $this->storage[$offset] = NULL;
+      }
+      $value = &$this->get($offset);
+    }
     return $value;
   }
 
@@ -513,7 +841,12 @@ public function &offsetGet($offset) {
    * @deprecated in Drupal 8.0.x, might be removed before Drupal 8.0.0.
    */
   public function offsetSet($offset, $value) {
-    $this->set($offset, $value);
+    if (property_exists($this, $offset)) {
+      $this->{$offset} = $value;
+    }
+    else {
+      $this->set($offset, $value);
+    }
   }
 
   /**
@@ -525,65 +858,58 @@ public function offsetUnset($offset) {
     if (property_exists($this, $offset)) {
       $this->{$offset} = NULL;
     }
-    unset($this->internalStorage[$offset]);
+    else {
+      unset($this->storage[$offset]);
+    }
   }
 
   /**
    * {@inheritdoc}
    */
-  public function setIfNotExists($property, $value) {
-    if (!$this->has($property)) {
-      $this->set($property, $value);
-    }
+  public function &get($property) {
+    $value = &NestedArray::getValue($this->storage, (array) $property);
+    return $value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function set($property, $value) {
+    NestedArray::setValue($this->storage, (array) $property, $value, TRUE);
     return $this;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function &get($property) {
-    if (property_exists($this, $property)) {
-      return $this->{$property};
-    }
-    else {
-      if (!isset($this->internalStorage[$property])) {
-        $this->internalStorage[$property] = NULL;
-      }
-      return $this->internalStorage[$property];
-    }
+  public function has($property) {
+    $exists = NULL;
+    NestedArray::getValue($this->storage, (array) $property, $exists);
+    return $exists;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function set($property, $value) {
-    if (property_exists($this, $property)) {
-      $this->{$property} = $value;
-    }
-    else {
-      $this->internalStorage[$property] = $value;
-    }
+  public function setBuildInfo(array $build_info) {
+    $this->build_info = $build_info;
     return $this;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function has($property) {
-    if (property_exists($this, $property)) {
-      return $this->{$property} !== NULL;
-    }
-
-    return array_key_exists($property, $this->internalStorage);
+  public function getBuildInfo() {
+    return $this->build_info;
   }
 
   /**
    * {@inheritdoc}
    */
   public function addBuildInfo($property, $value) {
-    $build_info = $this->get('build_info');
+    $build_info = $this->getBuildInfo();
     $build_info[$property] = $value;
-    $this->set('build_info', $build_info);
+    $this->setBuildInfo($build_info);
     return $this;
   }
 
@@ -621,6 +947,14 @@ public function &getValue($key, $default = NULL) {
     return $value;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function setValues(array $values) {
+    $this->values = $values;
+    return $this;
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -658,7 +992,7 @@ public function isValueEmpty($key) {
   /**
    * {@inheritdoc}
    */
-  public function setValueForElement($element, $value) {
+  public function setValueForElement(array $element, $value) {
     return $this->setValue($element['#parents'], $value);
   }
 
@@ -666,10 +1000,17 @@ public function setValueForElement($element, $value) {
    * {@inheritdoc}
    */
   public function setResponse(Response $response) {
-    $this->set('response', $response);
+    $this->response = $response;
     return $this;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getResponse() {
+    return $this->response;
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -682,7 +1023,7 @@ public function setRedirect($route_name, array $route_parameters = array(), arra
    * {@inheritdoc}
    */
   public function setRedirectUrl(Url $url) {
-    $this->set('redirect', $url);
+    $this->redirect = $url;
     return $this;
   }
 
@@ -692,19 +1033,19 @@ public function setRedirectUrl(Url $url) {
   public function getRedirect() {
     // Skip redirection for form submissions invoked via
     // \Drupal\Core\Form\FormBuilderInterface::submitForm().
-    if ($this->get('programmed')) {
+    if ($this->isProgrammed()) {
       return FALSE;
     }
     // Skip redirection if rebuild is activated.
-    if ($this->get('rebuild')) {
+    if ($this->isRebuilding()) {
       return FALSE;
     }
     // Skip redirection if it was explicitly disallowed.
-    if ($this->get('no_redirect')) {
+    if ($this->isRedirectDisabled()) {
       return FALSE;
     }
 
-    return $this->get('redirect');
+    return $this->redirect;
   }
 
   /**
@@ -728,22 +1069,15 @@ public static function hasAnyErrors() {
    * {@inheritdoc}
    */
   public function setErrorByName($name, $message = '') {
-    if ($this->get('validation_complete')) {
+    if ($this->isValidationComplete()) {
       throw new \LogicException('Form errors cannot be set after form validation has finished.');
     }
 
     $errors = $this->getErrors();
     if (!isset($errors[$name])) {
       $record = TRUE;
-      $limit_validation_errors = $this->get('limit_validation_errors');
+      $limit_validation_errors = $this->getLimitValidationErrors();
       if ($limit_validation_errors !== NULL) {
-        // #limit_validation_errors is an array of "sections" within which user
-        // input must be valid. If the element is within one of these sections,
-        // the error must be recorded. Otherwise, it can be suppressed.
-        // #limit_validation_errors can be an empty array, in which case all
-        // errors are suppressed. For example, a "Previous" button might want
-        // its submit action to be triggered even if none of the submitted
-        // values are valid.
         $record = FALSE;
         foreach ($limit_validation_errors as $section) {
           // Exploding by '][' reconstructs the element's #parents. If the
@@ -761,7 +1095,7 @@ public function setErrorByName($name, $message = '') {
       }
       if ($record) {
         $errors[$name] = $message;
-        $this->set('errors', $errors);
+        $this->errors = $errors;
         static::setAnyErrors();
         if ($message) {
           $this->drupalSetMessage($message, 'error');
@@ -775,7 +1109,7 @@ public function setErrorByName($name, $message = '') {
   /**
    * {@inheritdoc}
    */
-  public function setError(&$element, $message = '') {
+  public function setError(array &$element, $message = '') {
     $this->setErrorByName(implode('][', $element['#parents']), $message);
     return $this;
   }
@@ -784,14 +1118,14 @@ public function setError(&$element, $message = '') {
    * {@inheritdoc}
    */
   public function clearErrors() {
-    $this->set('errors', array());
+    $this->errors = [];
     static::setAnyErrors(FALSE);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getError($element) {
+  public function getError(array $element) {
     if ($errors = $this->getErrors($this)) {
       $parents = array();
       foreach ($element['#parents'] as $parent) {
@@ -808,34 +1142,47 @@ public function getError($element) {
    * {@inheritdoc}
    */
   public function getErrors() {
-    return $this->get('errors');
+    return $this->errors;
   }
 
   /**
    * {@inheritdoc}
    */
   public function setRebuild($rebuild = TRUE) {
-    $this->set('rebuild', $rebuild);
+    $this->rebuild = $rebuild;
     return $this;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isRebuilding() {
+    return $this->rebuild;
+  }
 
   /**
    * {@inheritdoc}
    */
   public function prepareCallback($callback) {
     if (is_string($callback) && substr($callback, 0, 2) == '::') {
-      $callback = array($this->get('build_info')['callback_object'], substr($callback, 2));
+      $callback = [$this->getFormObject(), substr($callback, 2)];
     }
     return $callback;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function setFormObject(FormInterface $form_object) {
+    $this->addBuildInfo('callback_object', $form_object);
+    return $this;
+  }
+
   /**
    * {@inheritdoc}
    */
   public function getFormObject() {
-    $build_info = $this->get('build_info');
-    return $build_info['callback_object'];
+    return $this->getBuildInfo()['callback_object'];
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Form/FormStateInterface.php b/core/lib/Drupal/Core/Form/FormStateInterface.php
index fe8fc91ea474..fd37fcddef0f 100644
--- a/core/lib/Drupal/Core/Form/FormStateInterface.php
+++ b/core/lib/Drupal/Core/Form/FormStateInterface.php
@@ -95,18 +95,6 @@ public function getCacheableArray();
    */
   public function setFormState(array $form_state_additions);
 
-  /**
-   * Sets a value to an arbitrary property if it does not exist yet.
-   *
-   * @param string $property
-   *   The property to use for the value.
-   * @param mixed $value
-   *   The data to store.
-   *
-   * @return $this
-   */
-  public function setIfNotExists($property, $value);
-
   /**
    * Sets a response for this form.
    *
@@ -120,6 +108,17 @@ public function setIfNotExists($property, $value);
    */
   public function setResponse(Response $response);
 
+  /**
+   * Gets a response for this form.
+   *
+   * If a response is set, it will be used during processing and returned
+   * directly. The form will not be rebuilt or redirected.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response|null
+   *   The response to return, or NULL.
+   */
+  public function getResponse();
+
   /**
    * Sets the redirect for the form.
    *
@@ -164,11 +163,32 @@ public function setRedirectUrl(Url $url);
    */
   public function getRedirect();
 
+  /**
+   * Sets the entire set of arbitrary data.
+   *
+   * @param array $storage
+   *   The entire set of arbitrary data to store for this form.
+   *
+   * @return $this
+   */
+  public function setStorage(array $storage);
+
+  /**
+   * Returns the entire set of arbitrary data.
+   *
+   * @return array
+   *   The entire set of arbitrary data to store for this form.
+   */
+  public function &getStorage();
+
   /**
    * Gets any arbitrary property.
    *
-   * @param string $property
-   *   The property to retrieve.
+   * @param string|array $property
+   *   Properties are often stored as multi-dimensional associative arrays. If
+   *   $property is a string, it will return $storage[$property]. If $property
+   *   is an array, each element of the array will be used as a nested key. If
+   *   $property = ['foo', 'bar'] it will return $storage['foo']['bar'].
    *
    * @return mixed
    *   A reference to the value for that property, or NULL if the property does
@@ -179,8 +199,12 @@ public function &get($property);
   /**
    * Sets a value to an arbitrary property.
    *
-   * @param string $property
-   *   The property to use for the value.
+   * @param string|array $property
+   *   Properties are often stored as multi-dimensional associative arrays. If
+   *   $property is a string, it will use $storage[$property] = $value. If
+   *   $property is an array, each element of the array will be used as a nested
+   *   key. If $property = ['foo', 'bar'] it will use
+   *   $storage['foo']['bar'] = $value.
    * @param mixed $value
    *   The value to set.
    *
@@ -189,11 +213,39 @@ public function &get($property);
   public function set($property, $value);
 
   /**
+   * Determines if an arbitrary property is present.
+   *
    * @param string $property
-   *   The property to use for the value.
+   *   Properties are often stored as multi-dimensional associative arrays. If
+   *   $property is a string, it will return isset($storage[$property]). If
+   *   $property is an array, each element of the array will be used as a nested
+   *   key. If $property = ['foo', 'bar'] it will return
+   *   isset($storage['foo']['bar']).
    */
   public function has($property);
 
+  /**
+   * Sets the build info for the form.
+   *
+   * @param array $build_info
+   *   An array of build info.
+   *
+   * @return $this
+   *
+   * @see \Drupal\Core\Form\FormState::$build_info
+   */
+  public function setBuildInfo(array $build_info);
+
+  /**
+   * Returns the build info for the form.
+   *
+   * @return array
+   *   An array of build info.
+   *
+   * @see \Drupal\Core\Form\FormState::$build_info
+   */
+  public function getBuildInfo();
+
   /**
    * Adds a value to the build info.
    *
@@ -252,6 +304,19 @@ public function &getValues();
    */
   public function &getValue($key, $default = NULL);
 
+  /**
+   * Sets the submitted form values.
+   *
+   * This should be avoided, since these values have been validated already. Use
+   * self::setUserInput() instead.
+   *
+   * @param array $values
+   *   The multi-dimensional associative array of form values.
+   *
+   * @return $this
+   */
+  public function setValues(array $values);
+
   /**
    * Sets the submitted form value for a specific key.
    *
@@ -335,7 +400,7 @@ public function isValueEmpty($key);
    *
    * @return $this
    */
-  public function setValueForElement($element, $value);
+  public function setValueForElement(array $element, $value);
 
   /**
    * Determines if any forms have any errors.
@@ -448,7 +513,7 @@ public function setErrorByName($name, $message = '');
    *
    * @return $this
    */
-  public function setError(&$element, $message = '');
+  public function setError(array &$element, $message = '');
 
   /**
    * Clears all errors against all form elements made by self::setErrorByName().
@@ -475,7 +540,7 @@ public function getErrors();
    * @return string|null
    *   Either the error message for this element or NULL if there are no errors.
    */
-  public function getError($element);
+  public function getError(array $element);
 
   /**
    * Sets the form to be rebuilt after processing.
@@ -487,6 +552,14 @@ public function getError($element);
    */
   public function setRebuild($rebuild = TRUE);
 
+  /**
+   * Determines if the form should be rebuilt after processing.
+   *
+   * @return bool
+   *   TRUE if the form should be rebuilt, FALSE otherwise.
+   */
+  public function isRebuilding();
+
   /**
    * Converts support notations for a form callback to a valid callable.
    *
@@ -509,4 +582,409 @@ public function prepareCallback($callback);
    */
   public function getFormObject();
 
+  /**
+   * Sets the form object that is responsible for building this form.
+   *
+   * @param \Drupal\Core\Form\FormInterface $form_object
+   *   The form object.
+   *
+   * @return $this
+   */
+  public function setFormObject(FormInterface $form_object);
+
+  /**
+   * Sets this form to always be processed.
+   *
+   * This should only be used on RESTful GET forms that do NOT write data, as
+   * this could lead to security issues. It is useful so that searches do not
+   * need to have a form_id in their query arguments to trigger the search.
+   *
+   * @param bool $always_process
+   *   TRUE if the form should always be processed, FALSE otherwise.
+   *
+   * @return $this
+   */
+  public function setAlwaysProcess($always_process = TRUE);
+
+  /**
+   * Determines if this form should always be processed.
+   *
+   * @return bool
+   *   TRUE if the form should always be processed, FALSE otherwise.
+   */
+  public function getAlwaysProcess();
+
+  /**
+   * Stores the submit and button elements for the form.
+   *
+   * @param array $buttons
+   *   The submit and button elements.
+   *
+   * @return $this
+   */
+  public function setButtons(array $buttons);
+
+  /**
+   * Returns the submit and button elements for the form.
+   *
+   * @return array
+   *   The submit and button elements.
+   */
+  public function getButtons();
+
+  /**
+   * Sets this form to be cached.
+   *
+   * @param bool $cache
+   *   TRUE if the form should be cached, FALSE otherwise.
+   *
+   * @return $this
+   */
+  public function setCached($cache = TRUE);
+
+  /**
+   * Determines if the form should be cached.
+   *
+   * @return bool
+   *   TRUE if the form should be cached, FALSE otherwise.
+   */
+  public function isCached();
+
+  /**
+   * Prevents the form from being cached.
+   *
+   * @return $this
+   */
+  public function disableCache();
+
+  /**
+   * Sets that the form was submitted and has been processed and executed.
+   *
+   * @return $this
+   */
+  public function setExecuted();
+
+  /**
+   * Determines if the form was submitted and has been processed and executed.
+   *
+   * @return bool
+   *   TRUE if the form was submitted and has been processed and executed.
+   */
+  public function isExecuted();
+
+  /**
+   * Sets references to details elements to render them within vertical tabs.
+   *
+   * @param array $groups
+   *   References to details elements to render them within vertical tabs.
+   *
+   * @return $this
+   */
+  public function setGroups(array $groups);
+
+  /**
+   * Returns references to details elements to render them within vertical tabs.
+   *
+   * @return array
+   */
+  public function getGroups();
+
+  /**
+   * Sets that this form has a file element.
+   *
+   * @param bool $has_file_element
+   *   Whether this form has a file element.
+   *
+   * @return $this
+   */
+  public function setHasFileElement($has_file_element = TRUE);
+
+  /**
+   * Returns whether this form has a file element.
+   *
+   * @return bool
+   *   Whether this form has a file element.
+   */
+  public function hasFileElement();
+
+  /**
+   * Sets the limited validation error sections.
+   *
+   * @param array|null $limit_validation_errors
+   *   The limited validation error sections.
+   *
+   * @return $this
+   *
+   * @see \Drupal\Core\Form\FormState::$limit_validation_errors
+   */
+  public function setLimitValidationErrors($limit_validation_errors);
+
+  /**
+   * Retrieves the limited validation error sections.
+   *
+   * @return array|null
+   *   The limited validation error sections.
+   *
+   * @see \Drupal\Core\Form\FormState::$limit_validation_errors
+   */
+  public function getLimitValidationErrors();
+
+  /**
+   * Sets the HTTP form method.
+   *
+   * @param string $method
+   *   The HTTP form method.
+   *
+   * @see \Drupal\Core\Form\FormState::$method
+   *
+   * @return $this
+   */
+  public function setMethod($method);
+
+  /**
+   * Returns the HTTP form method.
+   *
+   * @param string
+   *   The HTTP form method.
+   *
+   * @return bool
+   *   TRUE if the HTTP form method matches.
+   *
+   * @see \Drupal\Core\Form\FormState::$method
+   */
+  public function isMethodType($method_type);
+
+  /**
+   * Enforces that validation is run.
+   *
+   * @param bool $must_validate
+   *   If TRUE, validation will always be run.
+   *
+   * @return $this
+   */
+  public function setValidationEnforced($must_validate = TRUE);
+
+  /**
+   * Checks if validation is enforced.
+   *
+   * @return bool
+   *   If TRUE, validation will always be run.
+   */
+  public function isValidationEnforced();
+
+  /**
+   * Prevents the form from redirecting.
+   *
+   * @param bool $no_redirect
+   *   If TRUE, the form will not redirect.
+   *
+   * @return $this
+   */
+  public function disableRedirect($no_redirect = TRUE);
+
+  /**
+   * Determines if redirecting has been prevented.
+   *
+   * @return bool
+   *   If TRUE, the form will not redirect.
+   */
+  public function isRedirectDisabled();
+
+  /**
+   * Sets that the form should process input.
+   *
+   * @param bool $process_input
+   *   If TRUE, the form input will be processed.
+   *
+   * @return $this
+   */
+  public function setProcessInput($process_input = TRUE);
+
+  /**
+   * Determines if the form input will be processed.
+   *
+   * @return bool
+   *   If TRUE, the form input will be processed.
+   */
+  public function isProcessingInput();
+
+  /**
+   * Sets that this form was submitted programmatically.
+   *
+   * @param bool $programmed
+   *   If TRUE, the form was submitted programmatically.
+   *
+   * @return $this
+   */
+  public function setProgrammed($programmed = TRUE);
+
+  /**
+   * Returns if this form was submitted programmatically.
+   *
+   * @return bool
+   *   If TRUE, the form was submitted programmatically.
+   */
+  public function isProgrammed();
+
+  /**
+   * Sets if this form submission should bypass #access.
+   *
+   * @param bool $programmed_bypass_access_check
+   *   If TRUE, programmatic form submissions are processed without taking
+   *   #access into account.
+   *
+   * @return $this
+   *
+   * @see \Drupal\Core\Form\FormState::$programmed_bypass_access_check
+   */
+  public function setProgrammedBypassAccessCheck($programmed_bypass_access_check = TRUE);
+
+  /**
+   * Determines if this form submission should bypass #access.
+   *
+   * @return bool
+   *
+   * @see \Drupal\Core\Form\FormState::$programmed_bypass_access_check
+   */
+  public function isBypassingProgrammedAccessChecks();
+
+  /**
+   * Sets the rebuild info.
+   *
+   * @param array $rebuild_info
+   *   The rebuild info.
+   *
+   * @return $this
+   *
+   * @see \Drupal\Core\Form\FormState::$rebuild_info
+   */
+  public function setRebuildInfo(array $rebuild_info);
+
+  /**
+   * Gets the rebuild info.
+   *
+   * @return array
+   *   The rebuild info.
+   *
+   * @see \Drupal\Core\Form\FormState::$rebuild_info
+   */
+  public function getRebuildInfo();
+
+  /**
+   * Adds a value to the rebuild info.
+   *
+   * @param string $property
+   *   The property to use for the value.
+   * @param mixed $value
+   *   The value to set.
+   *
+   * @return $this
+   */
+  public function addRebuildInfo($property, $value);
+
+  /**
+   * Sets the submit handlers.
+   *
+   * @param array $submit_handlers
+   *   An array of submit handlers.
+   *
+   * @return $this
+   */
+  public function setSubmitHandlers(array $submit_handlers);
+
+  /**
+   * Gets the submit handlers.
+   *
+   * @return array
+   *   An array of submit handlers.
+   */
+  public function getSubmitHandlers();
+
+  /**
+   * Sets that the form has been submitted.
+   *
+   * @return $this
+   */
+  public function setSubmitted();
+
+  /**
+   * Determines if the form has been submitted.
+   *
+   * @return bool
+   *   TRUE if the form has been submitted, FALSE otherwise.
+   */
+  public function isSubmitted();
+
+  /**
+   * Sets temporary data.
+   *
+   * @param array $temporary
+   *   Temporary data accessible during the current page request only.
+   *
+   * @return $this
+   */
+  public function setTemporary(array $temporary);
+
+  /**
+   * Gets temporary data.
+   *
+   * @return array
+   *   Temporary data accessible during the current page request only.
+   */
+  public function getTemporary();
+
+  /**
+   * Sets the form element that triggered submission.
+   *
+   * @param array|null $triggering_element
+   *   The form element that triggered submission, of NULL if there is none.
+   *
+   * @return $this
+   */
+  public function setTriggeringElement($triggering_element);
+
+  /**
+   * Gets the form element that triggered submission.
+   *
+   * @return array|null
+   *   The form element that triggered submission, of NULL if there is none.
+   */
+  public function &getTriggeringElement();
+
+  /**
+   * Sets the validate handlers.
+   *
+   * @param array $validate_handlers
+   *   An array of validate handlers.
+   *
+   * @return $this
+   */
+  public function setValidateHandlers(array $validate_handlers);
+
+  /**
+   * Gets the validate handlers.
+   *
+   * @return array
+   *   An array of validate handlers.
+   */
+  public function getValidateHandlers();
+
+  /**
+   * Sets that validation has been completed.
+   *
+   * @param bool $validation_complete
+   *   TRUE if validation is complete, FALSE otherwise.
+   *
+   * @return $this
+   */
+  public function setValidationComplete($validation_complete = TRUE);
+
+  /**
+   * Determines if validation has been completed.
+   *
+   * @return bool
+   *   TRUE if validation is complete, FALSE otherwise.
+   */
+  public function isValidationComplete();
+
 }
diff --git a/core/lib/Drupal/Core/Form/FormSubmitter.php b/core/lib/Drupal/Core/Form/FormSubmitter.php
index ab7e76ef4249..b975a70a5076 100644
--- a/core/lib/Drupal/Core/Form/FormSubmitter.php
+++ b/core/lib/Drupal/Core/Form/FormSubmitter.php
@@ -48,7 +48,7 @@ public function __construct(RequestStack $request_stack, UrlGeneratorInterface $
    * {@inheritdoc}
    */
   public function doSubmitForm(&$form, FormStateInterface &$form_state) {
-    if (!$form_state['submitted']) {
+    if (!$form_state->isSubmitted()) {
       return;
     }
 
@@ -63,7 +63,7 @@ public function doSubmitForm(&$form, FormStateInterface &$form_state) {
       // Store $form_state information in the batch definition.
       $batch['form_state'] = $form_state;
 
-      $batch['progressive'] = !$form_state['programmed'];
+      $batch['progressive'] = !$form_state->isProgrammed();
       $response = batch_process();
       if ($batch['progressive']) {
         return $response;
@@ -76,15 +76,15 @@ public function doSubmitForm(&$form, FormStateInterface &$form_state) {
     }
 
     // Set a flag to indicate the the form has been processed and executed.
-    $form_state['executed'] = TRUE;
+    $form_state->setExecuted();
 
     // If no response has been set, process the form redirect.
-    if (!$form_state->has('response') && $redirect = $this->redirectForm($form_state)) {
+    if (!$form_state->getResponse() && $redirect = $this->redirectForm($form_state)) {
       $form_state->setResponse($redirect);
     }
 
     // If there is a response was set, return it instead of continuing.
-    if (($response = $form_state->get('response')) && $response instanceof Response) {
+    if (($response = $form_state->getResponse()) && $response instanceof Response) {
       return $response;
     }
   }
@@ -94,16 +94,11 @@ public function doSubmitForm(&$form, FormStateInterface &$form_state) {
    */
   public function executeSubmitHandlers(&$form, FormStateInterface &$form_state) {
     // If there was a button pressed, use its handlers.
-    if (!empty($form_state['submit_handlers'])) {
-      $handlers = $form_state['submit_handlers'];
-    }
+    $handlers = $form_state->getSubmitHandlers();
     // Otherwise, check for a form-level handler.
-    elseif (!empty($form['#submit'])) {
+    if (!$handlers && !empty($form['#submit'])) {
       $handlers = $form['#submit'];
     }
-    else {
-      $handlers = array();
-    }
 
     foreach ($handlers as $callback) {
       // Check if a previous _submit handler has set a batch, but make sure we
diff --git a/core/lib/Drupal/Core/Form/FormValidator.php b/core/lib/Drupal/Core/Form/FormValidator.php
index 5efedb089d60..504a606507e3 100644
--- a/core/lib/Drupal/Core/Form/FormValidator.php
+++ b/core/lib/Drupal/Core/Form/FormValidator.php
@@ -68,16 +68,11 @@ public function __construct(RequestStack $request_stack, TranslationInterface $s
    */
   public function executeValidateHandlers(&$form, FormStateInterface &$form_state) {
     // If there was a button pressed, use its handlers.
-    if (isset($form_state['validate_handlers'])) {
-      $handlers = $form_state['validate_handlers'];
-    }
+    $handlers = $form_state->getValidateHandlers();
     // Otherwise, check for a form-level handler.
-    elseif (isset($form['#validate'])) {
+    if (!$handlers && isset($form['#validate'])) {
       $handlers = $form['#validate'];
     }
-    else {
-      $handlers = array();
-    }
 
     foreach ($handlers as $callback) {
       call_user_func_array($form_state->prepareCallback($callback), array(&$form, &$form_state));
@@ -90,12 +85,12 @@ public function executeValidateHandlers(&$form, FormStateInterface &$form_state)
   public function validateForm($form_id, &$form, FormStateInterface &$form_state) {
     // If this form is flagged to always validate, ensure that previous runs of
     // validation are ignored.
-    if (!empty($form_state['must_validate'])) {
-      $form_state['validation_complete'] = FALSE;
+    if ($form_state->isValidationEnforced()) {
+      $form_state->setValidationComplete(FALSE);
     }
 
     // If this form has completed validation, do not validate again.
-    if (!empty($form_state['validation_complete'])) {
+    if ($form_state->isValidationComplete()) {
       return;
     }
 
@@ -138,9 +133,10 @@ public function validateForm($form_id, &$form, FormStateInterface &$form_state)
   protected function handleErrorsWithLimitedValidation(&$form, FormStateInterface &$form_state, $form_id) {
     // If validation errors are limited then remove any non validated form values,
     // so that only values that passed validation are left for submit callbacks.
-    if (isset($form_state['triggering_element']['#limit_validation_errors']) && $form_state['triggering_element']['#limit_validation_errors'] !== FALSE) {
+    $triggering_element = $form_state->getTriggeringElement();
+    if (isset($triggering_element['#limit_validation_errors']) && $triggering_element['#limit_validation_errors'] !== FALSE) {
       $values = array();
-      foreach ($form_state['triggering_element']['#limit_validation_errors'] as $section) {
+      foreach ($triggering_element['#limit_validation_errors'] as $section) {
         // If the section exists within $form_state->getValues(), even if the
         // value is NULL, copy it to $values.
         $section_exists = NULL;
@@ -153,13 +149,13 @@ protected function handleErrorsWithLimitedValidation(&$form, FormStateInterface
       // allow the value of the clicked button to be retained in its normal
       // $form_state->getValues() locations, even if these locations are not
       // included in #limit_validation_errors.
-      if (!empty($form_state['triggering_element']['#is_button'])) {
-        $button_value = $form_state['triggering_element']['#value'];
+      if (!empty($triggering_element['#is_button'])) {
+        $button_value = $triggering_element['#value'];
 
         // Like all input controls, the button value may be in the location
         // dictated by #parents. If it is, copy it to $values, but do not
         // override what may already be in $values.
-        $parents = $form_state['triggering_element']['#parents'];
+        $parents = $triggering_element['#parents'];
         if (!NestedArray::keyExists($values, $parents) && NestedArray::getValue($form_state->getValues(), $parents) === $button_value) {
           NestedArray::setValue($values, $parents, $button_value);
         }
@@ -168,12 +164,12 @@ protected function handleErrorsWithLimitedValidation(&$form, FormStateInterface
         // $form_state->getValue(BUTTON_NAME). If it's still there, after
         // validation handlers have run, copy it to $values, but do not override
         // what may already be in $values.
-        $name = $form_state['triggering_element']['#name'];
+        $name = $triggering_element['#name'];
         if (!isset($values[$name]) && $form_state->getValue($name) === $button_value) {
           $values[$name] = $button_value;
         }
       }
-      $form_state->set('values', $values);
+      $form_state->setValues($values);
     }
   }
 
@@ -191,7 +187,7 @@ protected function finalizeValidation(&$form, FormStateInterface &$form_state, $
     // After validation, loop through and assign each element its errors.
     $this->setElementErrorsFromFormState($form, $form_state);
     // Mark this form as validated.
-    $form_state['validation_complete'] = TRUE;
+    $form_state->setValidationComplete();
   }
 
   /**
@@ -209,7 +205,7 @@ protected function finalizeValidation(&$form, FormStateInterface &$form_state, $
    *   an explicit copy of the values for the sake of simplicity. Validation
    *   handlers can also $form_state to pass information on to submit handlers.
    *   For example:
-   *     $form_state['data_for_submission'] = $data;
+   *     $form_state->set('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.
@@ -233,7 +229,7 @@ protected function doValidateForm(&$elements, FormStateInterface &$form_state, $
       }
 
       // Set up the limited validation for errors.
-      $form_state['limit_validation_errors'] = $this->determineLimitValidationErrors($form_state);
+      $form_state->setLimitValidationErrors($this->determineLimitValidationErrors($form_state));
 
       // Make sure a value is passed when the field is required.
       if (isset($elements['#needs_validation']) && $elements['#required']) {
@@ -293,7 +289,7 @@ protected function doValidateForm(&$elements, FormStateInterface &$form_state, $
     // Done validating this element, so turn off error suppression.
     // self::doValidateForm() turns it on again when starting on the next
     // element, if it's still appropriate to do so.
-    $form_state['limit_validation_errors'] = NULL;
+    $form_state->setLimitValidationErrors(NULL);
   }
 
   /**
@@ -307,7 +303,7 @@ protected function doValidateForm(&$elements, FormStateInterface &$form_state, $
    *   an explicit copy of the values for the sake of simplicity. Validation
    *   handlers can also $form_state to pass information on to submit handlers.
    *   For example:
-   *     $form_state['data_for_submission'] = $data;
+   *     $form_state->set('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.
@@ -374,8 +370,9 @@ protected function determineLimitValidationErrors(FormStateInterface &$form_stat
     // is ignored if submit handlers will run, but the element doesn't have a
     // #submit property, because it's too large a security risk to have any
     // invalid user input when executing form-level submit handlers.
-    if (isset($form_state['triggering_element']['#limit_validation_errors']) && ($form_state['triggering_element']['#limit_validation_errors'] !== FALSE) && !($form_state['submitted'] && !isset($form_state['triggering_element']['#submit']))) {
-      return $form_state['triggering_element']['#limit_validation_errors'];
+    $triggering_element = $form_state->getTriggeringElement();
+    if (isset($triggering_element['#limit_validation_errors']) && ($triggering_element['#limit_validation_errors'] !== FALSE) && !($form_state->isSubmitted() && !isset($triggering_element['#submit']))) {
+      return $triggering_element['#limit_validation_errors'];
     }
     // If submit handlers won't run (due to the submission having been
     // triggered by an element whose #executes_submit_callback property isn't
@@ -386,7 +383,7 @@ protected function determineLimitValidationErrors(FormStateInterface &$form_stat
     // types, #limit_validation_errors defaults to FALSE (via
     // system_element_info()), so that full validation is their default
     // behavior.
-    elseif (isset($form_state['triggering_element']) && !isset($form_state['triggering_element']['#limit_validation_errors']) && !$form_state['submitted']) {
+    elseif ($triggering_element && !isset($triggering_element['#limit_validation_errors']) && !$form_state->isSubmitted()) {
       return array();
     }
     // As an extra security measure, explicitly turn off error suppression if
diff --git a/core/lib/Drupal/Core/Form/FormValidatorInterface.php b/core/lib/Drupal/Core/Form/FormValidatorInterface.php
index 061cf9395c56..6f1cc5289de6 100644
--- a/core/lib/Drupal/Core/Form/FormValidatorInterface.php
+++ b/core/lib/Drupal/Core/Form/FormValidatorInterface.php
@@ -47,7 +47,7 @@ public function executeValidateHandlers(&$form, FormStateInterface &$form_state)
    *   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;
+   *     $form_state->set('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.
diff --git a/core/modules/aggregator/tests/src/Unit/Plugin/AggregatorPluginSettingsBaseTest.php b/core/modules/aggregator/tests/src/Unit/Plugin/AggregatorPluginSettingsBaseTest.php
index c10d3adb8fcb..ed5e0b8835cc 100644
--- a/core/modules/aggregator/tests/src/Unit/Plugin/AggregatorPluginSettingsBaseTest.php
+++ b/core/modules/aggregator/tests/src/Unit/Plugin/AggregatorPluginSettingsBaseTest.php
@@ -74,7 +74,10 @@ protected function setUp() {
    */
   public function testSettingsForm() {
     // Emulate a form state of a sumbitted form.
-    $form_state = new FormState(array('values' => array('dummy_length' => '', 'aggregator_allowed_html_tags' => '')));
+    $form_state = (new FormState())->setValues([
+      'dummy_length' => '',
+      'aggregator_allowed_html_tags' => '',
+    ]);
 
     $test_processor = $this->getMock(
       'Drupal\aggregator_test\Plugin\aggregator\processor\TestProcessor',
diff --git a/core/modules/block/src/BlockForm.php b/core/modules/block/src/BlockForm.php
index bda93320cd77..128abe1db608 100644
--- a/core/modules/block/src/BlockForm.php
+++ b/core/modules/block/src/BlockForm.php
@@ -149,9 +149,7 @@ public function validate(array $form, FormStateInterface $form_state) {
 
     // The Block Entity form puts all block plugin form elements in the
     // settings form element, so just pass that to the block for validation.
-    $settings = new FormState(array(
-      'values' => $form_state->getValue('settings')
-    ));
+    $settings = (new FormState())->setValues($form_state->getValue('settings'));
     // Call the plugin validate handler.
     $this->entity->getPlugin()->validateConfigurationForm($form, $settings);
     // Update the original form values.
@@ -168,9 +166,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     // The Block Entity form puts all block plugin form elements in the
     // settings form element, so just pass that to the block for submission.
     // @todo Find a way to avoid this manipulation.
-    $settings = new FormState(array(
-      'values' => $form_state->getValue('settings'),
-    ));
+    $settings = (new FormState())->setValues($form_state->getValue('settings'));
 
     // Call the plugin submit handler.
     $entity->getPlugin()->submitConfigurationForm($form, $settings);
diff --git a/core/modules/config/src/Form/ConfigSingleExportForm.php b/core/modules/config/src/Form/ConfigSingleExportForm.php
index 6a22b1685416..8e30e6fd121b 100644
--- a/core/modules/config/src/Form/ConfigSingleExportForm.php
+++ b/core/modules/config/src/Form/ConfigSingleExportForm.php
@@ -123,10 +123,10 @@ public function buildForm(array $form, FormStateInterface $form_state, $config_t
       '#suffix' => '</div>',
     );
     if ($config_type && $config_name) {
-      $fake_form_state = new FormState(array('values' => array(
+      $fake_form_state = (new FormState())->setValues([
         'config_type' => $config_type,
         'config_name' => $config_name,
-      )));
+      ]);
       $form['export'] = $this->updateExport($form, $fake_form_state);
     }
     return $form;
diff --git a/core/modules/image/src/Form/ImageEffectFormBase.php b/core/modules/image/src/Form/ImageEffectFormBase.php
index 008e0a550c91..910c781effe2 100644
--- a/core/modules/image/src/Form/ImageEffectFormBase.php
+++ b/core/modules/image/src/Form/ImageEffectFormBase.php
@@ -106,9 +106,7 @@ public function buildForm(array $form, FormStateInterface $form_state, ImageStyl
   public function validateForm(array &$form, FormStateInterface $form_state) {
     // The image effect configuration is stored in the 'data' key in the form,
     // pass that through for validation.
-    $effect_data = new FormState(array(
-      'values' => $form_state->getValue('data'),
-    ));
+    $effect_data = (new FormState())->setValues($form_state->getValue('data'));
     $this->imageEffect->validateConfigurationForm($form, $effect_data);
     // Update the original form values.
     $form_state->setValue('data', $effect_data['values']);
@@ -122,9 +120,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
     // The image effect configuration is stored in the 'data' key in the form,
     // pass that through for submission.
-    $effect_data = new FormState(array(
-      'values' => $form_state->getValue('data'),
-    ));
+    $effect_data = (new FormState())->setValues($form_state->getValue('data'));
     $this->imageEffect->submitConfigurationForm($form, $effect_data);
     // Update the original form values.
     $form_state->setValue('data', $effect_data['values']);
diff --git a/core/modules/menu_ui/src/MenuForm.php b/core/modules/menu_ui/src/MenuForm.php
index fde70bb06514..8684e5950134 100644
--- a/core/modules/menu_ui/src/MenuForm.php
+++ b/core/modules/menu_ui/src/MenuForm.php
@@ -214,7 +214,10 @@ protected function buildOverviewForm(array &$form, FormStateInterface $form_stat
     // section.
     $form['#tree'] = TRUE;
     $form['#theme'] = 'menu_overview_form';
-    $form_state->setIfNotExists('menu_overview_form_parents', array());
+
+    if (!$form_state->has('menu_overview_form_parents')) {
+      $form_state->set('menu_overview_form_parents', []);
+    }
 
     $form['#attached']['css'] = array(drupal_get_path('module', 'menu') . '/css/menu.admin.css');
 
diff --git a/core/modules/quickedit/src/QuickEditController.php b/core/modules/quickedit/src/QuickEditController.php
index c17e4e766c4e..1ccf969ce883 100644
--- a/core/modules/quickedit/src/QuickEditController.php
+++ b/core/modules/quickedit/src/QuickEditController.php
@@ -178,13 +178,10 @@ public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view
       $this->tempStoreFactory->get('quickedit')->set($entity->uuid(), $entity);
     }
 
-    $form_state = new FormState(array(
-      'langcode' => $langcode,
-      'no_redirect' => TRUE,
-      'build_info' => array(
-        'args' => array($entity, $field_name),
-      ),
-    ));
+    $form_state = (new FormState())
+      ->set('langcode', $langcode)
+      ->disableRedirect()
+      ->addBuildInfo('args', [$entity, $field_name]);
     $form = $this->formBuilder()->buildForm('Drupal\quickedit\Form\QuickEditFieldForm', $form_state);
 
     if (!empty($form_state['executed'])) {
diff --git a/core/modules/simpletest/src/Form/SimpletestResultsForm.php b/core/modules/simpletest/src/Form/SimpletestResultsForm.php
index ead1767a3dbe..7f85f65f84b9 100644
--- a/core/modules/simpletest/src/Form/SimpletestResultsForm.php
+++ b/core/modules/simpletest/src/Form/SimpletestResultsForm.php
@@ -271,7 +271,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     }
 
     $form_execute = array();
-    $form_state_execute = new FormState(array('values' => array()));
+    $form_state_execute = new FormState();
     foreach ($classes as $class) {
       $form_state_execute['values']['tests'][$class] = $class;
     }
diff --git a/core/modules/system/src/Tests/Element/PathElementFormTest.php b/core/modules/system/src/Tests/Element/PathElementFormTest.php
index 0683b97b1098..dce7df6362f4 100644
--- a/core/modules/system/src/Tests/Element/PathElementFormTest.php
+++ b/core/modules/system/src/Tests/Element/PathElementFormTest.php
@@ -147,14 +147,13 @@ public function validateForm(array &$form, FormStateInterface $form_state) {}
    * Tests that default handlers are added even if custom are specified.
    */
   public function testPathElement() {
-    $form_state = new FormState(array(
-      'values' => array(
+    $form_state = (new FormState())
+      ->setValues([
         'required_validate' => 'user/' . $this->testUser->id(),
         'required_non_validate' => 'magic-ponies',
         'required_validate_route' => 'user/' . $this->testUser->id(),
         'required_validate_url' => 'user/' . $this->testUser->id(),
-      ),
-    ));
+      ]);
     $form_builder = $this->container->get('form_builder');
     $form_builder->submitForm($this, $form_state);
 
@@ -175,13 +174,12 @@ public function testPathElement() {
     ));
 
     // Test #required.
-    $form_state = new FormState(array(
-      'values' => array(
+    $form_state = (new FormState())
+      ->setValues([
         'required_non_validate' => 'magic-ponies',
         'required_validate_route' => 'user/' . $this->testUser->id(),
         'required_validate_url' => 'user/' . $this->testUser->id(),
-      ),
-    ));
+      ]);
     $form_builder->submitForm($this, $form_state);
     $errors = $form_state->getErrors();
     // Should be missing 'required_validate' field.
@@ -189,14 +187,13 @@ public function testPathElement() {
     $this->assertEqual($errors, array('required_validate' => t('!name field is required.', array('!name' => 'required_validate'))));
 
     // Test invalid parameters.
-    $form_state = new FormState(array(
-      'values' => array(
+    $form_state = (new FormState())
+      ->setValues([
         'required_validate' => 'user/74',
         'required_non_validate' => 'magic-ponies',
         'required_validate_route' => 'user/74',
         'required_validate_url' => 'user/74',
-      ),
-    ));
+      ]);
     $form_builder = $this->container->get('form_builder');
     $form_builder->submitForm($this, $form_state);
 
diff --git a/core/modules/system/src/Tests/Form/FormDefaultHandlersTest.php b/core/modules/system/src/Tests/Form/FormDefaultHandlersTest.php
index 51c79f85212c..d51cc1aa814f 100644
--- a/core/modules/system/src/Tests/Form/FormDefaultHandlersTest.php
+++ b/core/modules/system/src/Tests/Form/FormDefaultHandlersTest.php
@@ -75,7 +75,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    * Tests that default handlers are added even if custom are specified.
    */
   function testDefaultAndCustomHandlers() {
-    $form_state = new FormState(array('values' => array()));
+    $form_state = new FormState();
     $form_builder = $this->container->get('form_builder');
     $form_builder->submitForm($this, $form_state);
 
diff --git a/core/modules/system/src/Tests/Form/ProgrammaticTest.php b/core/modules/system/src/Tests/Form/ProgrammaticTest.php
index cfe9cb232d30..9b0234ad742b 100644
--- a/core/modules/system/src/Tests/Form/ProgrammaticTest.php
+++ b/core/modules/system/src/Tests/Form/ProgrammaticTest.php
@@ -72,7 +72,7 @@ function testSubmissionWorkflow() {
    */
   private function submitForm($values, $valid_input) {
     // Programmatically submit the given values.
-    $form_state = new FormState(array('values' => $values));
+    $form_state = (new FormState())->setValues($values);
     \Drupal::formBuilder()->submitForm('\Drupal\form_test\Form\FormTestProgrammaticForm', $form_state);
 
     // Check that the form returns an error when expected, and vice versa.
@@ -99,10 +99,10 @@ private function submitForm($values, $valid_input) {
    * Test the programmed_bypass_access_check flag.
    */
   public function testProgrammaticAccessBypass() {
-    $form_state = new FormState(array('values' => array(
+    $form_state = (new FormState())->setValues([
       'textfield' => 'dummy value',
       'field_restricted' => 'dummy value'
-    )));
+    ]);
 
     // Programmatically submit the form with a value for the restricted field.
     // Since programmed_bypass_access_check is set to TRUE by default, the
diff --git a/core/modules/system/src/Tests/System/SystemConfigFormTestBase.php b/core/modules/system/src/Tests/System/SystemConfigFormTestBase.php
index 388977ac43f3..a9f17c0483ce 100644
--- a/core/modules/system/src/Tests/System/SystemConfigFormTestBase.php
+++ b/core/modules/system/src/Tests/System/SystemConfigFormTestBase.php
@@ -52,7 +52,7 @@ public function testConfigForm() {
     foreach ($this->values as $form_key => $data) {
       $values[$form_key] = $data['#value'];
     }
-    $form_state = new FormState(array('values' => $values));
+    $form_state = (new FormState())->setValues($values);
     \Drupal::formBuilder()->submitForm($this->form, $form_state);
 
     // Check that the form returns an error when expected, and vice versa.
diff --git a/core/modules/system/tests/modules/batch_test/src/Controller/BatchTestController.php b/core/modules/system/tests/modules/batch_test/src/Controller/BatchTestController.php
index fcd9b010f53a..d6829fad0e08 100644
--- a/core/modules/system/tests/modules/batch_test/src/Controller/BatchTestController.php
+++ b/core/modules/system/tests/modules/batch_test/src/Controller/BatchTestController.php
@@ -85,9 +85,9 @@ public function testNoForm() {
    *   Render array containing markup.
    */
   function testProgrammatic($value = 1) {
-    $form_state = new FormState(array(
-      'values' => array('value' => $value)
-    ));
+    $form_state = (new FormState())->setValues([
+      'value' => $value,
+    ]);
     \Drupal::formBuilder()->submitForm('Drupal\batch_test\Form\BatchTestChainedForm', $form_state);
     return array(
       'success' => array(
diff --git a/core/modules/views/includes/ajax.inc b/core/modules/views/includes/ajax.inc
index da57333aaf76..c6daa7147623 100644
--- a/core/modules/views/includes/ajax.inc
+++ b/core/modules/views/includes/ajax.inc
@@ -17,12 +17,14 @@
  */
 function views_ajax_form_wrapper($form_class, FormStateInterface &$form_state) {
   // This won't override settings already in.
-  $form_state->setIfNotExists('rerender', FALSE);
-  $form_state->setIfNotExists('no_redirect', !empty($form_state['ajax']));
-  $form_state->setIfNotExists('no_cache', TRUE);
-  $form_state->setIfNotExists('build_info', array(
-    'args' => array(),
-  ));
+  if (!$form_state->has('rerender')) {
+    $form_state->set('rerender', FALSE);
+  }
+  // Do not overwrite if the redirect has been disabled.
+  if (!$form_state->isRedirectDisabled()) {
+    $form_state->disableRedirect(!empty($form_state['ajax']));
+  }
+  $form_state->disableCache();
 
   $form = \Drupal::formBuilder()->buildForm($form_class, $form_state);
   $output = drupal_render($form);
diff --git a/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php b/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php
index 211a388b2c0f..4f30943468a3 100644
--- a/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php
+++ b/core/modules/views/src/Plugin/views/exposed_form/ExposedFormPluginBase.php
@@ -129,14 +129,15 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    */
   public function renderExposedForm($block = FALSE) {
     // Deal with any exposed filters we may have, before building.
-    $form_state = new FormState(array(
-      'view' => &$this->view,
-      'display' => &$this->view->display_handler->display,
-      'method' => 'get',
-      'rerender' => TRUE,
-      'no_redirect' => TRUE,
-      'always_process' => TRUE,
-    ));
+    $form_state = (new FormState())
+      ->setStorage([
+        'view' => $this->view,
+        'display' => &$this->view->display_handler->display,
+        'rerender' => TRUE,
+      ])
+      ->setMethod('get')
+      ->setAlwaysProcess()
+      ->disableRedirect();
 
     // Some types of displays (eg. attachments) may wish to use the exposed
     // filters of their parent displays instead of showing an additional
diff --git a/core/modules/views/src/Tests/Wizard/WizardPluginBaseUnitTest.php b/core/modules/views/src/Tests/Wizard/WizardPluginBaseUnitTest.php
index 72a8823065ca..07d0783fda44 100644
--- a/core/modules/views/src/Tests/Wizard/WizardPluginBaseUnitTest.php
+++ b/core/modules/views/src/Tests/Wizard/WizardPluginBaseUnitTest.php
@@ -63,12 +63,12 @@ public function testCreateView() {
     ));
     language_save($language);
 
-    $form_state->set('values', array(
+    $form_state->setValues([
       'id' => $random_id,
       'label' => $random_label,
       'description' => $random_description,
       'base_table' => 'views_test_data',
-    ));
+    ]);
 
     $this->wizard->validateView($form, $form_state);
     $view = $this->wizard->createView($form, $form_state);
diff --git a/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php b/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php
index 15e6ee2c8975..d9d9d6375ff3 100644
--- a/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php
+++ b/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php
@@ -66,20 +66,16 @@ protected function setType($type) {
   public function getFormState(ViewStorageInterface $view, $display_id, $js) {
     // $js may already have been converted to a Boolean.
     $ajax = is_string($js) ? $js === 'ajax' : $js;
-    return new FormState(array(
-      'form_id' => $this->getFormId(),
-      'form_key' => $this->getFormKey(),
-      'ajax' => $ajax,
-      'display_id' => $display_id,
-      'view' => $view,
-      'type' => $this->type,
-      'id' => $this->id,
-      'no_redirect' => TRUE,
-      'build_info' => array(
-        'args' => array(),
-        'callback_object' => $this,
-      ),
-    ));
+    return (new FormState())
+      ->set('form_id', $this->getFormId())
+      ->set('form_key', $this->getFormKey())
+      ->set('ajax', $ajax)
+      ->set('display_id', $display_id)
+      ->set('view', $view)
+      ->set('type', $this->type)
+      ->set('id', $this->id)
+      ->disableRedirect()
+      ->addBuildInfo('callback_object', $this);
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
index b680217f9464..9e36fcc42b13 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
@@ -46,7 +46,7 @@ public function testGetFormIdWithClassName() {
     $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
 
     $this->assertSame('test_form', $form_id);
-    $this->assertSame($form_arg, get_class($form_state['build_info']['callback_object']));
+    $this->assertSame($form_arg, get_class($form_state->getFormObject()));
   }
 
   /**
@@ -62,7 +62,7 @@ public function testGetFormIdWithInjectedClassName() {
     $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
 
     $this->assertSame('test_form', $form_id);
-    $this->assertSame($form_arg, get_class($form_state['build_info']['callback_object']));
+    $this->assertSame($form_arg, get_class($form_state->getFormObject()));
   }
 
   /**
@@ -77,7 +77,7 @@ public function testGetFormIdWithObject() {
     $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
 
     $this->assertSame($expected_form_id, $form_id);
-    $this->assertSame($form_arg, $form_state['build_info']['callback_object']);
+    $this->assertSame($form_arg, $form_state->getFormObject());
   }
 
   /**
@@ -99,8 +99,8 @@ public function testGetFormIdWithBaseForm() {
     $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
 
     $this->assertSame($expected_form_id, $form_id);
-    $this->assertSame($form_arg, $form_state['build_info']['callback_object']);
-    $this->assertSame($base_form_id, $form_state['build_info']['base_form_id']);
+    $this->assertSame($form_arg, $form_state->getFormObject());
+    $this->assertSame($base_form_id, $form_state->getBuildInfo()['base_form_id']);
   }
 
   /**
@@ -123,7 +123,7 @@ public function testHandleFormStateResponse($class, $form_state_key) {
     $form_arg->expects($this->any())
       ->method('submitForm')
       ->will($this->returnCallback(function ($form, FormStateInterface $form_state) use ($response, $form_state_key) {
-        $form_state->set($form_state_key, $response);
+        $form_state->setFormState([$form_state_key => $response]);
       }));
 
     $form_state = new FormState();
@@ -136,7 +136,7 @@ public function testHandleFormStateResponse($class, $form_state_key) {
     catch (\Exception $e) {
       $this->assertSame('exit', $e->getMessage());
     }
-    $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $form_state->get('response'));
+    $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $form_state->getResponse());
   }
 
   /**
@@ -190,7 +190,7 @@ public function testHandleRedirectWithResponse() {
     catch (\Exception $e) {
       $this->assertSame('exit', $e->getMessage());
     }
-    $this->assertSame($response, $form_state->get('response'));
+    $this->assertSame($response, $form_state->getResponse());
   }
 
   /**
@@ -279,7 +279,7 @@ public function testBuildFormWithObject() {
     $form_state = new FormState();
     $form = $this->formBuilder->buildForm($form_arg, $form_state);
     $this->assertFormElement($expected_form, $form, 'test');
-    $this->assertSame($form_id, $form_state['build_info']['form_id']);
+    $this->assertSame($form_id, $form_state->getBuildInfo()['form_id']);
     $this->assertArrayHasKey('#id', $form);
   }
 
@@ -305,15 +305,15 @@ public function testRebuildForm() {
     $original_build_id = $form['#build_id'];
 
     // Rebuild the form, and assert that the build ID has not changed.
-    $form_state['rebuild'] = TRUE;
+    $form_state->setRebuild();
     $input['form_id'] = $form_id;
     $form_state->setUserInput($input);
-    $form_state['rebuild_info']['copy']['#build_id'] = TRUE;
+    $form_state->addRebuildInfo('copy', ['#build_id' => TRUE]);
     $this->formBuilder->processForm($form_id, $form, $form_state);
     $this->assertSame($original_build_id, $form['#build_id']);
 
     // Rebuild the form again, and assert that there is a new build ID.
-    $form_state['rebuild_info'] = array();
+    $form_state->setRebuildInfo([]);
     $form = $this->formBuilder->buildForm($form_arg, $form_state);
     $this->assertNotSame($original_build_id, $form['#build_id']);
   }
@@ -337,10 +337,9 @@ public function testGetCache() {
       ->will($this->returnValue($expected_form));
 
     // Do an initial build of the form and track the build ID.
-    $form_state = new FormState();
-    $form_state['build_info']['args'] = array();
-    $form_state['build_info']['files'] = array(array('module' => 'node', 'type' => 'pages.inc'));
-    $form_state['cache'] = TRUE;
+    $form_state = (new FormState())
+      ->addBuildInfo('files', [['module' => 'node', 'type' => 'pages.inc']])
+      ->setCached();
     $form = $this->formBuilder->buildForm($form_arg, $form_state);
 
     $cached_form = $form;
@@ -353,12 +352,12 @@ public function testGetCache() {
 
     // The final form build will not trigger any actual form building, but will
     // use the form cache.
-    $form_state['executed'] = TRUE;
+    $form_state->setExecuted();
     $input['form_id'] = $form_id;
     $input['form_build_id'] = $form['#build_id'];
     $form_state->setUserInput($input);
     $this->formBuilder->buildForm($form_arg, $form_state);
-    $this->assertEmpty($form_state['errors']);
+    $this->assertEmpty($form_state->getErrors());
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php b/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php
index 6d668b2fb15c..d08557ac8d7f 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormCacheTest.php
@@ -235,7 +235,7 @@ public function testLoadCachedFormState() {
       ->willReturn($cached_form_state);
 
     $this->formCache->getCache($form_build_id, $form_state);
-    $this->assertSame($cached_form_state['storage'], $form_state['storage']);
+    $this->assertSame($cached_form_state['storage'], $form_state->getStorage());
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Form/FormStateTest.php b/core/tests/Drupal/Tests/Core/Form/FormStateTest.php
index 0697b2da79e3..83e251816dd7 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormStateTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormStateTest.php
@@ -29,7 +29,7 @@ class FormStateTest extends UnitTestCase {
    * @dataProvider providerTestGetRedirect
    */
   public function testGetRedirect($form_state_additions, $expected) {
-    $form_state = new FormState($form_state_additions);
+    $form_state = (new FormState())->setFormState($form_state_additions);
     $redirect = $form_state->getRedirect();
     $this->assertEquals($expected, $redirect);
   }
@@ -82,9 +82,9 @@ public function testSetError() {
    */
   public function testGetError($errors, $parents, $error = NULL) {
     $element['#parents'] = $parents;
-    $form_state = new FormState(array(
+    $form_state = (new FormState())->setFormState([
       'errors' => $errors,
-    ));
+    ]);
     $this->assertSame($error, $form_state->getError($element));
   }
 
@@ -110,9 +110,9 @@ public function providerTestGetError() {
    */
   public function testSetErrorByName($limit_validation_errors, $expected_errors, $set_message = FALSE) {
     $form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
-      ->setConstructorArgs(array(array('limit_validation_errors' => $limit_validation_errors)))
       ->setMethods(array('drupalSetMessage'))
       ->getMock();
+    $form_state->setLimitValidationErrors($limit_validation_errors);
     $form_state->clearErrors();
     $form_state->expects($set_message ? $this->once() : $this->never())
       ->method('drupalSetMessage');
@@ -122,7 +122,7 @@ public function testSetErrorByName($limit_validation_errors, $expected_errors, $
     $form_state->setErrorByName('options');
 
     $this->assertSame(!empty($expected_errors), $form_state::hasAnyErrors());
-    $this->assertSame($expected_errors, $form_state['errors']);
+    $this->assertSame($expected_errors, $form_state->getErrors());
   }
 
   public function providerTestSetErrorByName() {
@@ -147,9 +147,9 @@ public function providerTestSetErrorByName() {
    */
   public function testFormErrorsDuringSubmission() {
     $form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
-      ->setConstructorArgs(array(array('validation_complete' => TRUE)))
       ->setMethods(array('drupalSetMessage'))
       ->getMock();
+    $form_state->setValidationComplete();
     $form_state->setErrorByName('test', 'message');
   }
 
@@ -183,12 +183,12 @@ public function testSetValueForElement() {
    * @dataProvider providerTestGetValue
    */
   public function testGetValue($key, $expected, $default = NULL) {
-    $form_state = new FormState(array('values' => array(
+    $form_state = (new FormState())->setValues([
       'foo' => 'one',
       'bar' => array(
         'baz' => 'two',
       ),
-    )));
+    ]);
     $this->assertSame($expected, $form_state->getValue($key, $default));
   }
 
@@ -215,7 +215,9 @@ public function providerTestGetValue() {
    * @dataProvider providerTestSetValue
    */
   public function testSetValue($key, $value, $expected) {
-    $form_state = new FormState(array('values' => array('bar' => 'wrong')));
+    $form_state = (new FormState())->setValues([
+      'bar' => 'wrong',
+    ]);
     $form_state->setValue($key, $value);
     $this->assertSame($expected, $form_state->getValues());
   }
@@ -225,9 +227,9 @@ public function testSetValue($key, $value, $expected) {
    */
   public function testPrepareCallbackValidMethod() {
     $form_state = new FormState();
-    $form_state['build_info']['callback_object'] = new PrepareCallbackTestForm();
+    $form_state->setFormObject(new PrepareCallbackTestForm());
     $processed_callback = $form_state->prepareCallback('::buildForm');
-    $this->assertEquals(array($form_state['build_info']['callback_object'], 'buildForm'), $processed_callback);
+    $this->assertEquals([$form_state->getFormObject(), 'buildForm'], $processed_callback);
   }
 
   /**
@@ -235,7 +237,7 @@ public function testPrepareCallbackValidMethod() {
    */
   public function testPrepareCallbackInValidMethod() {
     $form_state = new FormState();
-    $form_state['build_info']['callback_object'] = new PrepareCallbackTestForm();
+    $form_state->setFormObject(new PrepareCallbackTestForm());
     $processed_callback = $form_state->prepareCallback('not_a_method');
     // The callback was not changed as no such method exists.
     $this->assertEquals('not_a_method', $processed_callback);
@@ -246,8 +248,8 @@ public function testPrepareCallbackInValidMethod() {
    */
   public function testPrepareCallbackArray() {
     $form_state = new FormState();
-    $form_state['build_info']['callback_object'] = new PrepareCallbackTestForm();
-    $callback = array($form_state['build_info']['callback_object'], 'buildForm');
+    $form_state->setFormObject(new PrepareCallbackTestForm());
+    $callback = [$form_state->getFormObject(), 'buildForm'];
     $processed_callback = $form_state->prepareCallback($callback);
     $this->assertEquals($callback, $processed_callback);
   }
@@ -272,7 +274,7 @@ public function providerTestSetValue() {
    * @dataProvider providerTestHasValue
    */
   public function testHasValue($key, $expected) {
-    $form_state = new FormState(array('values' => array(
+    $form_state = (new FormState())->setValues([
       'foo' => 'one',
       'bar' => array(
         'baz' => 'two',
@@ -280,7 +282,7 @@ public function testHasValue($key, $expected) {
       'true' => TRUE,
       'false' => FALSE,
       'null' => NULL,
-    )));
+    ]);
     $this->assertSame($expected, $form_state->hasValue($key));
   }
 
@@ -313,7 +315,7 @@ public function providerTestHasValue() {
    * @dataProvider providerTestIsValueEmpty
    */
   public function testIsValueEmpty($key, $expected) {
-    $form_state = new FormState(array('values' => array(
+    $form_state = (new FormState())->setValues([
       'foo' => 'one',
       'bar' => array(
         'baz' => 'two',
@@ -321,7 +323,7 @@ public function testIsValueEmpty($key, $expected) {
       'true' => TRUE,
       'false' => FALSE,
       'null' => NULL,
-    )));
+    ]);
     $this->assertSame($expected, $form_state->isValueEmpty($key));
   }
 
@@ -405,22 +407,113 @@ public function testLoadIncludeAlreadyLoaded() {
     $module = 'some_module';
     $name = 'some_name';
     $form_state = $this->getMockBuilder('Drupal\Core\Form\FormState')
-      ->setConstructorArgs([['build_info' => ['files' => [
-        'some_module:some_name.some_type' => [
-          'type' => $type,
-          'module' => $module,
-          'name' => $name,
-        ],
-      ]]]])
       ->setMethods(array('moduleLoadInclude'))
       ->getMock();
 
+    $form_state->addBuildInfo('files', [
+      'some_module:some_name.some_type' => [
+        'type' => $type,
+        'module' => $module,
+        'name' => $name,
+      ],
+    ]);
     $form_state->expects($this->never())
       ->method('moduleLoadInclude');
 
     $this->assertFalse($form_state->loadInclude($module, $type, $name));
   }
 
+  /**
+   * @covers ::isCached
+   *
+   * @dataProvider providerTestIsCached
+   */
+  public function testIsCached($cache_key, $no_cache_key, $expected) {
+    $form_state = (new FormState())->setFormState([
+      'cache' => $cache_key,
+      'no_cache' => $no_cache_key,
+    ]);
+    $this->assertSame($expected, $form_state->isCached());
+  }
+
+  /**
+   * Provides test data for testIsCached().
+   */
+  public function providerTestIsCached() {
+    $data = [];
+    $data[] = [
+      TRUE,
+      TRUE,
+      FALSE,
+    ];
+    $data[] = [
+      FALSE,
+      TRUE,
+      FALSE,
+    ];
+    $data[] = [
+      FALSE,
+      FALSE,
+      FALSE,
+    ];
+    $data[] = [
+      TRUE,
+      FALSE,
+      TRUE,
+    ];
+    $data[] = [
+      TRUE,
+      NULL,
+      TRUE,
+    ];
+    $data[] = [
+      FALSE,
+      NULL,
+      FALSE,
+    ];
+    return $data;
+  }
+
+  /**
+   * @covers ::isMethodType
+   * @covers ::setMethod
+   *
+   * @dataProvider providerTestIsMethodType
+   */
+  public function testIsMethodType($set_method_type, $input, $expected) {
+    $form_state = (new FormState())
+      ->setMethod($set_method_type);
+    $this->assertSame($expected, $form_state->isMethodType($input));
+  }
+
+  /**
+   * Provides test data for testIsMethodType().
+   */
+  public function providerTestIsMethodType() {
+    $data = [];
+    $data[] = [
+      'get',
+      'get',
+      TRUE,
+    ];
+    $data[] = [
+      'get',
+      'GET',
+      TRUE,
+    ];
+    $data[] = [
+      'GET',
+      'GET',
+      TRUE,
+    ];
+    $data[] = [
+      'post',
+      'get',
+      FALSE,
+    ];
+    return $data;
+  }
+
 }
 
 /**
diff --git a/core/tests/Drupal/Tests/Core/Form/FormSubmitterTest.php b/core/tests/Drupal/Tests/Core/Form/FormSubmitterTest.php
index e1c9462e3ed1..ff2b2cd3a085 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormSubmitterTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormSubmitterTest.php
@@ -45,7 +45,7 @@ public function testHandleFormSubmissionNotSubmitted() {
     $form_state = new FormState();
 
     $return = $form_submitter->doSubmitForm($form, $form_state);
-    $this->assertFalse($form_state['executed']);
+    $this->assertFalse($form_state->isExecuted());
     $this->assertNull($return);
   }
 
@@ -55,13 +55,12 @@ public function testHandleFormSubmissionNotSubmitted() {
   public function testHandleFormSubmissionNoRedirect() {
     $form_submitter = $this->getFormSubmitter();
     $form = array();
-    $form_state = new FormState(array(
-      'submitted' => TRUE,
-      'no_redirect' => TRUE,
-    ));
+    $form_state = (new FormState())
+      ->setSubmitted()
+      ->disableRedirect();
 
     $return = $form_submitter->doSubmitForm($form, $form_state);
-    $this->assertTrue($form_state['executed']);
+    $this->assertTrue($form_state->isExecuted());
     $this->assertNull($return);
   }
 
@@ -78,10 +77,9 @@ public function testHandleFormSubmissionWithResponses($class, $form_state_key) {
       ->method('prepare')
       ->will($this->returnValue($response));
 
-    $form_state = new FormState(array(
-      'submitted' => TRUE,
-      $form_state_key => $response,
-    ));
+    $form_state = (new FormState())
+      ->setSubmitted()
+      ->setFormState([$form_state_key => $response]);
 
     $form_submitter = $this->getFormSubmitter();
     $form = array();
@@ -202,7 +200,7 @@ public function testRedirectWithoutResult() {
    */
   public function testExecuteSubmitHandlers() {
     $form_submitter = $this->getFormSubmitter();
-    $mock = $this->getMock('stdClass', array('submit_handler', 'hash_submit', 'simple_string_submit'));
+    $mock = $this->getMockForAbstractClass('Drupal\Core\Form\FormBase', [], '', TRUE, TRUE, TRUE, ['submit_handler', 'hash_submit', 'simple_string_submit']);
     $mock->expects($this->once())
       ->method('submit_handler')
       ->with($this->isType('array'), $this->isInstanceOf('Drupal\Core\Form\FormStateInterface'));
@@ -221,13 +219,13 @@ public function testExecuteSubmitHandlers() {
     $form_submitter->executeSubmitHandlers($form, $form_state);
 
     // $form_state submit handlers will supersede $form handlers.
-    $form_state['submit_handlers'][] = array($mock, 'submit_handler');
+    $form_state->setSubmitHandlers([[$mock, 'submit_handler']]);
     $form_submitter->executeSubmitHandlers($form, $form_state);
 
     // Methods directly on the form object can be specified as a string.
-    $form_state = new FormState();
-    $form_state['build_info']['callback_object'] = $mock;
-    $form_state['submit_handlers'][] = '::simple_string_submit';
+    $form_state = (new FormState())
+      ->setFormObject($mock)
+      ->setSubmitHandlers(['::simple_string_submit']);
     $form_submitter->executeSubmitHandlers($form, $form_state);
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php
index f3a9443f282d..47b80b1a22f3 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php
@@ -215,23 +215,20 @@ protected function getMockForm($form_id, $expected_form = NULL, $count = 1) {
    * @param \Drupal\Core\Form\FormStateInterface $form_state
    *   The current state of the form.
    * @param bool $programmed
-   *   Whether $form_state['programmed'] should be set to TRUE or not. If it is
-   *   not set to TRUE, you must provide additional data in $form_state for the
-   *   submission to take place.
+   *   Whether $form_state->setProgrammed() should be passed TRUE or not. If it
+   *   is not set to TRUE, you must provide additional data in $form_state for
+   *   the submission to take place.
    *
    * @return array
    *   The built form.
    */
   protected function simulateFormSubmission($form_id, FormInterface $form_arg, FormStateInterface $form_state, $programmed = TRUE) {
-    $form_state['build_info']['callback_object'] = $form_arg;
-    $form_state['build_info']['args'] = array();
-
     $input = $form_state->getUserInput();
     $input['op'] = 'Submit';
-    $form_state->setUserInput($input);
-
-    $form_state['programmed'] = $programmed;
-    $form_state['submitted'] = TRUE;
+    $form_state
+      ->setUserInput($input)
+      ->setProgrammed($programmed)
+      ->setSubmitted();
     return $this->formBuilder->buildForm($form_arg, $form_state);
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Form/FormValidatorTest.php b/core/tests/Drupal/Tests/Core/Form/FormValidatorTest.php
index bc6e081962de..cc49c2cb32a8 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormValidatorTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormValidatorTest.php
@@ -33,9 +33,9 @@ public function testValidationComplete() {
 
     $form = array();
     $form_state = new FormState();
-    $this->assertFalse($form_state['validation_complete']);
+    $this->assertFalse($form_state->isValidationComplete());
     $form_validator->validateForm('test_form_id', $form, $form_state);
-    $this->assertTrue($form_state['validation_complete']);
+    $this->assertTrue($form_state->isValidationComplete());
   }
 
   /**
@@ -52,8 +52,8 @@ public function testPreventDuplicateValidation() {
       ->method('doValidateForm');
 
     $form = array();
-    $form_state = new FormState();
-    $form_state['validation_complete'] = TRUE;
+    $form_state = (new FormState())
+      ->setValidationComplete();
     $form_validator->validateForm('test_form_id', $form, $form_state);
     $this->assertArrayNotHasKey('#errors', $form);
   }
@@ -72,9 +72,9 @@ public function testMustValidate() {
       ->method('doValidateForm');
 
     $form = array();
-    $form_state = new FormState();
-    $form_state['validation_complete'] = TRUE;
-    $form_state['must_validate'] = TRUE;
+    $form_state = (new FormState())
+      ->setValidationComplete()
+      ->setValidationEnforced();
     $form_validator->validateForm('test_form_id', $form, $form_state);
     $this->assertArrayHasKey('#errors', $form);
   }
@@ -110,7 +110,7 @@ public function testValidateInvalidFormToken() {
       ->with('form_token', 'The form has become outdated. Copy any unsaved work in the form below and then <a href="/test/example?foo=bar">reload this page</a>.');
     $form_state->setValue('form_token', 'some_random_token');
     $form_validator->validateForm('test_form_id', $form, $form_state);
-    $this->assertTrue($form_state['validation_complete']);
+    $this->assertTrue($form_state->isValidationComplete());
   }
 
   /**
@@ -141,7 +141,7 @@ public function testValidateValidFormToken() {
       ->method('setErrorByName');
     $form_state->setValue('form_token', 'some_random_token');
     $form_validator->validateForm('test_form_id', $form, $form_state);
-    $this->assertTrue($form_state['validation_complete']);
+    $this->assertTrue($form_state->isValidationComplete());
   }
 
   /**
@@ -182,10 +182,9 @@ public function testHandleErrorsWithLimitedValidation($sections, $triggering_ele
 
     $triggering_element['#limit_validation_errors'] = $sections;
     $form = array();
-    $form_state = new FormState(array(
-      'values' => $values,
-      'triggering_element' => $triggering_element,
-    ));
+    $form_state = (new FormState())
+      ->setValues($values)
+      ->setTriggeringElement($triggering_element);
 
     $form_validator->validateForm('test_form_id', $form, $form_state);
     $this->assertSame($expected, $form_state->getValues());
@@ -292,7 +291,8 @@ public function testExecuteValidateHandlers() {
     $form_validator->executeValidateHandlers($form, $form_state);
 
     // $form_state validate handlers will supersede $form handlers.
-    $form_state['validate_handlers'][] = array($mock, 'validate_handler');
+    $validate_handlers[] = [$mock, 'validate_handler'];
+    $form_state->setValidateHandlers($validate_handlers);
     $form_validator->executeValidateHandlers($form, $form_state);
   }
 
-- 
GitLab