Follow-up to: #1331486: Move module_invoke_*() and friends to an Extensions class
Problem
- "module_handler" as a service name is a bit off, since most services are "handlers".
Details
- Just "module" would be too unspecific/generic/meaningless.
- So what does ModuleHandler really do?
- It determines which module implements which hook.
- It invokes hooks in modules that implement them.
- It maintains a list of active modules that should be invoked for hooks.
To do so, it gets the list of enabled modules injected from DrupalKernel (which reads the list from the
system.module:enabled
configuration). The list of modules can be overridden/mocked for special use-cases though; e.g., install.php, update.php, and tests are operating with a fixed/locked module list, which is not the configured list of enabled modules.There's an additional set of functionality tacked onto it:
- It loads .module files, in order to determine existence of hook implementations and to invoke hooks.
- It tracks whether all modules in the currently active module list have been loaded, so certain code that is invoking hooks are able to verify that the returned results are complete (e.g., the theme registry needs to be sure that all
hook_theme()
implementations are loaded in order to build the complete theme registry). - It answers whether a given module "exists" — based on the currently active module list.
Proposed solution A
- Rename "module_handler" to "hook", and rename ModuleHandler to HookManager.
- Clarify or move the straw ::moduleExists() method somewhere else, and decide whether its result shouldn't be based on the configured list of enabled modules, instead of the currently active module list being used for hooks.
Comments
Comment #1
Crell CreditAttribution: Crell commentedThis is not linear with but circular with #1893690: Find a new home and service for module_enable(), module_disable(), module_set_weight(), etc. If ModuleHandler turns into a hook-based event dispatcher (which is essentially what Proposed Solution A is suggesting), then we will need to feed it with a dependent service that manages "modules", absent hooks, and then this service provides hook-event functionality based on the module service.
I think that actually makes quite a bit of sense to do that way.
Comment #2
sunAight. ;) That "circular" is what actually caused me the most headaches when working on the module_handler patch. :-D
Add to that, DrupalKernel additionally manages a list of active modules in %container.modules%. That caused me even more headaches. ;)
So if we'd map the current conclusions, then we'd end up here:
Note the difference on the currently active module list (provided by the kernel) and the configured list of enabled modules (in config). That's highly important:
A module manager service would mainly/only operate on the configuration, and its main purpose is to change the actively participating extensions in the application — in essence, changing the kernel's build parameters. (If we wanted to do Symfony [CMF] a favor, then we'd architecturally design that service in a way to be able to cope with arbitrary PSR-0 components, instead/in-addition of just Drupal modules.)
That said, what's still missing in this puzzle is the
moduleExists()
method. Right now + in the past,module_exists()
is/was based on the currently active module list only. As such, it would actually map closest to %container.modules%, because we're not talking about hooks, and we're also not really talking about the configured list of enabled modules.module_exists()
(outside of the hook system) actually asks whether a module is currently active (in a rather undefined way). So with a fixed module list (as in install.php or update.php), it actually returned TRUE when asking for e.g. system.module ('cos it's in the currently active module list).However, if we decide that
moduleExists()
should be bound to the hook system, then I think I'd be down with that. From all possible (but unknown) meanings, that probably comes closest. :)Comment #3
Crell CreditAttribution: Crell commentedThat largely makes sense, I think. 2 caveats, though:
1) While I am all for supporting arbitrary components in Drupal, I don't think the module system is per-se the place for them. For a fully stand-alone component/library, the answer is composer and the vendor directory. For a Drupal bridge module for same (eg, to register services), that's a Bundle class and we already have that. We just need #1398772: Replace .info.yml with composer.json for extensions and then that's quite simple. For supporting actual Symfony [CMF] Bundles, those almost always depend on the Symfony Config component and FrameworkBundle, neither of which we have, so I don't think that's really feasible or on topic here.
2) I would actually argue that moduleExists() belongs on the module manager object, not the hook object, because it is related to "is this module here or not?" It should not have anything to do with hooks, which are an event system tightly coupled to the module namespace. Let's not confuse it with moduleImplements(), which is a subtly different thing.
Comment #4
jibran#1331486-239: Move module_invoke_*() and friends to an Extensions class is committed.
Comment #5
sunYeah, I agree with #3.1, and I was probably shooting for too much.
re: #3.2, since the current meaning is undefined, we can go either way. If we detach it from the hook system, then I wonder whether it wouldn't actually fit best on DrupalKernel?
That is, because I think that the new service for #1893690: Find a new home and service for module_enable(), module_disable(), module_set_weight(), etc should *only* deal with operations that change the configuration of the application; i.e., module_enable(), module_disable(), module_uninstall(), module_set_weight(), etc. — In other words, you only use that service if you want to change the configuration of the system.
Compared to that,
module_exists()
just asks whether a given extension is currently active. "Active" would have to be defined:1) In the active list of modules whose services are available and whose code is loaded and executed.
2) In the list of configured, enabled modules. (But not necessarily active/loaded right now.)
Perhaps we can just do both, in which case
1) would be
DrupalKernel::moduleExists()
2) would be
ModuleConfigurator::isEnabled()
ModuleHandler::moduleExists()
would cease to exist, since the question is not related to hooks.Would that make sense?
Also, work on the follow-up patch #1894910: Move module_invoke() into ModuleHandler revealed:
There's a stale
@defgroup
for the module/hook system in module.inc, which explains its architecture and usage. So if we rename module_handler to be the hook system, then we should probably move those docs entirely into the class phpDoc of ModuleHandler[Interface].The more I think about this, the more it makes sense to me. Apparently, the Hook system isn't really limited to modules; e.g.,
[drupal_]alter()
checks for hook implementations in themes, too. I actually wanted to remove that separation/limitation entirely for quite some time already, and simply invoke hooks in all extensions. That is, because the artificial limitation to "only modules and a tiny bit of themes" requires tons of ugly, custom code in single spots, and isn't consistent at all (you know...hook_theme()
is not an alter hook, but yet, invoked in themes...). So, alas, I expect the overall code to become much more simple if that special-casing was removed entirely. I'm working on converting theme registry into service already, and I might get to that afterwards.Comment #6
Crell CreditAttribution: Crell commentedI haven't kept up on the gory details of the deep kernel refactor that Kat's been involved with, but I don't understand the distinction between "active module" and "enabled module". How would a module be "enabled" but not "active"?
Moving the docblock makes sense to me.
Themes being able to invoke all hooks scares the dickens out of me, and with the move to Twig and SCOTCH I think that entire question changes. But that's not a topic for this thread either way.
Comment #7
Crell CreditAttribution: Crell as a volunteer commentedComment #8
dawehnerJust to give a quick overview of the status of this class: We now have a ModuleInstaller and a ModuleHandler and the ModuleHandler is everything you need on runtime, like invoking hooks and getting the list of active modules, on the other hand ModuleInstaller is about install and uninstall and this is all.
Comment #9
catchIf we want to do this, it could be done in a minor release with bc. However it should also stay postponed on #2208429: Extension System, Part III: ExtensionList, ModuleExtensionList and ProfileExtensionList.