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?
    1. It determines which module implements which hook.
    2. It invokes hooks in modules that implement them.
    3. 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:

    1. It loads .module files, in order to determine existence of hook implementations and to invoke hooks.
    2. 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).
    3. It answers whether a given module "exists" — based on the currently active module list.

Proposed solution A

  1. Rename "module_handler" to "hook", and rename ModuleHandler to HookManager.
  2. 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

Crell’s picture

This 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.

sun’s picture

Aight. ;) 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:

  1. A kernel builds a container, based on a list of modules. (DrupalKernel)
  2. A hook-based event dispatcher gets the list of modules injected by the kernel. (ex. module_handler)
  3. A module manager gets the config service injected by the kernel to install/uninstall/enable/disable/persistently-sort modules. [new]

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. :)

Crell’s picture

That 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.

jibran’s picture

sun’s picture

Yeah, 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.

Crell’s picture

I 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.

Crell’s picture

Version: 8.0.x-dev » 9.x-dev
Status: Active » Postponed
dawehner’s picture

Just 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.

catch’s picture

Version: 9.x-dev » 8.2.x-dev

If 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.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.