Brown yarn roll with two brown crochet hooks on top of white surface.

A Plugin-based approach to Drupal Hooks.

Introduction

Welcome to the new Drupal hooks! This new approach might feel a bit intimidating at first, but in time you'll feel its advantages. Promised.

Purpose

This module is dedicated to implementing the Drupal Event Subscriber system instead of the default hook system. It also provides base Hook Plugins and Annotations for you to extend, so there's even less code to be written to get the job done.

Structure

We're using on a combination of two methodologies:

That's right, we're going to do a lot less hooks implementations in a .module file from now on.

How To Use

This module primarily does two things:

  • It registers Event Subscribers that match Events dispatched by the Hook Event Dispatcher contrib module
  • It defines Services, Plugin Managers, Plugins and Annotations required for Plugin discovery in other modules

In the end, what you need to do is the following:

  • In your custom module, add a new class in the src/Plugin/crocheteer/Hook directory (directory names must match, including capital letters)
  • Give a meaningful name to your class following the usual hook declaration, e.g.:
    • HookEntityPresave (hook_entity_presave)
    • HookNodePresave (hook_ENTITY_TYPE_presave as hook_node_presave)
  • Make use of the desired Annotation in your class docblock comment, e.g. @HookEntityPresave
  • Fill in necessary annotation properties, e.g. id, title and entityTypes
  • Extend the appropriate Plugin class, e.g. HookEntityPresavePlugin
  • Implement the public function hook() method in your class

Here is a HookEntityPresave example implementation:


namespace Drupal\mymodule\Plugin\crocheteer\Hook;

use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\crocheteer\Plugin\Hook\Entity\HookEntityPresavePlugin;

/**
 * Example class for Hook Entity Presave.
 * 
 * Note that the entityTypes annotation definition property acts as a
 * restriction on affected entities. In this case, this Hook will only affect
 * node and user entities. Omitting entityTypes or leaving it empty will have
 * the Hook act on all entity types, without restrictions.
 *
 * @HookEntityPresave(
 *   id = "mymodule_hook_entity_presave_example",
 *   title = @Translation("My Module: Hook Entity Presave Example"),
 *   entityTypes = {
 *     "node",
 *     "user",
 *   },
 * )
 */
final class HookEntityPresaveExample extends HookEntityPresavePlugin {

  /**
   * {@inheritdoc}
   *
   * Do your hook stuff here!
   *
   * The example below has been adapted from the official documentation (see
   * link below). In this case, $entity has been retrieved from the
   * $this->event->getEntity() event method.
   *
   * @link https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21entity.api.php/function/hook_entity_presave/8.9.x
   */
  public function hook() : void {
    $entity = $this->event->getEntity();
    if ($entity instanceof ContentEntityInterface && $entity->isTranslatable()) {
      $route_match = \Drupal::routeMatch();
      \Drupal::service('content_translation.synchronizer')->synchronizeFields(
        $entity,
        $entity->language()->getId(),
        $route_match->getParameter('source_langcode')
      );
    }
  }

}

This piece of code does four things:

  • Registers a new class HookEntityPresaveExample
  • Uses the @HookEntityPresave annotation statement, and assign values to its public properties
    • The id and title properties are common to all Hook Annotations
    • The entityTypes property is used to restrict the affected objects, and these properties will vary per Hook Annotation
  • Extends the HookEntityPresavePlugin class, transforming the class into a Plugin
  • Implements the hook() method, where all its hook manipulations reside

Some things to consider:

  • Annotation properties are declared in the corresponding Annotation class, e.g. \Drupal\crocheteer\Annotation\HookEntityPresave
  • Plugin methods and utility properties are declared in the extended Plugin class, e.g. \Drupal\crocheteer\Plugin\Hook\Entity\HookEntityPresavePlugin
  • Try to split your hook() method logic into private methods as much as possible, for better code readability and quality
  • Hook-related properties and methods can be retrieved from the referenced $this->event Event object (which belongs to the Hook Event Dispatcher module)
  • If you need dependency injection mechanisms in your Hook Plugin, you must implement the ContainerFactoryPluginInterface interface, which requires the following adjustments to your class:
    • Implement the public static function create() method while injecting your new dependencies
    • Override the public function __construct() method to add new dependencies (parameters)

Examples

See the example submodule Crocheteer - Example for concrete examples.

Requirements

PHP minimal version required: 7.4

Image credit

Photo by Olliss on Unsplash

Breaking Changes

Along with the 8.x-1.0 release comes a lot of breaking changes, since the module had to be revised and modified multiple times.

If you need to do update the module from the previous 8.x-1.0-beta5 version, it is strongly recommended that you first uninstall the module and then install the 8.x-1.0 release.

For more information on said changes, check out the 8.x-1.0 release notes.

Project information

Releases