Drupal 8 module development #3 - adding a settings page

This is the 3rd of several on-going blog post series which aim to educate on the process of porting modules to Drupal 8 with real life examples by porting a popular Drupal 7 module to Drupal 8.

Drupal modules often provide an administrator with a settings page so that various configuration options can be tuned and setup using the web interface. We will take a look at how we can create a configuration page and get to know some basic interactions with Drupal's new configuration system.

In the previous article we briefly introduced the routing system, with adding a basic route. When that route is triggered Drupal will be searching for the settings form class implementation - Drupal\globalredirect\Form\GlobalredirectSettingsForm that we defined in the route setting.

 

Let's begin by creating this directory structure of lib/Drupal/globalredirect/Form in the module's root directory and then create the form class GlobalRedirectSettingsForm.php which will contain the skeleton class:

<?php
/**
 * @file
 * This is the GlobalRedirect admin include which provides an interface to global redirect to change some of the default settings
 * Contains \Drupal\globalredirect\Form\GlobalredirectSettingsForm.
 */

namespace Drupal\globalredirect\Form;

use Drupal\system\SystemConfigFormBase;
use Drupal\Core\Config\ConfigFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Defines a form to configure module settings.
 */
class GlobalredirectSettingsForm extends SystemConfigFormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormID() {
    return 'globalredirect_settings';
  }

}

A few things to point out about this class:

  1. It defines the namespace, per the directory structure the file resides in.
  2. It makes use of several classes we may need access to. In particular, ConfigFactory which provides access to Drupal's configuration system so that we can get and save configuration items, and SystemConfigFormBase which is the base class for doing module's settings form, basically replacing Drupal 7's system_settings_form(). It extends on FormBase, the very basic form class which implements FormInterface.
  3. It implements getFormID() which returns a string, defining the form name.

Now that we have a very basic implementation of the module's settings form we will need to implement some other methods which will get this form to actually display the form (to build the form if so to speak), handle submit actions, etc. Let's proceed to update the form with some more code:

<?php
/**
 * @file
 * This is the GlobalRedirect admin include which provides an interface to global redirect to change some of the default settings
 * Contains \Drupal\globalredirect\Form\GlobalredirectSettingsForm.
 */

namespace Drupal\globalredirect\Form;

use Drupal\system\SystemConfigFormBase;
use Drupal\Core\Config\ConfigFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Defines a form to configure module settings.
 */
class GlobalredirectSettingsForm extends SystemConfigFormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormID() {
    return 'globalredirect_settings';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, array &$form_state) {
    
    // Get all settings
    $config = $this->configFactory->get('globalredirect.settings');
    $settings = $config->get();

    $form['settings'] = array(
      '#tree' => TRUE,
    );

    $form['settings']['deslash'] = array(
      '#type' => 'checkbox',
      '#title' => t('Deslash'),
      '#description' => t('If enabled, this option will remove the trailing slash from requests. This stops requests such as example.com/node/1/ failing to match the corresponding alias and can cause duplicate content. On the other hand, if you require certain requests to have a trailing slash, this feature can cause problems so may need to be disabled.'),
      '#default_value' => $settings['deslash'],
    );

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

  /**
   * {@inheritdoc}
   * Compares the submitted settings to the defaults and unsets any that are equal. This was we only store overrides.
   */
  public function submitForm(array &$form, array &$form_state) {

    // Get config factory
    $config = $this->configFactory->get('globalredirect.settings');

    $form_values = $form_state['values']['settings'];

    $config
      ->set('deslash', $form_values['deslash'])
      ->save();

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

  }
  
}

The updated class has now implementations for buildForm() and submitForm(). The code shows only how the 'deslash' configuration property is configured to avoid pasting a very large chunk of code with all the settings this module actually configures.

While the ConfigFactory and ContainerInterface classes aren't really required explicitly, if you're wondering what is their purpose then you should consult the class source of SystemConfigFormBase, which implements the required methods for dependency injection. What does that mean? To explain roughly, the class makes use of constructor injection principle to make some objects avaialble for you "behind the scenes". This eliminates cluttered code and allows for more reusable code. If you're asking yourself where is the configFactory coming from in our class? the answer is dependency injection. We could've used the global config() function or possibly the static Drupal::Config() method but we didn't, because using the configFactory is a more preferred method.

 

Comments

6

Thanks for the posts. I just wanted to point out a couple of minor things that I noticed. First, as of last week SystemConfigFormBase is now ConfigFormBase: https://drupal.org/node/2089731. Also, {@inheritdoc} should not be combined with other comments, but there is an issue to change that: https://drupal.org/node/1994890.

 
SystemConfigFormBase is gone, its ConfigFormBase
 
Instead of $this->configFactory->get() you have $this->config() available
You can use $this->t() not t()
 
<code>use Drupal\Core\Config\ConfigFactory;</code> and <code>use Symfony\Component\DependencyInjection\ContainerInterface;</code> are not needed

great feedback, thanks guys!

I'll be sure to update the module port as well.

At this point if you make the corrections regarding ConfigFormBase and other changes to the API to get this up and running, the fist time you go to the settings page, you'll get an warning that $settings['deslash'] doesn't exist, because it doesn't yet. What do you think is the best way of handling that...
 
I was thinking around line 34 after you get all the settings you could check to see if it exists and then set it to zero if it doesn't. What's your take on that?
 
if(!isset($settings['deslash'])) {
 $settings['deslash'] = 0;
}
 
Cheers,
Andrew

I wanted to add your RSS to feedly but your rss.xml link is broken.

Thanks for the heads-up, I fixed it :)

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

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