Problem/Motivation
In Drupal < 11.3, it was possible to define a theme preprocess function as THEMENAME_preprocess_HOOK() inside a module.
The reason to do that would be to to only run the preprocess if both the module is installed, and the theme is active (as current or base theme).
In Drupal >= 11.3, these are either fully ignored, or added in the wrong order relative to sub-theme preprocess functions.
To be specific:
- If a regular (without "__") THEMENAME_preprocess_HOOK() is defined in a MODULENAME.module file in a module directory, it is not added.
- If a preprocess for a "theme hook suggestion" (containing "__") THEMENAME_preprocess_HOOK__SUFFIX() is defined in a MODULENAME.module file, it is either not added, or added at the end of the list, after implementations by a sub-theme. (depends if that sub-hook is known at the time)
The same problem also occurs for other theme hooks like THEMENAME_something_ALTER() - to be verified in tests.
Steps to reproduce
There will be test cases in the MR.
Proposed resolution
First, we need A LOT more tests to cover this fragile system.
For a solution, we need to extend the function search space in ThemeHookCollectorPass to functions in module files.
To avoid parsing the same file more than once, we can cache plain function lists per file, instead of hook lists per file.
Remaining tasks
User interface changes
Introduced terminology
API changes
Restoring behavior of 11.2 could be seen as regression relative to 11.3.
Comments
Comment #2
nicxvan commentedI would welcome more tests, but this particular issue should be closed works as designed.
It was an explicit bc break decision when adding oop support: https://www.drupal.org/node/3551652
Comment #3
nicxvan commentedTHEMENAME_preprocess_HOOK__SUFFIX getting added like that will go away once .theme files are removed and we can refactor https://git.drupalcode.org/project/drupal/-/blob/main/core/lib/Drupal/Co...
That also explains why they are at the end of the list now too and only if the suggestion is known.
Comment #4
donquixote commentedOk, I should have checked that change record.
But...
Why do we break stuff in a minor? It has become a bad habit in core.
We have used this pattern for a long time, for a specific reason:
To only run that preprocess if both the module is installed and the theme is active.
Now this is failing in non-obvious ways, because in some cases the function is removed, in other cases the order changes.
https://www.drupal.org/project/drupal/issues/3544715#comment-16307074 (@nicxvan)
How did we determine that it is "unintentional"?
It is not.
It would have just been more work to continue supporting.
https://www.drupal.org/project/drupal/issues/3544715#comment-16307591 (@berdir)
Not really unpredictable.
It has worked for us reliably for a long time.
I don't get this part from berdir :)
(continuation next comment)
Comment #5
donquixote commentedThis is hopefully only in Drupal 12. So not relevant here.
Well maybe it was..
Maybe that "only if the suggestion is known" was already an issue in the past.
I would have to test that.
Comment #6
donquixote commentedThis is also on me for not doing more reviewing.
It was a reason why long ago I wanted to add more tests before doing any of the work on hooks.
But my dedication of time to core dev is far less than others, and not consistent.
Comment #7
donquixote commentedWorkaround
A workaround we found was:
- Move these functions to the theme
- Add a custom attribute #[RequiredModule('MODULENAME')] on each function.
- Use hook_theme_registry_alter() to remove those preprocess if the module is not installed.
This only works if you control both the module and the theme.
The other option would be:
- Rename the functions to MODULENAME_preprocess_*
- Add custom attribute #[RequiredTheme('THEMENAME')]
- Use hook_theme_registry_alter() to remove those preprocess if the theme is not active as a base theme or current theme.
In both cases this has to be a custom attribute, we cannot add it in core, because it has to work for older core versions.
For the second option, removal is not trivial because hook_theme_registry_alter() does not know what is the theme name being processed.
Comment #8
nicxvan commentedRather than using custom attributes make them module hooks and use the theme manager to check the active theme.
And use hook theme to ensure they are picked up.