Alter form without hooks system

Hooks system in Drupal plays a major part in Drupal 7 or 8. If any Drupal developer wants to extend the Drupal core functionality they can use hooks and extend it. E.g Node API, Entity API, Form API, etc.

The issue with the hooks: As we know MVC or Symfony libraries are now implemented in Drupal 8 and most of the hooks is deprecated now. But In future version of Drupal, all the hooks system will be removed or depreciated from Drupal. So every Drupal developer has to start work using Drupal recommended way to extend the Drupal core or contributed module functionality.

In this blog, we will discuss how can we extend the Drupal core functionality with an example like extending site-information form by using Drupal class and services, etc. So let's start step by step with an example called extend_drupal:

Step-1:
Create an extend_drupal.info.yml file to define our module information.


name: Site Api Key
description: Provides enhancement and customization to site-information.
type: module
package: Custom
core: 8.x

Step-2:
In this step, we will add a new custom configuration to the default site.configuration. When we enable our custom module it will tell Drupal to compiles and merge our custom configuration with default site.configuration, maintaining the default configuration with the configuration we've added. To do this we will add our custom configuration in /config/schema/describe_site.schema.yml file.


# We want to extend the system.site configuration
system.site:
  mapping:
    # Our field name is 'siteapikey'
    siteapikey:
      type: string
      label: 'Site API Key'

Step-3:
We need to determine how to override the route and which route we need to override. In our case, we need to override core system.site_information_settings and we can find this file in core/modules/system/system.routing.yml path. Now create a new file called extend_drupal.routing.yml file and add this below code in it.


apikey.page_json:
  path: '/page_json/{key}/{nid}'
  defaults:
    _controller: '\Drupal\apikey\Controller\ApiKeyController::nodejsonresponse'
  options:
    parameters:
      nid:
        type: entity:node
  requirements:
    _permission: 'access content'

Step-4:
Next step we need to extend our system site-information default form. For this, we will add new textfield called sitekey, which will be used to submit and save our custom field value with default system site-information form.
The default form is provided by the core in Drupal\system\Form\SiteInformationForm. We will override this form by buildform() function to add new textfield called siteapi and override the submitForm() method to save the custom field value with default form value. 
We will create our custom form at /src/Form/ExtendDrupal.php, where we will add our code:


<?php

namespace Drupal\apikey\Form;

// Classes referenced in this class:
use Drupal\Core\Form\FormStateInterface;

// This is the form we are extending.
use Drupal\system\Form\SiteInformationForm;

/**
 * Configure site information settings for this site.
 */
class SiteApiKey extends SiteInformationForm {

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    // Retrieve the system.site configuration.
    $site_config = $this->config('system.site');
    // Get the original form from the class we are extending.
    $form = parent::buildForm($form, $form_state);

    // Add a textarea to the site information section of the form for our
    // description.
    $form['site_information']['siteapikey'] = [
      '#type' => 'textfield',
      '#title' => t('Site API Key'),
      // The default value is the new value we added to our configuration
      // in step 1.
      '#default_value' => $site_config->get('siteapikey') ? $site_config->get('siteapikey') : 'No API Key yet',
      '#description' => $this->t('The Site API Key.'),
    ];

    // Update form action test.
    $form['actions']['submit']['#value'] = t('Update Configuration');
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    // Check condition for default value.
    if (($form_state->getValue('siteapikey') != 'No API Key yet') &&
    !empty($form_state->getValue('siteapikey'))) {
      // system.site.description configuration.
      $this->config('system.site')
      // The siteapikey is retrieved from the submitted form values
      // and saved to the 'description' element of the system.site config.
        ->set('siteapikey', $form_state->getValue('siteapikey'))
      // Make sure to save the configuration.
        ->save();

      \Drupal::messenger()->addMessage($this->t('Site API Key has been saved with %value', [
        '%value' => $form_state->getValue('siteapikey'),
      ]), 'status', TRUE);

      // Pass the remaining values off to the original form that we have
      // extended, so that they are also saved.
      parent::submitForm($form, $form_state);
    }
  }
}

Step-5:
Now create a new file called extend_drupal.services.yml file and add this below code in it. This will tells Drupal that we have a custom class that we created a route subscriber.


services:
  # This is an arbitrary name, but should be made description
  apikey.route_subscriber:
    # Tell Drupal which class to use
    class: 'Drupal\apikey\Routing\SiteApiKeyRouteSuscriber'
    # This next code is the key, as it tells Drupal to use our class when
    # building routes.
    tags:
      - { name: event_subscriber }

Step-6:
Add a new class file in src/Routing/ExtendDrupal.php and place the below code in it. This routing code is used to extend form. In our case, we will extend the system.site_information_settings route which can be found in system.routing.yml file.


<?php

namespace Drupal\apikey\Routing;

// Classes referenced in this class.
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;

/**
 * Listens to the dynamic route events.
 */
class SiteApiKeyRouteSuscriber extends RouteSubscriberBase {

  /**
   * {@inheritdoc}
   */
  protected function alterRoutes(RouteCollection $collection) {
    // Change form for the system.site_information_settings route
    // to Drupal\siteapikey\Form\SiteApiKey
    // First, we need to act only on the system.site_information_settings route.
    if ($route = $collection->get('system.site_information_settings')) {
      // Next, we need to set the value for _form to the form we have created.
      $route->setDefault('_form', 'Drupal\apikey\Form\SiteApiKey');
    }
  }
}