Drupal: Extending Core Configuration, Extending Core Forms, and Overriding Core Routes

04/03/2017 - 14:51

Overview

When working with Drupal, sometimes it is desirable to extend the configuration provided by core or other modules, extending a form provided by core or other modules, and/or overriding forms on a route. This tutorial will explain how to add a new field to the site for site description, to be used in structured data (aka RDF) on sites. This setting will be global to the site, so the most natural place for this setting is on the basic site settings page, located at /admin/config/system/site-information. This page represents the system.site configuration, and this tutorial will explain how to add a new form element to this page, adding the submitted data to the system.site configuration.

Requirements and Setup

To follow along with this tutorial, you will need to understand how to create a module in Drupal, and how the Form API works, as knowledge of these topics is assumed. On top of this, a knowledge of routes, services, and configuration will be beneficial, though not absolutely required.

To get started on this tutorial, you will create a module with a machine name of describe_site.

Steps

The following steps achieve the goal of adding a field for site description to the site settings page:

  1. Extend Drupal core's system.site configuration, creating the system.site.description configuration
  2. Extend the site settings form, adding a textarea for the site description
  3. Tell the routing system to use the extended form, rather than the form provided by core

So, let's get to it!

Step 1: Extend Drupal core's system.site configuration

Drupal core provides the system.site configuration with the following values:

  • uuid
  • name
  • mail
  • slogan
  • etc...

The goal is to add an additional item to this configuration: description. First, create the following file: describe_site.module, and add the following code:

  1. /**
  2.  * Implements hook_config_schema_info_alter().
  3.  */
  4. function describe_site_config_schema_info_alter(&$definitions) {
  5. $definitions['system.site']['mapping']['description'] = [
  6. 'type' => 'string',
  7. 'label' => 'Site Description',
  8. ];
  9. }

This code maps description, a string, to the system.site configuration. When Drupal compiles the configuration, it merges the original system.site configuration with the configuration alterations provided in the above code, maintaining the original configuration with the extended configuration.

The next step is to create a form to set the value of the description.

Step 2: Extend the site settings form

The next step is to add a textarea to the existing site settings form. To keep the code as slim and maintainable as possible, the form class provided by core for the basic settings page is extended, and the new textarea into which the description will be added. This discription will be saved to configuration when the form is submitted.

The original form provided by core is Drupal\system\Form\SiteInformationForm. The buildForm() method is overridden to add the textarea, and the submitForm() method is overridden to save the submitted description value.

The first thing to do is create the form at describe_site/src/Form/DescribeSiteSiteInformationForm.php:

  1. <?php
  2.  
  3. namespace Drupal\describe_site\Form;
  4.  
  5. // Classes referenced in this class:
  6. use Drupal\Core\Form\FormStateInterface;
  7. use Drupal\Core\Form\ConfigFormBase;
  8.  
  9. // This is the form being extended:
  10. use Drupal\system\Form\SiteInformationForm;
  11.  
  12. /**
  13.  * Configure site information settings for this site.
  14.  */
  15. class DescribeSiteSiteInformationForm extends SiteInformationForm {
  16.  
  17. /**
  18.   * {@inheritdoc}
  19.   */
  20. public function buildForm(array $form, FormStateInterface $form_state) {
  21. // Retrieve the system.site configuration
  22. $site_config = $this->config('system.site');
  23.  
  24. // Get the original form from the class we are extending
  25. $form = parent::buildForm($form, $form_state);
  26.  
  27. // Add a textarea to the site information section of the form for the description
  28. $form['site_information']['site_description'] = [
  29. '#type' => 'textarea',
  30. '#title' => $this->t('Site description'),
  31. // The default value is the new value that was added to the configuration
  32. // in step 1
  33. '#default_value' => $site_config->get('description'),
  34. '#description' => $this->t('The description of the site'),
  35. ];
  36.  
  37. return $form;
  38. }
  39.  
  40. /**
  41.   * {@inheritdoc}
  42.   */
  43. public function submitForm(array &$form, FormStateInterface $form_state) {
  44. $config = $this->config('system.site');
  45.  
  46. // The site_description is retrieved from the submitted form values
  47. // and saved to the 'description' element of the system.site configuration.
  48. $new_description = $form_state->getValue('site_description');
  49. $config->set('description', new_description);
  50.  
  51. // Save the configuration
  52. $config ->save();
  53.  
  54. // Pass the remaining values off to the parent form that is being extended,
  55. // so that that the parent form can process the values.
  56. parent::submitForm($form, $form_state);
  57. }
  58. }

As you can see in the above code, a new textarea named site_description has been added to the form, and the submitted value is saved to configuration when the form is submitted. The last thing to do is tell Drupal to use this overridden form instead of the original form when accessing the site settings page.

Step 3: Tell the routing system to use the extended form

The first thing to do in determining how to override the route, is to determine exactly what route needs to be overriden. In this case, the route is system.site_information_settings which can be found in the file system.routing.yml. Take a look at the route in question:

  1. system.site_information_settings:
  2.   path: '/admin/config/system/site-information'
  3.   defaults:
  4.   _form: 'Drupal\system\Form\SiteInformationForm'
  5.   _title: 'Basic site settings'
  6.   requirements:
  7.   _permission: 'administer site configuration'

This route loads the original form by setting the _form key in the defaults on the route.

Overriding this requires creating a route subscriber class that extends the Drupal\Core\Routing\RouteSubscriberBase, and implementing the alterRoutes() method.

The following code goes in to describe_site/src/Routing/DescribeSiteRouteSuscriber.php:

  1. <?php
  2.  
  3. namespace Drupal\describe_site\Routing;
  4.  
  5. // Classes referenced in this class
  6. use Drupal\Core\Routing\RouteSubscriberBase;
  7. use Symfony\Component\Routing\RouteCollection;
  8.  
  9. /**
  10.  * Listens to the dynamic route events.
  11.  */
  12. class DescribeSiteRouteSubscriber extends RouteSubscriberBase {
  13. /**
  14.   * {@inheritdoc}
  15.   */
  16. protected function alterRoutes(RouteCollection $collection) {
  17. // Change form for the system.site_information_settings route
  18. // to Drupal\describe_site\Form\DescribeSiteSiteInformationForm
  19. // Act only on the system.site_information_settings route.
  20. if ($route = $collection->get('system.site_information_settings')) {
  21. // Next, set the value for _form to the form override.
  22. $route->setDefault('_form', 'Drupal\describe_site\Form\DescribeSiteSiteInformationForm');
  23. }
  24. }
  25. }

The last step that needs to happen before this route will be used is to register the route subscriber. This happens in describe_site/describe_site.services.yml:

  1. services:
  2. # This is an arbitrary name, but should be made descriptive
  3.   describe_site.route_subscriber:
  4. # Tell Drupal which class to use
  5.   class: 'Drupal\describe_site\Routing\DescribeSiteRouteSubscriber'
  6. # This next code tells Drupal to use this class when
  7. # building routes.
  8.   tags: - { name: event_subscriber }

With this .yml file, Drupal now knows to look for the new class when accessing the route for the site settings page. When it builds the route, the overridden form is used instead of the one provided by core.

Summary

So there you have it. With the code in this tutorial, the form provided by Drupal core was overridden with a form that captures a description, and saves it to the site configuration.

After enabling this module (or clearing the cache if the module was already enabled), when visiting the system settings page, there will be a new textarea into which the site description can be saved and migrated.

Complete Code

describe_site.module:

  1. /**
  2.  * Implements hook_config_schema_info_alter().
  3.  */
  4. function describe_site_config_schema_info_alter(&$definitions) {
  5. $definitions['system.site']['mapping']['description'] = [
  6. 'type' => 'string',
  7. 'label' => 'Site Description',
  8. ];
  9. }

describe_site/src/Form/DescribeSiteSiteInformationForm.php:

  1. <?php
  2.  
  3. namespace Drupal\describe_site\Form;
  4.  
  5. // Classes referenced in this class:
  6. use Drupal\Core\Form\FormStateInterface;
  7. use Drupal\Core\Form\ConfigFormBase;
  8.  
  9. // This is the form being extended:
  10. use Drupal\system\Form\SiteInformationForm;
  11.  
  12. /**
  13.  * Configure site information settings for this site.
  14.  */
  15. class DescribeSiteSiteInformationForm extends SiteInformationForm {
  16.  
  17. /**
  18.   * {@inheritdoc}
  19.   */
  20. public function buildForm(array $form, FormStateInterface $form_state) {
  21. // Retrieve the system.site configuration
  22. $site_config = $this->config('system.site');
  23.  
  24. // Get the original form from the class we are extending
  25. $form = parent::buildForm($form, $form_state);
  26.  
  27. // Add a textarea to the site information section of the form for the description
  28. $form['site_information']['site_description'] = [
  29. '#type' => 'textarea',
  30. '#title' => $this->t('Site description'),
  31. // The default value is the new value that was added to the configuration
  32. // in step 1
  33. '#default_value' => $site_config->get('description'),
  34. '#description' => $this->t('The description of the site'),
  35. ];
  36.  
  37. return $form;
  38. }
  39.  
  40. /**
  41.   * {@inheritdoc}
  42.   */
  43. public function submitForm(array &$form, FormStateInterface $form_state) {
  44. $config = $this->config('system.site');
  45.  
  46. // The site_description is retrieved from the submitted form values
  47. // and saved to the 'description' element of the system.site configuration.
  48. $new_description = $form_state->getValue('site_description');
  49. $config->set('description', new_description);
  50.  
  51. // Save the configuration
  52. $config ->save();
  53.  
  54. // Pass the remaining values off to the parent form that is being extended,
  55. // so that that the parent form can process the values.
  56. parent::submitForm($form, $form_state);
  57. }
  58. }

describe_site/src/Routing/DescribeSiteRouteSuscriber.php:

  1. <?php
  2.  
  3. namespace Drupal\describe_site\Routing;
  4.  
  5. // Classes referenced in this class
  6. use Drupal\Core\Routing\RouteSubscriberBase;
  7. use Symfony\Component\Routing\RouteCollection;
  8.  
  9. /**
  10.  * Listens to the dynamic route events.
  11.  */
  12. class DescribeSiteRouteSubscriber extends RouteSubscriberBase {
  13. /**
  14.   * {@inheritdoc}
  15.   */
  16. protected function alterRoutes(RouteCollection $collection) {
  17. // Change form for the system.site_information_settings route
  18. // to Drupal\describe_site\Form\DescribeSiteSiteInformationForm
  19. // Act only on the system.site_information_settings route.
  20. if ($route = $collection->get('system.site_information_settings')) {
  21. // Next, set the value for _form to the form override.
  22. $route->setDefault('_form', 'Drupal\describe_site\Form\DescribeSiteSiteInformationForm');
  23. }
  24. }
  25. }

describe_site/describe_site.services.yml:

  1. services:
  2. # This is an arbitrary name, but should be made descriptive
  3. describe_site.route_subscriber:
  4. # Tell Drupal which class to use
  5. class: 'Drupal\describe_site\Routing\DescribeSiteRouteSubscriber'
  6. # This next code tells Drupal to use this class when
  7. # building routes.
  8. tags: - { name: event_subscriber }