In Drupal 7, a form was a function that returned an array containing nested elements from the Form API. Then, you would add appropriate _validate and _submit functions to handle verifying the submission and to handle the completed form in a simple case that would look as follows:
function my_module_my_form($form, &$form_state)
$form['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#required' => TRUE,
);
$form['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#required' => TRUE,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}
function my_module_my_form_validate($form, &$form_state) {
// Handle form validation
}
function my_module_my_form_submit($form, &$form_state) {
// Handle form submission
}
Like many of the changes in Drupal 8, this convention has been replaced by an explicit class hierarchy. Forms are classes that implement \Drupal\Core\FormInterface or extend \Drupal\Core\Form\FormBase. Like other components in Drupal 8, the simplest way of creating a form is to use Drupal Console by running drupal generate:form. For our new forms, we'll be adding them to the Mastering Drupal 8 custom module that we built in Chapter 10, Extending Drupal.

While the structure of the form is different from the Drupal 7 example, it still has all the information needed. Instead of form ID being the name of the function that returns the Form API array, it's explicitly returned in the getFormId function. The Form API array is returned by the buildForm function. Form submission is implemented in the submitForm function, and optional validation occurs with the validateForm function. So, to have a form like the Drupal 7 example from earlier, it will look like this:
class TestForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'test_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['first_name'] = array(
'#type' => 'textfield',
'#title' => $this->t('First name'),
'#maxlength' => 64,
'#size' => 64,
);
$form['last_name'] = array(
'#type' => 'textfield',
'#title' => $this->t('Last name'),
'#maxlength' => 64,
'#size' => 64,
);
$form['submit'] = array(
'#type' => 'submit',
'#value => $this->t('Submit'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
}
}
In addition to the Form class, Drupal Console also creates a menu router in the module. By default, it uses the machine name of the module and the first part of the form machine name. So, for our test form, this becomes /mastering_drupal_8/form/test. You can see this router in the mastering_drupal_8.routing.yml file, where it looks as follows:
mastering_drupal_8.test_form:
path: '/mastering_drupal_8/form/test'
defaults:
_form: '\Drupal\mastering_drupal_8\Form\TestForm'
_title: 'TestForm'
requirements:
_access: 'TRUE'

One area that hasn't changed significantly from Drupal 7 is the actual structure of the form elements array. While Symfony has its own API to define forms, the decision was made to keep the Drupal Form API's structure the same in Drupal 8. As backward compatibility was not guaranteed with Symfony Forms in 2.3, any further changes to the Drupal Form API were deferred to Drupal 9.