Getting Started with Forms in Drupal 8

Cross-posted with permission from Drupalize.Me

Forms are an essential part of any web application. They are the primary mechanism for collecting input from our users, and without them Drupal wouldn't be very useful. As such, they're also one of the first things people want to learn when they start learning Drupal. Forms are fundamental to creating Drupal modules, whether you're asking someone to leave a review of your video or giving an administrator the option to turn JavaScript aggregation off.

Form basics

There are two key elements to crafting forms. The workflow a form goes through, including how Drupal locates the form to display on a page, handling validation when someone submits a form, and ultimately doing something with the collected data. And the definition of the form itself, in which one determines if your form will have checkboxes, textfields, upload widgets, and/or any user-facing text.

Form definition

The way that forms are defined in Drupal hasn't changed that much between Drupal 7 and Drupal 8, and I'm not going to go into too much detail here. Form definitions are still a Drupal render array made up of Form API elements that are ultimately parsed down to the HTML that is presented to the browser. The biggest change to crafting forms is the addition of some new HTML5 elements that can be defined in the Form API array, like tel number date.

Watch our Drupal 7 Module Developement series for a few lessons about using the Form API and form arrays in Drupal 7.

Form workflow

Truth be told, form workflow hasn't changed that much at a high level either. We still have the concepts of building, validation, and submission. And they're still all available for us to hook into by simply conforming to a specific pattern. It's really just the pattern that has changed. So lets take a look at that.

With the move to more modern PHP usage and Object Oriented Progamming patterns in Drupal 8, we now have the concept of form objects defined by a form class. All form classes implement the Drupal\Core\Form\FormInterface interface, which states that any form object should have getFormId, buildForm, validateForm, and submitForm methods. It turns out that this matches up nicely with the build, validate, and submit workflow. By conforming to this interface, we ensure that Drupal knows how to process each step of the workflow for the form in question, given any form object. Before we look at some sample code, however, lets talk just a little bit more about a typical form workflow.

When a user visits a URL on a site, /contact for example, Drupal needs to return the HTML representation of the required form so that it can be displayed in the user's browser. In order to get that form definition, Drupal loads the required form object. Then Drupal calls the buildForm() method on that object. This returns a Form API array that Drupal can turn into HTML. Likely this HTML includes also a button that a user can click. Clicking the button generates an HTTP Post request to the URL defined as the action of the form. In Drupal, this is the same URl in which the form is displayed (i.e., /contact).

This time, however, when Drupal gets the request for /contact it notices also that the request contains $_POST data. This means that the form being requested has actually just been submitted, and it should proceed to the next step in the workflow, which is validation. So Drupal instantiates our form object and calls the validateForm() method, which it knows is present because we're implementing the FormInterface. If the validation handler determines there are any errors in the data it flags them, and Drupal halts processing. It displays the form to the user to get errors fixed, and then it waits for the user to submit the form again before proceding. If no errors are found, Drupal moves on to the submission step of the workflow by calling our form objects submitForm(). Here we perform whatever logic is necessary with the data we collect in the form, like save it to the database or a config file.

Once you know how it works, the entire process is actually quite simple and beautiful. And it hasn't changed all that much, even since the Drupal 4.7 era. Many people love to hate it, but it's easy to argue that Form API is one of the strongest features in Drupal.

Show me some code already!

Ready to wire it all up? The first thing you'll need to do is create a route for your form. In our example, it looks like this:

chad.settings:
  path: '/admin/config/system/chad'
  defaults:
    _form: 'Drupal\chad\Form\SettingsForm'
    _title: 'Chad Settings'
  requirements:
    _permission: 'administer site configuration'

The only difference between this route and one that displays non-form content on a page is the _form key instead of the usual _content key. Here _form tells Drupal the location of the class that it should use when constructing our form object. Note that we simply specify the class name here and not the method, like SettingsForm::buildForm. Because we've defined this route as a form, Drupal will call buildForm whenever someone requests /admin/config/system/chad.

Our form class then looks like the following and lives in lib/Drupal/chad/Form/SettingsForm.php

/**
 * @file
 */

namespace Drupal\chad\Form;

use Drupal\Core\Form\ConfigFormBase;

class SettingsForm extends ConfigFormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'chad_settings';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, array &$form_state) {

    // Build our Form API array here.

    return parent::buildForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, array &$form_state) {

    // Handle submitted values in $form_state here.

    return parent::submitForm($form, $form_state);
  }

}

Also note that we've opted to extend the Drupal\Core\Form\ConfigFormBase class which provides some additional boilerplate code for system settings forms. There is a Drupal\Core\Form\FormBase class also. This is a great starting point for most forms because it handles injection of common dependencies. Nevertheless, anything that implements the FormInterface will work.

See the previous post in this series, Drupal 8: Writing a Hello World Module, for background on the code this video utilizes.

Finally, watch the video to see it all wired together and working:

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.

Filtered HTML

  • Use [acphone_sales], [acphone_sales_text], [acphone_support], [acphone_international], [acphone_devcloud], [acphone_extra1] and [acphone_extra2] as placeholders for Acquia phone numbers. Add class "acquia-phones-link" to wrapper element to make number a link.
  • To post pieces of code, surround them with <code>...</code> tags. For PHP code, you can use <?php ... ?>, which will also colour it based on syntax.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <h4> <h5> <h2> <img>
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.