Introduction to Forms

Objectives
  • Learn about the Form API.
  • Learn about the three types of forms.
  • Learn how to define a form in code.
  • Learn how to access a form via a route.

Forms range from relatively simple forms for logging in or saving configuration settings, to configuring complex views, creating/editing nodes, or your own custom event registration system.

One way or another, your CMS and custom application will very likely need at least a few custom forms.

As a developer you can build forms in Drupal 10 using the Form API.

Three form types

In Drupal there are three form types you can specify:

  • Generic forms
  • Configuration forms: forms intended to save or update configuration data.
  • Confirmation forms: forms that provide a confirmation step before executing destructive logic such as deleting content.

In this course we'll focus on generic forms.

Configuration forms and confirmation forms are slightly more complex but they follow the same principles. If you're curious, our sample code contains examples of all three types of forms.

Building a generic form

To build a generic form (just form for short) you need to create a class and implement a handful of methods to:

  • Build the form.
  • Give the form a unique ID.
  • Validate the submitted form data (optional)
  • Process the submitted data

Example: ColourForm

Let's build the following form:

The following sample code defines two things:

  • A form class.
  • A route that lets you access the form.

The ColourForm class

Code
<?php
// File: my_form_demo/src/Form/ColourForm.php
namespace Drupal\my_form_demo\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

class ColourForm extends FormBase {

  // Return unique string that identifies the form.
  public function getFormId() {

    return 'my_colour_form';
  }

  public function buildForm(array $form, FormStateInterface $form_state) {

    // Add field .
    $form['fav_colour'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Your favourite colour'),
    ];

    $form['save'] = [
      '#type' => 'submit',
      '#value' => $this->t('Save'),
    ];
    return $form;
  }

  public function validateForm(array &$form, FormStateInterface $form_state) {
    // Validate the submitted form data.
  }

  public function submitForm(array &$form, FormStateInterface $form_state) {
    // Do something useful with the submitted form data.
  }

}

Let's break this down.

Namespaces

Code
<?php
namespace Drupal\my_form_demo\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

Forms must use the [module name]\Form namespace, and import two namespaces: FormBase and FormStateInterface.

Extend FormBase

Code
<?php
class ColourForm extends FormBase {
 // [...]
}

Form classes must extend FormBase.

Implement getFormId()

Code
  public function getFormId() {
    return 'my_colour_form';
  }

Form classes must implement getFormId().
The only thing this method needs to do is return a unique string that identifies the form.

Implement buildForm()

Code
 public function buildForm(array $form, FormStateInterface $form_state) {

    $form['fav_colour'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Your favourite colour'),
    ];

    $form['save'] = [
      '#type' => 'submit',
      '#value' => $this->t('Save'),
    ];

    return $form;
  }

Form classes must implement buildForm().

This method is where you add fields and buttons to the $form array that gets passed in.

In this example we're adding:

  • a text field named 'fav_colour' with the label 'Your favourite colour'.
  • a submit button named 'save' with the label 'Save'.

At the end of the function we return the $form array.

A Validation Handler

Code
 public function validateForm(array &$form, FormStateInterface $form_state) {
    // Validate the submitted form data.
  }

The validateForm() method is where you can verify and validate the values that were entered.

Specifying a validation handler is optional.

If you want certain values to be formatted a certain way, or want to ensure values lie in a certain range, or want to check a given value against a list of banned words, or anything else… this is the place to do it.

If any of the fields don't match your validation criteria, you can flag the value as invalid and declare a form error.

If any form errors were declared, Drupal will present the form again to the end user, with the supplied values. The user can then correct the indicated field(s) and try to submit the form again, at which time this validation handler will be called again to validate all the submitted values again.

When no form errors are reported (anymore), the submit handler will be called.

A Submit Handler

The submitForm method is where you process the submitted values and do something useful with them such as saving them to the database, or calling an external webservice to send them to a third party system.

In this example we're not doing anything, so the provided value will not be saved anywhere; after submitting the form, the form is simply loaded again.

The /colour route

The most common way to make custom forms available to the end user is to provide a route that loads the form. The form will then be displayed on a page of its own:

Code
// File: my_form_demo.routing.yml
 
my_form_demo.colour:
 path: '/colour'
 defaults:
   _form: 'Drupal\my_form_demo\Form\ColourForm'
   _title: 'Favourite Colour'
 requirements:
   _permission: 'access content'

[...]

This route is almost identical to other routes you created earlier in this course.

The only difference is: rather than providing a _controller property that points to a controller method that is executed when you access the route, you provide a _form property that points to a form class that provides a form.

Summary

  • You can use the Form API to build forms in Drupal.
  • To build a generic form, you create a class that extends FormBase.
  • You need to implement at least three methods: getFormId(), buildForm(), and submitForm().
  • validateForm() lets you validate submitted data and return to the form in case of errors.
  • submitForm() lets you process the submitted data and do something useful with it.
  • The easiest way to access a custom form is by providing a route.
  • Form routes are almost identical to page routes, but use the _form property instead of the _controller property.