Direkt zum Inhalt
  • Agentur für web development
  • Content Management mit Primer
  • Open Source Leadership mit Drupal
    Sprache
  • Deutsch
  • English
  • Kontakt
Logo der Website
Agentur für Webentwicklung
  • Referenzen
  • Angebot
  • Aktuelles
  • Über uns
  • Agentur für web development
  • Content Management mit Primer
  • Open Source Leadership mit Drupal
close

Converting legacy hooks to the new OOP system in Drupal 11.1+

19. Oktober 2025
Sascha Grossenbacher Porträt
Sascha Grossenbacher

Starting with Drupal 11.1, hooks can now be implemented as methods on Hook classes. Support for legacy hooks will be removed in Drupal 13 (including support for .module files altogether). This works for almost all hooks, a few special ones are only supported in Drupal 11.2 (hook_module_implements_alter(), requirements hooks). Note that currently, only OOP hooks in modules are supported, support for theme OOP hooks is close and will hopefully land in Drupal 11.3.

Benefits

While legacy hooks will be supported for years still, there are a number of benefits to converting them already now. 

  1. Using rector, it's possible to do an automatic conversion, at least the initial step.
     
  2. Direct support for dependency injection using autowiring results in cleaner, easier to test code. And thanks to loosened coding standards around constructors, and promoted properties, it is now actually easier to do proper DI than calling out to \Drupal::service()
     
  3. Better code organization, related hooks can be put in the same class, either based on what kind of hooks they implement (such as entity load/save) or by what they do.
     
  4. Built-in lazy loading of code. No more hook_hook_info() (hook implementations relying on it are deprecated for removal in Drupal 12). That means hooks in include files such as tokens.inc and views.inc. It is recommend to do a full conversion specifically for these instead of just moving them.
     
  5. Easy backwards-compatibility. For contrib, it's easy to still support Drupal 10 thanks to the well-thought-through BC using the LegacyHook attribute and a semi-manual services.yml definition. On Drupal 11.1+, hook classes are automatically discovered, custom code and modules requiring at least 11.1 do not need those.

One of the few drawbacks is disruption. Any MR that touches code in a hook will need to be manually updated. It needs to be done eventually. I've started converting some major modules like token and pathauto and typically combine it with doing a release with some commits and minor changes and fixes first, and then do the conversion as the first thing afterwards.

Conversion

This is how I approach these conversions:

  1. Start by getting the excellent ddev conversion script by @nicxvan: https://gitlab.com/-/snippets/4769802, set it up as a ddev command as explained there. If you are not using ddev (you should!), you can also run those commands manually or use them as a non-ddev script.
     
  2. Using a new or existing issue, switch to the merge request branch for that issue.
     
  3. Run ddev convertmoduleoop. This will move all identified hook implementions into a single ModuleNameHooks class, only hooks in .inc files such as tokens and views.inc are put in separate Classes. It will convert existing hook functions to legacy hooks and register the services for backward compatibility. Optionally, it will also run automated coding standard formatting, this is recommended but might change unrelated files, see below.
    1. Note: Discovery of hooks relies on the Implements hook_x() docblock. There is currently an issue with hooks using uppercase characters such as Implements hook_form_FORM_ID_alter(). This is being fixed in https://github.com/palantirnet/drupal-rector/pull/320
       
  4. (Optional) Restructure the hooks into multiple hook classes. A general recommendation is ModuleNameGroupHooks, where group is something like Entity, Tokens, Form and so on. A good starting point is the first part of a hook name. But a different approach may also be useful, such as grouping conceptually related hooks together. Yet another consideration is required services. If a certain hook requires many/expensive services to be injected and is rarely called, consider splitting that into a separate group. For example in token module, I split hook_token_info() and hook_tokens into separate classes, because both are very large for that module and hook_tokens() will be called a lot more than hook_token_info. Do not forget to update the legacy hook calls and add extra hook classes to services.yml.
     
  5. (Optional) Add injected services to your hook classes. Thanks to autowire this is mostly straightforward now. Add them as promoted properties to a constructor, no docblock required anymore:

      public function __construct(
       protected PathautoGeneratorInterface $pathautoGenerator,
       protected AliasStorageHelperInterface $aliasStorageHelper,
       protected ConfigFactoryInterface $configFactory,
     ) {
     }
    1. Note 1: Services from contributed modules may not yet register their services with their interface to support Autowire, it is possible to fall back to a service name using the #[Autowire]The same applies to services such as caches and loggers.
    2. Note 2: Optional integrations can be tricky, autowire supports conditional dependencies such as protected ?SomeInterface $someProperty = NULL, and soon, it will be possible to use the HookDependsOn attribute to skip specific hook classes from being registered when the optional integration module is not available.
       
  6. (Optional) Convert template_preprocess functions
     
  7. (Optional) Consider moving non-hook helper functions in .module files if they are only used by hooks, they could be put in hook classes or separate services, with appropriate BC/deprecations. Since support for .module files will eventually be removed, this will need to be done sooner or later.
     
  8. (Optional) The coding standards are not only applied to the new but also all existing code. For contrib, I typically revert changes to other classes to avoid out of scope changes. I also manually review the phpcs/phpstan reports for issues related to the new Hook classes if they are not configured to fail.
     
  9. (Optional) If all hooks are converted, consider adding a parameter to your module.services.yml file for a slight speedup of the hook discovery.

    parameters:
      MODULENAME.skip_procedural_hook_scan: true

    Note: This is not possible if the module implements hook_hook_info(), as there is no OOP counterpart for this hook and setting the flag would skip that and would break other modules relying on it.

All optional steps can be done at the same time or at a later time (or in some cases, skipped altogether, depending on the size of the project, changes and other factors. Do not forget to test the changes on all supported core versions. 

Hol Dir den Newsletter

Jetzt für unseren Newsletter anmelden und monatlich wichtige Insights aus der Branche und MD Systems erhalten. 

Zur Anmeldung

Über MD Systems

MD Systems mit Firmensitz Zürich ist ein einzigartiges Team von internationalen Open Source Initiative Leadern für das Content Management System Drupal.

Wir begleiten Sie von der Idee und Konzeption über die Realisation bis hin zur Einführung, Betrieb und laufenden Optimierung.

MD Systems GmbH

Hermetschloostrasse 77, CH-8048 Zürich

Schweiz

+41 44 500 45 95

[email protected]

  • Kontakt
  • Impressum
  • Datenschutz
To top

© Copyright 2023 - 2024 MD Systems GmbH. Alle Rechte vorbehalten. Erstellt mit PRIMER - powered by Drupal.