Extra Block Types (EBT) - New Layout Builder experience❗

Extra Block Types (EBT) - styled, customizable block types: Slideshows, Tabs, Cards, Accordions and many others. Built-in settings for background, DOM Box, javascript plugins. Experience the future of layout building today.

Demo EBT modules Download EBT modules

❗Extra Paragraph Types (EPT) - New Paragraphs experience

Extra Paragraph Types (EPT) - analogical paragraph based set of modules.

Demo EPT modules Download EPT modules

Scroll
27/09/2019, by mikhail

In one of the previous lessons we have learned what hooks are, in this lesson we will work with hook_form_alter() hooks in practice and add functionality to the existing form.

Examples of code can be found at github:
https://github.com/levmyshkin/drupalbook8

In this lesson we will start to look at hooks in practice, later on we will return to hooks and look at a couple of other examples. For now, let's start with hook_form_alter().

In order to add a hook to the module, we need to create a file called MODULENAME.module, so we will create a file called drupalbook.module. This will be a PHP file where our hooks and auxiliary functions will be stored, for the rest it is better to use separate files and classes in the src folder. So far you can add just an opening tag to the file

drupalbook

Now let's add hook_form_alter(). If you are using PhpStorm, start writing the name of the hooks and PhpStorm will offer you to choose one of the hooks. When you select a hook this way, PhpStorm automatically substitutes arguments into the function and you don't need to remember or look in the help what arguments you need to add:

fubction

When you want to add a hook, you need to replace the word hook with the name of your module, and then the drupal will automatically insert your code into the hooks location. As a result, you should have this function:

/**
 * Implements hook_form_alter().
 */
function drupalbook_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
 
}

In the last lesson we have already considered that to be stored in $form, $form_state, in $form_id we will have to store the form id, which we defined in the appropriate method:

9.8. Working with forms in Drupal. Add configuration form programmatically.

Let's now extend the form of adding API keys that we did in the last lesson. To do this, we need to define the form id, you can see it in this method:

function getFormId() {
  return 'drupalbook_admin_settings';
}

Or open the DOM inspector and see the id attribute in HTML:

settings

Here we need to replace - (hyphens) with _ (lower underscores). But be careful, the id in the form tag attribute can be incremented after an AJAX-request, for example, drupalbook-admin-settings-0, drupalbook-admin-settings-1, drupalbook-admin-settings-2 and so on. We need a part without a number at the end, this is the part formed from the form id, which is specified in the getFormId() method.

Now we need to limit the execution of our code only for the form drupalbook_admin_settings, because the code that is in hook_form_alter(), will be executed for absolutely all forms that are generated through the Drupal Form API:

($form_id == 'drupalbook_admin_settings') {
  // Further code here.
}

Inside if we can write our code for the key API form. Let's add placeholders for text fields so that empty fields show what to write:

($form_id == 'drupalbook_admin_settings') {
  $form['drupalbook_api_key']['#attributes']['placeholder'] = 'API key';
  $form['drupalbook_api_client_id']['#attributes']['placeholder'] = 'API client ID';
}

To apply hook_form_alter(), clean the cache.

api key

Although #attributes are not listed in the documentation as a possible key for the textfield:

https://api.drupal.org/api/drupal/core%2

However, this key can still be used to specify the attributes of the tags: class, placeholder, different rel-attributes. More clear table is for Drupal 7 Form API:

https://api.drupal.org/api/drupal/developer%21topics%21forms_api_reference.html/7.x

You can find #attributes here for textfield fields.

Attribute values are set in the Form API when generating (render) a form element in the setAttributes method:

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%

Although this is not stated in the API documentation form, you can see for yourself that it works.

You can also see what other values you can set for the form fields in the base FormElement class:
https://api.drupal.org/api/drupal/core%21lib%2

Drupal Form API looks unclear and confusing, so it is, I still do not understand some things in it. But to use this API is simple enough and clear enough, if at you at hand there is a working sample. Therefore, if you have any questions and you do not know how to do in the form of this or that, look on the Internet and you will find a 100% working example. You need to know that all forms go through all the APIs of Drupal Cache, Render, Theming and any field in the form you can change through hook_form_alter(), and you can already find out how to do that through Google. Also, the more you come across the Form API and the more you parse the examples with it, the easier and clearer this Form API will become for you. And don't worry that it's so big, just use only the part you need now.

Validate
Let's now look at how to use validate functions through the Form API. Let's check the Key API field so that it starts with the word google, for example google-KEY123a3sa. If we wrote the code for our form initially, we could insert the validateForm() method and do all the checks in it:

/**
 * {@inheritdoc}
 */
public function validateForm(array &$form, FormStateInterface $form_state) {
 
}

But often the forms we work with are in the contributed modules and it is better to write code for such forms in custom modules. So let's add a new validation function to the form:

['#validate'][] = 'drupalbook_settings_validate';

In the #validate array we store all the callback's and validation functions. Callback is a call of a function by its name, i.e. we'll add the ['callback_function1', 'callback_function2'] array and then take these names from the array and drupal will call these functions. In this case, the drupal will call these functions to check our form. And now we need to create the function drupalbook_settings_validate in the file drupalbook.module. In this function we will have parameters $form, $form_state:

/**
 * Custom validation callback.
 */
function drupalbook_settings_validate(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
 
}

And now let's add the check of the API Key field itself:

/**
 * Custom validation callback.
 */
function drupalbook_settings_validate(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  if (strpos($form_state->getValue('drupalbook_api_key'), 'google') === FALSE) {
    $form_state->setErrorByName('drupalbook_api_key', t('API Key must start from "google".'));
  }
}

We use the hard-comparison operator ===, because if google is at the beginning of the string, strpos() returns 0 and for PHP (0 == FALSE) will return TRUE, because in PHP '', 0, NULL, FALSE it is all empty values and they are equal in a simple comparison ==.

Now each time you save the form of the settings, a check will be performed, and if the check is not passed, the drupal will generate an error and the settings will not be saved:

settings form

Submit
After all validate functions have worked out and no errors have occurred, the drupal calls submit functions, they work after sending data from the form. You have already seen the method for submitForm() in the last lesson, where we save the data into configures. But we can perform other actions on submit, such as changing data or saving some data to other entities. Let's do another validation function that will display an additional message on how to use the API Key. In hook_form_alter() we will add a function name:

['#submit'][] = 'drupalbook_settings_submit';

Now in the drupalbook_settings_settings_submit() function, where we also pass $form, $form_state in the parameters, we display the message.

/**
 * Custom submit callback.
 */
function drupalbook_settings_submit(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  \Drupal::messenger()->addStatus(
    t(htmlentities('Insert API key in your  tag:  .'))
  );
}

We use the htmlentites() function to ensure that all special characters we use to output the "<", "/", ">" tags are not deleted by the drupal. All HTML text is cut out of the t() function, so that it cannot be pasted into the text, for example, if such a code contains javascript redirect to another site, it will be redirected when displaying messages. That's why we use the htmlentities function to display the tags.

The message will not be displayed if the validation functions fail, only when there are no errors in the form we see the message.

drupalbook form

Previously, the drupal_set_message function was used in drupal to output messages:

(t('An error occurred and processing did not complete.'));

But now everybody tries to unify and lead to the use of OOP everywhere.

Below is the whole current code of the drupalbook.module file:

/**
 * Implements hook_form_alter().
 */
function drupalbook_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  if ($form_id == 'drupalbook_admin_settings') {
    $form['drupalbook_api_key']['#attributes']['placeholder'] = 'API key';
    $form['drupalbook_api_client_id']['#attributes']['placeholder'] = 'API client ID';
 
    $form['#validate'][] = 'drupalbook_settings_validate';
    $form['#submit'][] = 'drupalbook_settings_submit';
  }
}
 
/**
 * Custom validation callback.
 */
function drupalbook_settings_validate(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  if (strpos($form_state->getValue('drupalbook_api_key'), 'google') === FALSE) {
    $form_state->setErrorByName('drupalbook_api_key', t('API Key must start from "google".'));
  }
}
 
/**
 * Custom submit callback.
 */
function drupalbook_settings_submit(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  // drupal_set_message is deprecated
  // drupal_set_message(t('An error occurred and processing did not complete.'));
 
  \Drupal::messenger()->addStatus(
    t(htmlentities('Insert API key in your  tag:  .'))
  );
}

This concludes our lesson on Form API, but does not end the study of Form API, we will not once again encounter the functions of validate, submit and various fields. We will also understand in one of the lessons how to use AJAX and form_states through Form API.

Examples of code can be found at github:
https://github.com/levmyshkin/drupalbook8