John
John John has been developing websites and software for over 20 years. Focusing on Drupal over the last 12 years.

Drupal 8: Creating custom forms

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.

Custom Form 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.

comments powered by Disqus