Drupal 8: Creating custom forms

There will be many occasions where you will have to create custom forms in Drupal 8. Drupal Console can be used again to create the boilerplate code, which we can then alter to suit our needs.
Generate a form with 2 fields and a config file specifying the module name, the class, a form id, the inputs and the path.
drupal generate:form \
--module="mymodule" \
--class="MyModuleForm" \
--form-id="mymodule_form" \
--config-file \
--inputs='"name":"inputname", "type":"text_format", "label":"InputName", "options":"", "description":"Just a text input", "maxlength":"", "size":"", "default_value":"", "weight":"0", "fieldset":""' \
--inputs='"name":"email", "type":"email", "label":"Email", "options":"", "description":"Just an email input", "maxlength":"", "size":"", "default_value":"", "weight":"0", "fieldset":""' \
--path="/mymodule/form/default"
The great thing about Drupal Console is that you have the option of injecting services into your newly created form class. Once complete, you will end up with the following directory structure.
If we open up the src/Form/MyModuleForm.php
file, you will see that Drupal Console has done a lot of the work for us. The formatting is a little bit scruffy but you can soon tidy that up as you refactor.
<?php
namespace Drupal\mymodule\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Database\Driver\mysql\Connection;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\webprofiler\Config\ConfigFactoryWrapper;
/**
* Class MyModuleForm.
*/
class MyModuleForm extends FormBase {
/**
* Drupal\Core\Session\AccountProxyInterface definition.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser;
/**
* Drupal\Core\Database\Driver\mysql\Connection definition.
*
* @var \Drupal\Core\Database\Driver\mysql\Connection
*/
protected $database;
/**
* Drupal\Core\Logger\LoggerChannelFactoryInterface definition.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
protected $loggerFactory;
/**
* Drupal\webprofiler\Config\ConfigFactoryWrapper definition.
*
* @var \Drupal\webprofiler\Config\ConfigFactoryWrapper
*/
protected $configFactory;
/**
* Constructs a new MyModuleForm object.
*/
public function __construct(
AccountProxyInterface $current_user,
Connection $database,
LoggerChannelFactoryInterface $logger_factory,
ConfigFactoryWrapper $config_factory
) {
$this->currentUser = $current_user;
$this->database = $database;
$this->loggerFactory = $logger_factory;
$this->configFactory = $config_factory;
}
public static function create(ContainerInterface $container) {
return new static(
$container->get('current_user'),
$container->get('database'),
$container->get('logger.factory'),
$container->get('config.factory')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'mymodule_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['inputname'] = [
'#type' => 'text_format',
'#title' => $this->t('InputName'),
'#description' => $this->t('Just a text input'),
];
$form['email'] = [
'#type' => 'email',
'#title' => $this->t('Email'),
'#description' => $this->t('Just an email input'),
];
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Submit'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormtateInterface $form_state) {
// Display result.
foreach ($form_state->getValues() as $key => $value) {
drupal_set_message($key . ': ' . $value);
}
}
}
I selected to inject the current_user
, database
, logger.factory
and config.factory
services into my class. These are purely for demonstration purposes, so you can inject whichever services you require.
Using the form
So, if we wanted to then add this form to a twig template somewhere, we could implement template_preprocess_node
, load the form and pass it to the twig template in the $variables
array.
/**
* Implements template_preprocess_node().
*/
function risktheme_preprocess_node(&$variables) {
// Get the mymodule form.
$variables['mymodule_form'] = \Drupal::formBuilder()
->getForm('Drupal\mymodule\Form\MyModuleForm');
}
…and then in the twig template, it can be output as follows:
<div class="mymodule-form">
{{ mymodule_form }}
</div>
Please leave a comment with any feedback or if you feel I have explained something incorrectly.