Hooks

by Gisle Hannemyr

This chapter discusses use cases for the hooks provided by Synfony-based Drupal.

Table of contents

https://www.drupal.org/project/devel

Drupal extensions discussed in this chapter:. Devel.

Introduction

Hooks allow extensions to alter and extend the behavior of Drupal core, or another module. They are one of the various ways that code components in Drupal can communicate with one another. By using hooks one can change how core or another module works – without hacking the existing code.

Discover existing hooks

Source: https://drupalize.me/tutorial/discover-existing-hooks?p=2766

How do you figure out what hooks are available to implement? How do you know which hook to implement to accomplish a task?

The list of hooks that are available to implement in your custom Drupal module varies depending on the modules enabled for a site. Each module can optionally invoke new hooks. There are also some hooks invoked by Drupal core subsystems like Form API that are always present. This can make it a little bit tricky sometimes to figure out what hooks are available, and which one to implement.

Look at the source

The canonical location for hook definitions is in *.api.php files contained within Drupal core and contributed modules. You can browse through these files, like core/modules/node/node.api.php for example. Or use your IDE to search for functions whose name starts with hook_. The function names are generally indicative of what the hook does, but it can also help to read the documentation for more detail.

The documentation for a hook, what it does, and what parameters implementation functions receive is in the PHP @docblock comment for the hook_ function in question.

This technique is the best way to find documentation for hooks provided contributed modules. Look for a MODULENAME.api.php file in the root directory of the module.

Example hook definition from node.api.php:

[TBA]

Drupal core

Those @docblock comments from above are parsed and can be searched on api.drupal.org. In most cases this should be your first stop when trying to figure out what hook to implement. You can get a complete list of hooks here, or type "hook_" into the search field.

This is also an extremely useful reference even when you already know what hook you want to implement. We use it frequently to do things like look up the function signature, or remind ourselves what values a specific parameter might contain.

Find all modules that implement a specific hook

One good way to learn more about what a particular hook can be used to accomplish is to explore existing implementations. You can do so by seeking out the code in existing .module files.

The Devel extension contains a handy drush command that will give you a list of all implementations of a specific hook.

Download and install Devel (if you have not done so already). This provides a set of drush devel commands for use.

E.g.: The following lists all implementations of hook_help() in the current code base.

$ drush fn-hook help # Alias of drush devel:hook

Use cases

This section contains usage notes about useful hooks.

hook_form_alter, etc.

DOA: function hook_form_alter.

The principal use of hook form_alter() is to alter a form before it is rendered. For instance, to add form elements to the node form.

tipHowever, it can also be used to find the form id, which is handy to instead implement more focused hook_form_FORM_ID_alter().

To do so, enable Devel and insert the following in a custom module:

  function MYMODULE_form_alter(&$form,
  \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  dpm($form_id, 'form_id');
}

Assuming you got something like "node_mybundle_form"

function  hook_form_FORM_ID_alter(&$form,
  \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
}

function hook_form_node_mybundle_form_alter(&$form,
  \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
}

When altering a node form, the node entity can be retrieved by invoking $form_state->getFormObject()->getEntity().

When you visit the page the form is on, the name will be displayed.

hook_ENTITY_TYPE_presave

DOA: function hook_ENTITY_TYPE_presave.

Use hook_ENTITY_TYPE_presave() to act on a specific type of entity before it is created or updated.

You can get the original entity object from $entity->original when it is an update of the entity.

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function MYMODULE_node_presave(Drupal\node\NodeInterface $entity) {
    if ($entity->getType() == 'page') {
       dpm($entity, 'entity');
       $entity->setTitle('Hello');
       $entity->set('body', 'this is body');
    } 
}

or if the parameter is an entity:

function MYMODULE_node_presave(\Drupal\Core\Entity\EntityInterface $entity) {
}

To loop over a multivalue field to change their value, use this (it appends an exclamation mark to each instance):

$fields = $entity->field_myfield->getValue();
$newfields = [];
foreach ($fields as $field) {
  $newfields[] = $field['value'] . '!';
}
$entity->field_myfield = $newfields;

hook_ENTITY_TYPE_insert

DOA: function hook_ENTITY_TYPE_insert.

This hook responds to the creation of a new entity of a particular type.

This hook runs once after the entity has been stored. Note that hook implementations may not alter the stored entity data.

See alsoAs explained by Ajay Reddy's answer on DSE: How do I update a field value in hook_entity_insert()?, if you want to this, you're probably looking for hook_ENTITY_TYPE_presave.

I use ut in custom project Contactlist to delete a duplicate contact requst after it has been stored.

hook_ENTITY_TYPE_access

DOA: function hook_ENTITY_TYPE_access.

Sources:

hook_entity_bundle_info_alter

DOA: function hook_entity_bundle_info_alter

The correct way to do add validation for a field in an entity (e.g. a node) is to register it as a constraint. You may add a predefined constraint, or add a custom one.

If your field created using the GUI or it's already created by Drupal like (title, body, …) you should implement hook_entity_bundle_field_info_alter() and add another constraint using addConstraint(). Example:

use Drupal\Core\Entity\EntityTypeInterface;

function MYMODULE_entity_bundle_field_info_alter(&$fields,
  EntityTypeInterface $entity_type, $bundle) {
  if ($entity_type->id() === 'ENTITY_TYPE' && $bundle === 'BUNDLE_NAME') {
    if (isset($fields['FIELD_NAME'])) {
      $fields['FIELD_NAME']->addConstraint('UniqueField', [
        'message' => t('Field must be unique.')
      ]);
    }
  }
}

The message parameter is optional. There is a default: 'A @entity_type with @field_name %value already exists.', which is usually fine.

The 'ENTITY TYPE' is 'node' for standard content types.

noteAfter adding the hook, you must rebuild the cache to make it effective. If the constraint passed to addConstraint() does not exist, you get a WSOD if you try to create a new entity instance after the cache has been rebuilt.

The constraints you can pass are these. Only some of them are annotated. I haven't found the docs yet, but google usually serves up the landing page if you want to see what it actually does. [I got this list from the WSOD error message when passing a garbage value.]

Source: Berramou.com: How to add constraint to field in a content type (some errors).

You may also add a custom constraint. The hook should like the following [it also needs a Plugin]:

use Drupal\Core\Entity\EntityTypeInterface;

/**
 * Implements hook_entity_bundle_field_info_alter()
 */
function MYMODULE_entity_bundle_field_info_alter(&$fields,
  EntityTypeInterface $entity_type, $bundle) {
  if ($entity_type->id() === 'ENTITY_TYPE' && $bundle === 'BUNDLE_NAME') {
    if (isset($fields['FIELD_NAME'])) {
      $fields['FIELD_NAME']->addConstraint('CustomFieldConstraint');
    }
  }
}

Another example:

/**
 * Implements hook_entity_bundle_field_info_alter()
 */
function MYMODULE_entity_bundle_field_info_alter(&$fields,
  \Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle) {
  if ($entity_type->id() == 'user' && !empty($fields['nickname'])) {
    $fields['nickname']->addConstraint('unique_nickname', []);
  }
}

Please note that the path to the constraint should be /modules/custom/MODULENAME/src/Plugin/Validation/Constraint.

Sources:

Final word

There are lots of different ways to learn about the hooks available to implement in Drupal. All of which are wrappers around the canonical documentation that exists in .api.php files in the code base. These wrappers often times make it easier to quickly find what you're looking for, but if they aren't working, or the hook you want information about isn't part of Drupal core, your best bet is to open up an editor and read the source.


Last update: 2022-05-29 [gh].