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:
- Annotation-based Plugins (5 min read), covered by the Core's Plugin API (20 min read)
- Event Dispatchers and Subscribers (15 min read), covered by the Hook Event Dispatcher contrib module (1 min read)
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/Hookdirectory (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,titleandentityTypes - 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
@HookEntityPresaveannotation statement, and assign values to its public properties- The
idandtitleproperties are common to all Hook Annotations - The
entityTypesproperty is used to restrict the affected objects, and these properties will vary per Hook Annotation
- The
- Extends the
HookEntityPresavePluginclass, 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->eventEvent object (which belongs to the Hook Event Dispatcher module) -
If you need dependency injection mechanisms in your Hook Plugin, you must implement the
ContainerFactoryPluginInterfaceinterface, 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)
- Implement the
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
Comment #2
nbeaucage commentedComment #3
avpadernoComment #4
avpadernoI am changing priority as per https://www.drupal.org/node/539608.
Comment #5
avpadernoComment #6
avpadernoThank 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.