In this recipe, we will create a form, which will be accessible from a menu path. This will involve creating a route that tells Drupal to invoke our form and display it to the end user.
Forms are defined as classes, which implement DrupalCoreFormFormInterface
. The DrupalCoreFormFormBase
serves as a utility class that is intended to be extended. We will extend this class to create a new form.
Since we will be writing the code, you will want to have a custom module. Creating a custom module in Drupal is simply creating a folder and an info.yml
file. For this recipe, we will create a folder under /modules
in your Drupal folder called drupalform
.
In the drupalform
folder, create drupalform.info.yml
. The info.yml
file is what Drupal will parse to discover modules. An example of a module's info.yml
file is as follows:
name: Drupal form example description: Create a basic Drupal form, accessible from a route type: module version: 1.0 core: 8.x
The name will be your module's name, and the description will be listed on the Extend page. Specifying the core tells Drupal what version of Drupal it is built for. Chapter 4, Extending Drupal covers how to create a module in depth.
src
folder in your module directory. In this directory, create a Form
directory, which will hold the class that defines your form.ExampleForm.php
in your module's src/Form
directory.ExampleForm.php
file and add the proper PHP namespace, classes used, and the class itself:<?php /** * @file * Contains DrupaldrupalformFormExampleForm. **/ namespace DrupaldrupalformForm; use DrupalCoreFormFormBase; use DrupalCoreFormFormStateInterface; class ExampleForm extends FormBase { }
The namespace
defines the class in your module's Form
directory. The autoloader
will now look into the drupalform
module path and load the ExampleForm
class from the src/Form
directory.
The use
statement allows us to use just the class name when referencing FormBase
and, in the next steps, FormStateInterface
. Otherwise, we would be forced to use the fully qualified namespace path for each class whenever it is used.
DrupalCoreFormFormBase
is an abstract class and requires us to implement four remaining interface methods: getFormId
, buildForm
, validateForm
, and submitForm
. The latter two are covered in their own recipes; however, we will need to define the method stubs:class ExampleForm extends FormBase { /** * {@inheritdoc} */ public function getFormId() { return 'drupalform_example_form'; } /** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { // Return array of Form API elements. } /** * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { // Validation covered in later recipe, required to satisfy interface } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { // Validation covered in later recipe, required to satisfy interface } }
FormBase
provides utility
methods and does not satisfy the interface requirements for FormStateInterface
. We define those here, as they are unique across each form definition.getFormId
method returns a unique string to identify the form, for example, site_information
. You may encounter some forms that append _form
to the end of their form ID. This is not required, and it is just a naming convention often found in previous versions of Drupal.buildForm
method is covered in the following steps. The validateForm
and submitForm
methods are both called during the Form API processes and are covered in later recipes.buildForm
method will be invoked to return Form API elements that are rendered to the end user. We will add a simple text field to ask for a company name and a submit button:/** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { $form['company_name'] = array( '#type' => 'textfield', '#title' => $this->t('Company name'), ); $form['submit'] = array( '#type' => 'submit', '#value' => $this->t('Save'), ); return $form; }
We have added a form element definition to the form
array. Form elements are defined with a minimum of a type to specify what the element is and a title to act as the label. The title uses the t
method to ensure that it is translatable.
Adding a submit button is done by providing an element with the type submit.
drupalform.routing.yml
in the module's folder. A route entry will be created to instruct Drupal to use DrupalCoreFormFormBuilder
to create and display our form:drupalform.form: path: '/drupal-example-form' defaults: _title: 'Example form' _form: 'DrupaldrupalformFormExampleForm' requirements: _access: 'TRUE'
In Drupal, all routes have a name, and this example defines it as drupalform.form
. Routes then define a path attribute and override default variables. This route definition has altered the route's title, specified it as a form, and given the fully qualified namespace path to this form's class.
Routes need to be passed a requirements
property with specifications or else the route will be denied access.
/drupal-example-form
and the form is now visible, as shown in the following screenshot:This recipe creates a route to display the form. By passing the _form
variable in the defaults section of our route entry, we are telling the route controller how to render our route's content. The fully qualified class name, which includes the namespace, is passed to a method located in the form builder. The route controller will invoke Drupal::formBuilder()->getForm
(DrupaldrupalformFormExampleForm
) based on the recipe. At the same time, this can be manually called to embed the form elsewhere.
A form builder instance that implements DrupalCoreFormFormBuilderInterface
will then process the form by calling buildForm
and initiate the rendering process. The buildForm
method is expected to return an array of form elements and other API options. This will be sent to the render system to output the form as HTML.
Many components make up a form created through Drupal's Form API. We will explore a few of them in depth.
A form is a collection of form elements, which are types of plugin in Drupal 8. Plugins are small pieces of swappable functionalities in Drupal 8. Plugins and plugin development are covered in Chapter 7, Plug and Play with Plugins. At the time of writing this module, the Drupal.org Form API reference table was severely out of date and did not reflect all of the form element types available.
Here are some of the most common element properties that can be used:
weight
: This is used to alter the position of a form element in a form. By default, elements will be displayed in the order in which they were added to the form array. Defining a weight allows a developer to control element positions.default_value
: This gives a developer the ability to prefill the element with a value. For example, when building configuration forms that have existing data or when editing an entity.placeholder
: This is new to Drupal 8. Drupal 8 provides a new HTML5 support, and this attribute will set the placeholder attribute on the HTML input.The DrupalCoreFormFormStateInterface
object represents the current state of the form and its data. The form state contains user-submitted data for the form along with build state information. Redirection after form submission is handled through the form state as well. You will interact more with the form state during the validation and submission recipes.
Drupal utilizes a cache table for forms. This holds the build table, as identified by form build identifiers. This allows Drupal to validate forms during AJAX requests and easily build them when required. It is important to keep the form cache in persistent storage; otherwise, there may be repercussions, such as loss of form data or invalidating forms.