mcpField
D
drupal-hooks

drupal-hooks

github.com/edutrulDevelopment7351 installsUpdated 21w ago
Repository

About drupal-hooks

A Drupal 11 hook development reference for module developers. It favors testable object-oriented hooks in `src/Hook/`, while documenting procedural hooks and when to use event subscribers.

Key features

  1. 01Define object-oriented hooks with attributes
  2. 02Use constructor dependency injection
  3. 03Rely on automatic registration in Drupal 11.1+
  4. 04Register hooks outside `src/Hook/` manually
  5. 05Compare hooks with event subscribers

Use cases

  1. 01Implement a new Drupal module hook
  2. 02Refactor procedural hooks into classes
  3. 03Choose between hooks and event subscribers

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/edutrul/drupal-ai --skill drupal-hooks

After installing, type/drupal-hooksin Claude Code to confirm it loaded.

Bundled filesREADME.md

Details

From the project README

Drupal Hooks (Drupal 11)

OOP Hooks (Preferred)

Locationsrc/Hook/MyModuleHooks.php
NamespaceDrupal\my_module\Hook
Auto-registeredDrupal 11.1+ (no services.yml needed)
DI supportConstructor injection
TestableYes — instantiate directly, inject mocks

Example (Best Practice)

<?php

namespace Drupal\my_module\Hook;

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\node\NodeInterface;

final class MyModuleHooks {

  use StringTranslationTrait; // Provides $this->t().

  public function __construct(
    private readonly AccountProxyInterface $currentUser,
  ) {}

  /**
   * Implements hook_form_node_article_form_alter().
   *
   * Prefer targeted form hooks over generic form_alter — they only fire for
   * the specific form ID and avoid unnecessary processing.
   */
  #[Hook('form_node_article_form_alter')]
  public function formNodeArticleFormAlter(array &$form, FormStateInterface $form_state): void {
    $form['title']['#title'] = $this->t('Article title');
  }

  /**
   * Implements hook_form_alter().
   *
   * Use only when acting on multiple forms or the form ID is unknown at
   * development time. Check $form_id explicitly.
   */
  #[Hook('form_alter')]
  public function formAlter(array &$form, FormStateInterface $form_state, string $form_id): void {
    if ($form_id === 'node_page_form') {
      $form['title']['#description'] = $this->t('Enter a descriptive page title.');
    }
  }

  #[Hook('node_presave')]
  public function nodePresave(NodeInterface $node): void {
    if ($node->getType() === 'article') {
      // Example: stamp the current user's ID on save.
      $node->set('uid', $this->currentUser->id());
    }
  }

  #[Hook('theme')]
  public function theme(): array {
    return [
      'my_template' => [
        'variables' => ['content' => NULL],
      ],
    ];
  }

}

Note: If a hook is not executed, verify namespace and location. OOP hooks outside src/Hook/ require manual service registration.

services.yml

ScenarioRequired?
Class in src/Hook/, namespace Drupal\my_module\HookNo — auto-registered (Drupal 11.1+)
Class outside src/Hook/ (e.g. a custom service)Yes — register manually

When registration is needed — autowire resolves constructor dependencies by type hint:

namespace Drupal\my_module\Service;

use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Session\AccountProxyInterface;

final class MyCustomService {

  public function __construct(
    private readonly AccountProxyInterface $currentUser,
  ) {}

  #[Hook('form_alter')]
  public function formAlter(...): void {
    // ...
  }

}
services:
  Drupal\my_module\Service\MyCustomService:
    autowire: true

Procedural Hooks (.module)

Auto-discovered via my_module_hook_name() naming. Still valid; prefer OOP hooks for new code.

Hooks vs Event Subscribers

Use Hooks WhenUse Event Subscribers When
form_alter, entity hooks, theme hooksreacting to dispatched Symfony events
extending Drupal core/contrib behaviorPSR-14 / decoupled systems
working with existing Drupal APIsbuilding loosely coupled logic

OOP hooks in src/Hook/ are fully testable — instantiate the class directly and inject mocked dependencies.

RULES (IMPORTANT)

  • ALWAYS prefer OOP hooks in src/Hook/ for new implementations
  • NEVER add new hooks in .module unless explicitly requested
  • If procedural hook exists, suggest refactoring to OOP

Comments

Markdown supported