Hooks
This chapter discusses use cases for the hooks provided by Synfony-based Drupal.
Table of contents
https://www.drupal.org/project/develDrupal 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=2766How 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.
However, 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.
As 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:
- SO: How to use hook_node_access in Drupal 8
- DSE: Provide condtional 403 response when a user tries to view a node
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.
After
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.]
Callback
Blank
NotBlank
Email
: Validates an email address.Country
AddressFormat
CommentName
DateTimeFormat
FileValidation
FileUriUnique
GeoType
LinkType
LinkNotExistingInternal
LinkExternalProtocols
LinkAccess
MenuTreeHierarchy
MenuSettings
PathAlias
TaxonomyHierarchy
UserMailRequired
: Checks if the user's email address is provided if required.UserNameUnique
UserName
ProtectedUserField
UserMailUnique
ContentTranslationSynchronizedFields
Uuid
: Validates a UUID.NotNull
: NotNull constraint.PrimitiveType
: Supports validating all primitive types.Regex
: Regex constraint.AllowedValues
: Checks for the value being allowed.Null
: Null constraint.Length
: Length constraint.ComplexData
: Validates properties of complex data structures.Count
: Count constraint.UniqueField
: Checks if an entity field has a unique value.Range
: Range constraint.EntityChanged
EntityType
EntityHasField
EntityUntranslatableFields
ReferenceAccess
Bundle
ValidReference
ValidPath
UniquePathAlias
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:
- DO docs: Entity validation API
- Berramou.com: Create custom validation constraint.
- DA.SE: How do I add a custom validation handler to an existing form/field?
- DO forum: User Entity - Custom Validation Constraint
- DO issue: Implement custom validation constraint
- DO forum: My question
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].