Drupal 11.1 Adds Hooks as Classes: A History, How-To, and Tutorials We’ve Updated

With the release of Drupal 11.1, there’s a cool new feature for developers: Hooks can now be implemented as class methods using PHP attributes instead of functions. This change is a major step forward in modernizing Drupal’s codebase. While procedural function-based hooks are still supported (and will be for some time), developers writing new code should strongly consider using the object-oriented (OOP) approach introduced in Drupal 11.1.

A brief history of hooks in Drupal

The concept of hooks — functions that follow a defined naming convention and are invoked at specific points during Drupal’s runtime — has been a part of Drupal for a very, very, long time. I think it’s safe to say that they are one of Drupal’s most defining features. And virtually un-changed for 24 years. Until now.

The idea of PHP functions that follow a defined naming convention as a way to allow modular code was first added to Drupal this commit [#8d5b4e7b] on Dec. 23rd, 2000.

[#be8e898d]. This updates includes the addition of the module_invoke function below. And the introduction of this line of code $function = $name ."_". $hook; which has basically been with us ever since.

[#456fd7cc] to use call_user_func_array() which amongst other things allowed hooks at have any number of arguments. Note the use of $a1 ... $4 which prior to this limited the number of arguments a hook could receive.

[#c80c3e18]), this code was moved into the ModuleHandler::invoke() method, and updated to make use of some newer PHP language features, but the logic remained the same. And the code stayed this way until Drupal 11.1:

public function invoke($module, $hook, array $args = []) {
    if (!$this->hasImplementations($hook, $module)) {
        return;
    }
    $hookInvoker = Closure::fromCallable($module . '_' . $hook);
    return call_user_func_array($hookInvoker, $args);
}

In Drupal 11.1, the logic for calling a function based hook is now in ModuleHandler::legacyInvoke(), but is still basically the same:

protected function legacyInvoke($module, $hook, array $args = []) {
    $this->load($module);
    $function = $module . '_' . $hook;
    if (function_exists($function) && !(new ReflectionFunction($function))->getAttributes(LegacyHook::class)) {
        return $function(...$args);
    }
    return NULL;
}

Kind of amazing how well this system has served Drupal for 24 years. Drupal 8 introduced new patterns for altering, extending, and enhancing Drupal core like plugins, services, and events. But hooks are still an important part of how the system works.

Object-oriented hook implementations

The idea of switching to object-oriented hook implementations has been around for a long time. And numerous attempts have been made since the introduction of Drupal 8. But for various reasons, some technical and some DX related, the solution was elusive until recently.

Before Drupal 11.1, implementing a hook meant writing a procedural function following a specific naming convention in your .module file, like this:

/**
 * Implements hook_form_alter().
 */
function mymodule_form_alter(&$form, DrupalCoreFormFormStateInterface $form_state, $form_id) {
  // Custom code and comments go here ...
}

Now, with Drupal 11.1+, we can implement hooks inside classes using the #[Hook] attribute. Example code lives in src/Hook/MyModuleFormHooks.php:

<?php
declare(strict_types=1);

namespace DrupalmymoduleHook;

use DrupalCoreFormFormStateInterface;
use DrupalCoreHookAttributeHook;

class MyModuleFormHooks {

  /**
   * Implements hook_form_alter().
   */
  #[Hook('form_alter')]
  public function formAlter(&$form, FormStateInterface $form_state, $form_id): void {
    // Custom code and comments go here ...
  }

}

Why use OOP hooks instead of the traditional procedural function-based hooks?

Improved code organization and readability

  • Hooks are now encapsulated within classes, reducing clutter in .module files.
  • Instead of having multiple hook functions scattered across a .module file, related hooks can be grouped logically inside a single class.

Enables autowiring and dependency injection

Class-based hooks support dependency injection, allowing services to be injected directly into the class. This eliminates the need for Drupal::service() calls, making code more testable and modular.

Example:

namespace DrupalmymoduleHook;

use DrupalCoreLoggerLoggerChannelFactoryInterface;
use DrupalCoreHookAttributeHook;

class MyModuleHooks {
  protected LoggerChannelFactoryInterface $loggerFactory;

  public function __construct(LoggerChannelFactoryInterface $loggerFactory) {
    $this->loggerFactory = $loggerFactory;
  }

  #[Hook('cron')]
  public function cron() {
    $this->loggerFactory->get('mymodule')->info('Cron job executed.');
  }
}

Better testability

Unit tests become easier since hooks are now inside classes that can be instantiated with mocked dependencies. The procedural approach required global function mocking, which was cumbersome.

PSR-4 Autoloading and performance

Since class-based hooks follow PSR-4 autoloading, they aren’t loaded unless needed. This contrasts with .module files, which are always loaded, even if they contain only hook functions that might not be used. Reducing unnecessary file loading can lead to minor performance improvements.

TL;DR:

✅ Better code organization

✅ Supports dependency injection (no more Drupal::service())

✅ Easier to test

✅ Performance benefits

✅ Future-proof (aligns with modern PHP practices)

If you’re writing new Drupal 11.1+ code, the OOP approach is the way to go! 🚀

Updating our tutorials with the new approach

One of our core commitments at Drupalize.Me is ensuring that our tutorials remain accurate and relevant as Drupal evolves. So, we’re working on updating all of our tutorials to take into account the new OOP approach to adding hooks in a module. Procedural hooks have been around for 24 years. We know they aren’t going to disappear overnight. You’ll see them in example code and existing documentation for a long time to come. For now, we’ll be including both approaches in our content whenever doing so makes sense.

You should plan on learning both approaches, and then using the one that makes the most sense given your specific case.

Notable tutorial updates

  • What Are Hooks?: This tutorial now covers the concept of hooks, their purpose, and the new OOP implementation method introduced in Drupal 11.1.
  • Implement Any Hook: Updated to guide developers through the process of implementing hooks using both traditional procedural functions and the new class-based approach with attributes.

The Drupal Module Developer Guide has been thoroughly updated to ensure compatibility with Drupal 11.1, incorporating the new OOP methodologies for hook implementations.

Similar Posts

  • 2 New Tutorials Added to Single Directory Components Course

    As promised, we’ve added 2 new tutorials to our course, Single Directory Components in Drupal: Props and slots are both mechanisms for passing data and content to UI components. In Understanding Props and Slots in Drupal Single Directory Components, you’ll learn the difference between props and slots in Drupal SDCs, and how to choose the…

  • How to Guide Your Clients to the Best WordPress Solutions

    There’s more than one way to accomplish your design and functionality goals with WordPress. You’ll find multiple themes and plugins to choose from. And writing custom code is always an option. It’s wonderful to have choices. However, it can also be overwhelming – especially when working with clients. You want to find the best solutions…

  • How to Implement a Shipping Strategy

    If you sell physical goods, your shipping strategy is one of the most important aspects of your success. Customers shopping online expect to pay as little as possible, but receive products quickly and in perfect condition.  How can you meet these expectations without losing money and set your business up for growth?  Start by establishing…

  • Accept Bitcoin with Square: A new way to take payments at checkout

    Square just opened the Bitcoin floodgates. Here’s why it matters for your store. In November 2025, something quietly remarkable happened: Square made Bitcoin payments available to eligible US merchants. Your neighborhood coffee shop can now accept Bitcoin as easily as they swipe your credit card. Payments are confirmed in seconds via the Lightning Network. There…

  • Who Pays for the Documentation?

    Drupal documentation has a funding problem. I have some thoughts I want to share after nearly two decades in the thick of it. I’ve spent much of my career producing developer education for Drupal. Video tutorials, written guides, multi-day workshops, demo codebases, conference talks, and the Drupal User Guide. Over 200 video lessons and countless…