Problem/Motivation
The hook system has some awkward details that make refactoring and improving it harder.
Especially, these force some complex tricks in #3366083: [META] Hooks via attributes on service methods (hux style)#3368812: Hux-style hooks, proof of concept
Some of this can be cleaned up without major BC breaks.
Single module ModuleHandler->invoke()
One should think that this is equivalent to just calling the one implementations from ->invokeAll(), that matches the given module.
With the huxified system, it would mean _any_ implementation that is associated with that module.
However, this is not fully the case:
If the function $module . '_' . $hook
exists at the time when ModuleHandler->invoke()
is called, then that function is always called, even if it is not part of the (cached) list for ->invokeAll()
. The latter can be the case if:
- The implementation was removed through hook_module_implements_alter(), OR
- The file that contains the function (e.g. *.install) was not included at the time of discovery, OR
- The module that owns this implementation is not currently enabled - this is the case for
hook_requirements()
.
The hook_module_implements_alter()
has limited effects on single ->invoke()
:
- Reordering of implementations has no effect, because there is only one function.
- Removal of implementations has no effect, because the function is still called.
- Anything that causes a file with the
$module . '_' . $hook
function to be included, if it would otherwise not be, does have an effect. This generally means that hook_module_implements_alter()
has to add a module with include group, e.g. $implementations['mymodule'] = 'mygroup'
. This could even be another module that implements the hook on behalf of the module being called.
In fact, most of the hooks that are called with ->invoke() are never called with ->invokeAll().
This means we could consider to handle them completely separately.
Removal of missing functions from the cached list
If a list of implementations is loaded from cache, a function_exists() check is performed on every cached implementation.
If the function does not exist, it is removed from the list, and the updated list is later written to the cache.
The only use case I can think of is during development, if the developer temporarily checks out another git branch, or temporarily removes a function.
The practice of writing the updated list to the cache is questionable.
If you switch back to the main git branch, the function that was accidentally added is now gone from the cache.
Removing this mechanism would make things much easier in #3366083: [META] Hooks via attributes on service methods (hux style).
Removal or adding of modules
Currently it is possible to add or remove modules with ModuleHandler->addModule()
, ->addProfile()
and ->setModuleList()
.
This is a bit problematic with #3366083: [META] Hooks via attributes on service methods (hux style), because the services in the container where the ModuleHandler instance comes from is still based on the old module list. So we can add hook implementations for the newly added module, but the respective services won't be available at that time.
Note that this problem is only about adding ore removing modules.
Reordering modules via module_set_weight() is actually fine, because the order of hook implementations _can_ be dynamically changed.
Proposed resolution
Consider to simplify or remove some of these fragile mechanical details.
Possible changes:
- For
->invoke()
, we could completely disregard hook_module_implements_alter()
.
- No longer remove missing functions from the cached list, unless a cache rebuild was explicitly requested.
Instead, remove these functions in every request/process when the list is verified. This is not any better or worse for behavior, but it is easier to refactor.
- Don't allow removing or adding modules in ModuleHandler. Allow reordering for
module_set_weight()
, but otherwise, used the fixed list from the container parameter.
This sounds like a BC break, although I don't know of any known contrib modules that need this outside of tests.
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet