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

If you have an existing testing Drupal project around, you can install the module via composer with the following command:

  • composer require 'drupal/crocheteer:^1.0'

Project link

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

Git instructions

git clone https://git.drupalcode.org/project/crocheteer.git

Comments

nbeaucage created an issue. See original summary.

nbeaucage’s picture

Priority: Minor » Normal
avpaderno’s picture

Title: [D8, D9] Crocheteer, a plugin-based approach to Drupal hooks. » [D8] Crocheteer
Issue summary: View changes
Issue tags: -hooks, -plugins, -plugin managers, -annotations, -event subscribers
avpaderno’s picture

Priority: Normal » Major

I am changing priority as per https://www.drupal.org/node/539608.

avpaderno’s picture

Priority: Major » Critical
avpaderno’s picture

Assigned: Unassigned » avpaderno
Priority: Critical » Normal
Status: Needs review » Fixed

Thank you for your contribution! I am going to update your account.

These are some recommended readings to help with excellent maintainership:

You can find more contributors chatting on the IRC #drupal-contribute channel. So, come hang out and stay involved.
Thank you, also, for your patience with the review process.
Anyone is welcome to participate in the review process. Please consider reviewing other projects that are pending review. I encourage you to learn more about that process and join the group of reviewers.

I thank all the dedicated reviewers as well.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.