Refactor away ModuleInstaller::invokeAll()

Created on 6 December 2024, 20 days ago

Problem/Motivation

Following the OOP hook conversion we have ModuleInstaller::invokeAll() but in different places in ModuleInstaller we might call

$this->invokeAll();

or

$this->moduleHandler->invokeAll();

The difference is that the internal helper calls loadAll() first, but it is not clear which one to use in which case and why.

Steps to reproduce

Proposed resolution

Refactor away invokeAll() and call loadAll() directly in places where we need it, preferably with a comment explaining why we need to do it.

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

πŸ“Œ Task
Status

Active

Version

11.0 πŸ”₯

Component

extension system

Created by

πŸ‡¬πŸ‡§United Kingdom longwave UK

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

Merge Requests

Comments & Activities

  • Issue created by @longwave
  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    $this->invokeAll

    Is called explicitly to call loadAll before calling ModuleInstaller

    protected function invokeAll($hook, $args = []): void {
        $this->moduleHandler->loadAll();
        $this->moduleHandler->invokeAll($hook, $args);
      }
    

    We would have to call loadAll before each instance of invokeAll in ModuleInstaller

    There is no single call to $this->moduleHandler->invokeAll($hook, $args); outside of ModuleInstaller::invokeAll

    $this->invokeAll('module_preinstall', [$module, $sync_status]);
    $this->invokeAll('modules_installed', [$modules_installed, $sync_status]);
    $this->invokeAll('module_preuninstall', [$module, $sync_status]);
    $this->invokeAll('modules_uninstalled', [$module_list, $sync_status]);
    $this->invokeAll('cache_flush');
    

    Those 5 hooks are all invoked in other modules than the one being installed and this allows them to be OOP.

    The thing that might be confusing is there is also:

    ModuleInstaller::invoke()
    This is called on the modules being installed and cannot be oop cause they are not loaded yet.

    $this->invoke($module, 'update_last_removed')
    $this->invoke($module, 'install', [$sync_status])
    $this->invoke($module, 'uninstall', [$sync_status])
    $this->invoke($module, 'schema')
    

    This uses function_exists

    protected function invoke(string $module, string $hook, array $args = []): mixed {
        $function = $module . '_' . $hook;
        return function_exists($function) ? $function(... $args) : NULL;
      }
    

    I'd love to factor that away, but I think that should be handled elsewhere.

  • πŸ‡¬πŸ‡§United Kingdom longwave UK
        // Let other modules react.
        $this->invokeAll('modules_uninstalled', [$module_list, $sync_status]);
    
        // Flush all persistent caches.
        // Any cache entry might implicitly depend on the uninstalled modules,
        // so clear all of them explicitly.
        $this->invokeAll('cache_flush');
    

    would be much clearer to me as

        // Load all modules because ...
        $this->moduleHandler->loadAll();
    
        // Let other modules react.
        $this->moduleHandler->invokeAll('modules_uninstalled', [$module_list, $sync_status]);
    
        // Flush all persistent caches.
        // Any cache entry might implicitly depend on the uninstalled modules,
        // so clear all of them explicitly.
        $this->moduleHandler->invokeAll('cache_flush');
    

    Similarly

        foreach ($module_list as $module) {
    ...
          // Allow modules to react prior to the uninstallation of a module.
          $this->invokeAll('module_preuninstall', [$module, $sync_status]);
    

    Why do we need to call loadAll() every time we go around the loop? Clearer to call it once at the start, with a reason.

  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    The only concern I have for the former is if they get split up, but that would be in another issue, this way it's all unified.

    Why do we need to call loadAll() every time we go around the loop? Clearer to call it once at the start, with a reason.

    That's actually probably worth considering.
    In fact I wonder if the loadAll is needed for the uninstall ones at all.
    Install maybe especially now that we are considering doing them in batches.

  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    So the loadAll was added in πŸ“Œ OOP hooks using event dispatcher Needs review

    module_preinstall
    modules_installed
    module_preuninstall
    modules_uninstalled
    cache_flush
    

    Previously called: $this->moduleHandler->invokeAll

    They were changed to:
    protected function invokeAll($hook, $args = []): void {
    $this->moduleHandler->loadAll();
    foreach ($this->moduleHandler->getModuleList() as $module => $extension) {
    $this->invoke($module, $hook, $args);
    }
    }

    and invoke does the function exists.

    This was then consolidated to what it is today here: πŸ“Œ Fix invocation of hook_module(s)_(pre(un))install(ed) so they support OOP hooks Active and here πŸ“Œ Clean up how ModuleInstaller invokes hooks around installing other modules. Active

    I bet we can go full circle as you pointed out, and remove:
    ModuleInstaller::invokeAll() and just call $this->moduleHandler->invokeAll(
    Directly, that should just work.

  • Merge request !10478Switch back to modulehandler β†’ (Closed) created by nicxvan
  • Pipeline finished with Failed
    20 days ago
    Total: 648s
    #361459
  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    Oh that triggers Exception: The theme implementations may not be rendered until all modules are loaded. maybe we can use this to find out how to handle the hook_theme issues too.

  • πŸ‡¬πŸ‡§United Kingdom catch

    That exception is removed in https://git.drupalcode.org/project/drupal/-/merge_requests/6290/diffs?fi...

    I think it's going to be OK to remove it, because:

    1. It doesn't stop other early hook invocation like the cases mentioned in πŸ› ModuleHandler skips all hook implementations when invoked before the module files have been loaded Needs review .

    2. As we convert hooks to OOP, the concept of loading modules is going to eventually disappear.

    3. Drupal\Core\Utility\ThemeRegistry and \Drupal\Core\Theme\Registry both check if all modules are loaded before writing to the persistent theme registry cache, so that cache can't be polluted anyway. The worst that can happen is the early theme call fails, but it would already fail now with the exception.

    4. It was added in #2346937: Implement a Renderer service; reduces drupal_render / _theme service container calls β†’ which predates the code in #3.

  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    The only related issue is this: πŸ› Theme hooks not found warning on Umami install Active

  • πŸ‡ΊπŸ‡ΈUnited States nicxvan
  • Merge request !10534Convert remaining ModuleInstaller InvokeAll β†’ (Open) created by nicxvan
  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    nicxvan β†’ changed the visibility of the branch 3492282-refactor-away-moduleinstallerinvokeall to hidden.

  • Pipeline finished with Success
    15 days ago
    Total: 944s
    #366157
  • πŸ‡ΊπŸ‡ΈUnited States nicxvan
  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    Seems pretty straight forward. Matches what the issue summary is proposing and think we would know pretty quickly if it broke anything :)

Production build 0.71.5 2024