I have xautoload as a dependency for my install profile. It gets enabled before my profile module does, the profile implements hook_xautoload() and then I want to use one of the registered namespaces in the install before the site is 'done' and ready. There are two problems here at work:

  1. Stop trying to be clever and put your hook implementation inside include files and use hook_module_implements_alter() to include them. FULL STOP. This causes way too much pain.
  2. xautoload_modules_enabled() doesn't re-run the hook_xautoload implementations for discovery
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Dave Reid’s picture

Status: Active » Needs review
FileSize
7.75 KB
donquixote’s picture

Could you share (upload) the install profile, or maybe a stripped-down version of it?

Dave Reid’s picture

Basics of what I can share:

If I run the install profile via Drush or the install.php UI, I encounter the error and the class is not available, even though when the last few tasks are run, it means the install profile module has fully been installed and enabled (these tasks in hook_install_tasks() run after the 'Configure your new Drupal site' form).

donquixote’s picture

Ok, thanks for sharing!
I will test it and review in 9h or sth ..

Stop trying to be clever and put your hook implementation inside include files and use hook_module_implements_alter() to include them. FULL STOP. This causes way too much pain.

I need to think about that. I hate long files. I am doing this in a number of modules, and it generally works ok.
Caveats:
1. Drupal will include the file automatically on hook invocation, *except* for the invocation where the cached hook implementations are rebuilt. This is why require_once needs to be called from within hook_module_implements_alter().
2. Hook invocations that don't use module_implements(), e.g. some module's custom hook invocation, or I think maybe boot and install stuff, may miss out on the include.

(saying this from the top of my head, I will look into it later)

Dave Reid’s picture

Just don't. It's hacky and unless that hook is supported automatically in an include file via hook_hook_info(), then it just makes your code harder to read and follow. I don't expect those functions to live in a modulename.system.inc or modulename.ui.inc.

donquixote’s picture

Some quick intermediate feedback.

I was first not able to reproduce on the web install.php, but then managed to do so with drush site-install.
The proposed patch did fix it.
But, the following reduced fix works equally fine for me:
https://github.com/donquixote/drupal-xautoload/compare/7.x-4.x-install-p...
Looks more transparent to me.

I am not sure yet if this is the ideal way to fix this. More on this later.

But if I do, and if I also decide to move the functions back into the *.module file, I am going to do this in two commits, with the latter being postponed for now. I used to move functions and classes around in the past already. I may do so again, but I don't want to rush it.

donquixote’s picture

Do you think we should invoke all hook_xautoload() implementations, or only those for the modules that were just enabled?

Invoking all implementations would be useful for modules that register stuff on behalf of other modules, or depending on which other modules are enabled. It may result in duplicate namespace registration, but this is probably not going to be a problem.

donquixote’s picture

Does this also apply to regular non-install-profile modules?
Yes it does!

/**
 * Implements hook_xautoload().
 *
 * @param xautoload_InjectedAPI_hookXautoload $api
 */
function MODULENAME_xautoload(\xautoload_InjectedAPI_hookXautoload $api) {
  // Ensure the testlib library (in a subfolder of this module) can be autoloaded.
  $api->addPsr4('Drupal\MODULENAME\testlib\\', 'testlib/src');
}

/**
 * Implements hook_menu()
 */
function MODULENAME_menu() {
  // Let's see if this breaks.
  new \Drupal\MODULENAME\testlib\TestClass();
}

hook_menu() is called after the modules are loaded. The solution in #1 and #6 does fix this.

donquixote’s picture

There are also scenarios where #1 / #6 does NOT help:

// In MODULENAME.module:

function MODULENAME_xautoload($api) {
  $api->addPsr4('Drupal\MODULENAME\testlib\\', 'testlib/src');
}

function MODULENAME_theme() {
  // Let's see if this breaks.
  new \Drupal\MODULENAME\testlib\TestClass();
}

// And in MODULENAME.install:

function MODULENAME_enable() {
  // Call l() to trigger premature hook_theme().
  l('Admin', 'admin');
}

I am not sure whether this could be called a legit scenario, but it is quite typical to happen anyway in Drupal.

A solution can be to invoke hook_xautoload() from xautoload_module_implements_alter(), which we already use to call registerExtensionsByName() for exactly the same reason.

----

Still, a question is whether to invoke hook_xautoload() for just one module or for all.

donquixote’s picture

Title: Using xautoload in an install profile causes pain » Invoke hook_xautoload() after modules have been enabled.

Renaming the issue.

Dave Reid’s picture

It would be consistent to re-run all hook_xautoload implementations, not just for newly-enabled modules.

I'm afraid I cannot provide much further input since I've since switched from using xautoload to the raw Composer autoload.php files which is much more reliable for our production sites. I would highly encourage you to re-evaluate not using hook_module_implements_alter() to split your module files. Aside from potentially causing problems with poisining the module_implements() cache, it also causes other issues like hook_menu() strings not getting picked up by localize.drupal.org - because it *must* be in your .module file.

donquixote’s picture

I've since switched from using xautoload to the raw Composer autoload.php files which is much more reliable for our production sites.

It is certainly more direct, as you don't need to worry about whether another module is already available, or if you have the correct version of that module, etc.
Although if you want to publish the module, you need to consider the case where a 3rd party library is not yet installed, or a library is in a different place than expected.

Where do you put your require_once **/autoload.php?

Fyi, I rarely use hook_xautoload() myself, I mostly use the basic functionality. So I depend on the feedback of others. Not just for bug reporting, but also for API design.

I think I have done ok so far in dealing with *reported* issues.
But for API design, I am still undecided whether I prefer hook_xautoload, or a direct xautoload()->adapter->add(..). And for the latter, whether to call this from hook_boot() or hook_init() or somewhere else.

Anyway, thanks for the input so far.

donquixote’s picture

I am moving the discussion about hook_module_implements_alter() to drupal.stackexchange.
http://drupal.stackexchange.com/questions/108884/hook-module-implements-...
This affects more than just one module, and it is going to be useful for other people than me.

"poisining the module_implements() cache" and "causes way too much pain" is too vague for me to be changing a number of modules. "hook_menu() strings not getting picked up by localize.drupal.org" is a valid point, although I would argue this is rather a flaw in localize.drupal.org. "I don't expect those functions to live in a modulename.system.inc or modulename.ui.inc." is a matter of taste up to some degree, but definitely not to be dismissed.

donquixote’s picture

Status: Needs review » Fixed

Fixed in 7.x-4.5.
There is still the question of doing this from hook_module_implements_alter() to cover some special cases.
But don't cry before it hurts..

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

donquixote’s picture

@Dave Reid
I think libraries integration is now a lot better in the 7.x-5.x branch.

As for hook_module_implements_alter(), I actually found some information that backs your statement.
http://drupal.stackexchange.com/a/129112/2974
It is that simple: module_invoke_all() works, but module_invoke() fails.
Because module_invoke_all() uses module_implements(), but module_invoke() uses module_hook(), which checks only module_hook_info().

And obviously every hand-baked one-off function_exists() + call_user_func() fails, if it does not check either of both.

I think it is unlikely that the hooks we are dealing with here will ever be called with module_invoke() as a singular implementation, but maybe the possibility is enough to reconsider.