Allow to skip OOP hooks and services for modules that are not installed

Created on 14 May 2025, 23 days ago

Problem/Motivation

Modules often implement hooks of other modules they don't depend on as optional integrations. That works basically out of the box with legacy hooks and also new OOP hooks. But they are still registered and their class added to the container when we know they will never be called. For example, user module implements views hooks.

This becomes a bit more complicated if your hook also requires an injected dependency of that module. Because the class is added as a service, it will be invoked and you need to make that dependency optional even though that class and hook actually requires it. And we'll run into that more often when improving Hooks to do proper DI.

And, unlike in old version where hooks were only discovered and cached when invoked, now we add them all to the container.

If you provide a plugin for a given plugin type owned by another module that isn't installed this works as it's never discovered.

Steps to reproduce

Proposed resolution

We can't figure this out automatically, we don't have enough knowledge about hooks and who owns them.

My idea was to make it explicit with a new attribute:

#[Hook('config_translation_info_alter')]
#[HookBy(module: 'config_translation')
public function configTranslationInfoAlter(array &$info): void {

We can check for that, and if config_translation isn't in the container, we skip it. And if a service class has no hooks, we also don't add it to the container.

It might also automatically work if the hook class is defined by that module, like this:

#[ConfigTranslationInfoAlter('config_translation_info_alter')]
public function configTranslationInfoAlter(array &$info): void {

If we can't resolve ConfigTranslationInfoAlter as an attribute class. But I'm not sure yet if we're really going to add hook classes for every hook.

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

✨ Feature request
Status

Active

Version

11.2 πŸ”₯

Component

base system

Created by

πŸ‡¨πŸ‡­Switzerland berdir Switzerland

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @berdir
  • πŸ‡§πŸ‡ͺBelgium kristiaanvandeneynde Antwerp, Belgium

    Declaring all your hooks as extensions of Hook would definitely solve this as we can then support a "source" property on the base Hook class that we can scan for as described in the IS. The only drawback I see is we'd be creating tons of one-of attributes. They would be contained to namespaces that have Hook in them, so I suppose that could be fine.

    So maybe add an optional "source" property to Hook that we can specify in the Hook constructor as a named argument and then extensions of Hook can prefill it for you? The default value could be empty or "drupal" to indicate that it's a core hook.

    Anyways, big +1 to the idea. We shouldn't add classes to the container if we know they will never be called.

  • πŸ‡³πŸ‡ΏNew Zealand quietone
  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    There is a related issue I can't find at the moment. I think this needs to be solved with a condition, I'm wondering if it should be a separate attribute or a parameter. Both options have pros and cons.

    But either way, rather than a simple module exists it could be a callback.

    There would be some serious restrictions, but you could conditionally add the hooks based on that result.

    For example define the hook only if another module does not exist.

  • πŸ‡¨πŸ‡­Switzerland berdir Switzerland

    I think modules being installed or not is by far the most common use case for this and I'd rather focus on that. callbacks imply dynamic logic like something based on settings, but then you have to rebuild the container if that changes. Seems more complex to support and define. Could also design the attribute in a way that allows to extend it later?

    #[ConditionalRegister(moduleExists: 'config_translation')]
    

    And then we could add a callback parameter if there is a valid use case for that later.

  • πŸ‡§πŸ‡ͺBelgium kristiaanvandeneynde Antwerp, Belgium

    Adding an attribute to the class that skips it for registration if the dependency is unmet seems like a good solution to the optional constructor argument problem. If we know that the dependency needs to exist for our hooks to be registered as a service, we can safely use the dependency's services with autowiring.

Production build 0.71.5 2024