A reasonable assumption, IMO, is that hook_modules_installed should fire after a module is installed but before it's enabled. Currently that's not the case: hook_modules_installed and hook_modules_enabled run only after modules have been both installed and enabled.

In other words, with these two hooks it's impossible to inject logic between the module install and module enable phases.

In the original patch for this feature (#253569: DX: Add hook_modules to act on other module status changes), hook_modules_installed was invoked before module_enable. Since then, drupal_install_modules has gone away, module_enable has been rewritten, and install.inc has been refactored. At some point, hook_modules_installed was moved.

In this patch:
The invocation of hook_modules_installed is moved back to where it was, directly after module installation, before module enabling.

Advantages:
Allow developers to inject logic between install and enable phase.

Disadvantages:
The number of module_invoke_all('modules_installed') calls will increase from 1 to N, where N is the number of modules being installed for the current request.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

AaronBauman’s picture

Status: Active » Needs review
Issue tags: +D7DX
jhodgdon’s picture

Do you have a use case for wanting it to be this way? I mean, I agree that it's somewhat reasonable, but is there a reason for thinking that you'd want to do something between install and enable, that would justify the overhead?

sun’s picture

I don't see the relationship to #569326: Add hook_taxonomy_vocabulary_info() - could you please explain?

AaronBauman’s picture

forum.module defines a vocabulary via hook_taxonomy_vocabulary_info.
taxonomy.module reads the vocabularies defined thusly during its implementation of hook_modules_installed.
During taxonomy_modules_installed, any newly-defined vocabularies are inserted into the database.
In forum.install, forum_enable depends on the vocabulary defined in forum_taxonomy_vocabulary_info.
Therefore, unless taxonomy_modules_installed is called before forum_enable, the vocabulary it expects to find will not have been created yet.

Did that make sense?

AaronBauman’s picture

Following up, maybe somewhat tangentially:
ctools gets around this bootstrapping problem by implementing a system to enable/disable/override/clone/etc. code-based settings and content.

For this system, when loading an entity or setting via ctools, ctools gathers everything it needs from code as well as everything it needs from the database. The database takes precedence, and anything defined in both places is called "overridden". Anything defined in code only is called "default". Anything defined in the database only is "custom" more or less. Moreover, a setting or entity defined in code is a first-class citizen that doesn't need to be duplicated to the database and referenced with an auto-incrementing primary key.

The ctools system hinges on having unique identifiers for vocabularies across modules, e.g. forum_nav_vocabulary, and using these as primary identifiers. Drupal uses an arbitrarily assigned integer, e.g. vocabulary's vid, that could easily result in conflicts between modules and across sites. Such a change would represent a pretty fundamental shift in The Drupal Way™, so for now it seems like the best solution is to process some x_info hooks and dump the results into the database...

sun’s picture

+++ includes/module.inc	24 Jun 2010 15:29:14 -0000
@@ -393,11 +393,6 @@ function module_enable($module_list, $en
-  // If any modules were newly installed, invoke hook_modules_installed().
-  if (!empty($modules_installed)) {
-    module_invoke_all('modules_installed', $modules_installed);
-  }
-
   // If any modules were newly enabled, invoke hook_modules_enabled().
   if (!empty($modules_enabled)) {
     module_invoke_all('modules_enabled', $modules_enabled);

I still don't understand - hook_modules_installed() is invoked before hook_modules_enabled() right now already, as visible in the copied context.

Powered by Dreditor.

AaronBauman’s picture

Title: hook_modules_installed should be invoked after modules are installed but before they are enabled. » hook_modules_installed should be invoked after hook_install but before hook_enable

The patch is for module_invoke_all('modules_installed') to be invoked before module_invoke('enable').

Updating title for clarification.

sun’s picture

Title: hook_modules_installed should be invoked after hook_install but before hook_enable » hook_modules_installed() should be invoked before hook_enable()
Status: Needs review » Reviewed & tested by the community

d'oh.

Looks good then. The passed $modules "array" doesn't make much sense then, but stays consistent with hook_modules_enabled(), and consistency is good.

Not sure whether we need David Rothstein or catch to double-confirm this tweak. I don't think so.

sun’s picture

Status: Reviewed & tested by the community » Needs review

The only issue might be expensive operations being done in http://api.drupal.org/api/search/7/modules_installed implementations... contrary to now, they wouldn't know when the last module has been installed, so as to be able to do an expensive cache/data clearing and rebuilding, considering any new information provided by all newly installed modules.

AaronBauman’s picture

Good point - field_modules_installed calls field_cache_clear.
(For that matter, so does field_modules_enabled -- #836962: field_cache_clear called twice sequentially when installing modules.)

However, it's impossible to install a module without enabling it.
So, hook_modules_enabled would always be called after hook_modules_installed was called.

If an implementation needs to invoke an expensive operation after the module installation is complete, it should not be done in hook_modules_installed anyway. Right? Can you think of a counter example? The only one I can think of is a convoluted case in which an implementation of hook_modules_installed makes changes that invalidate fields' cache, and an implementation of hook_modules_enabled relies on those specific changes. In that case, maybe hook_modules_enabled should be responsible for setting its system weight higher than fields', or for clearing the fields cache itself?

yched’s picture

When 10 modules are enabled, I don't think replacing one call to hook_modules_installed() notifying of all modules by 10 separate calls is a good idea.

jhodgdon’s picture

yched has a point. But the point of this issue is that sometimes you need/want to react to the fact that a module has been installed, before the hook_enable() for that module gets called. So you need the hook_modules_installed() to run before that.

I think?

AaronBauman’s picture

Re #11: I agree that it's not ideal.
Re #12: bingo.

So, the attached patch refactors module_enable() in the following ways to strike a balance between the two concerns:

  • Break the main loop over $module_list into two separate loops, say an "install" loop and an "enable" loop.
  • Move the invocation of hook_modules_installed() in between the two loops.
  • Move the invocation of hook_modules_enabled() after the "enable" loop.

By doing this, hook_modules_installed() gets invoked between "install" and "enable" phases, addressing the OP; and it's invoked only once, addressing the concern in #11.

jhodgdon’s picture

You have some whitespace issues in that patch - tabs vs. spaces I think. When I look at the patch in my browser, the indentation is wonky. No tabs.

Other than that, this seems like a good approach to me.

David_Rothstein’s picture

Well, this is a bit tricky actually.

The current behavior of waiting to invoke the hook is a result of #620298: Schema not available in hook_install(). I can see why we might want to reverse that and not wait, but the counterargument is that in Drupal "installing a module" generally refers to the whole process of starting it from nothing and getting it fully ready to be used. As someone implementing hook_modules_installed(), I think I would reasonably expect that the list of modules I receive are fully installed, enabled, and ready to be treated like first-class module citizens.

I don't think we have to look very far to see an example of that:

http://api.drupal.org/api/function/user_modules_installed/7

This function receives the list of newly-installed modules and goes on to invoke hook_permission() in each of them; in doing so, it expects to get back a complete list of that module's permissions. But many modules in Drupal have dynamic permissions that depend, e.g., on items stored in the database - items which may very well have been initially inserted in hook_enable(). So if hook_enable() hasn't run yet, you'd get an incomplete permission list back, no?

David_Rothstein’s picture

Also, a minor point about the patch:

-  if (!empty($modules_installed)) {
-    module_invoke_all('modules_installed', $modules_installed);
-  }
+  module_invoke_all('modules_installed', $modules_installed);

Why remove that if() statement?

AaronBauman’s picture

Re: #15
OK, that makes perfect sense.
Thanks for the background and explanation.

Unless anyone else has a counter argument, I think this is now "won't fix".

I will attack #569326: Add hook_taxonomy_vocabulary_info() from a different angle.

sun’s picture

Title: hook_modules_installed() should be invoked before hook_enable() » Improve documentation about install/enable/etc. hook invocation order
Status: Needs review » Active

This question and topic is sufficiently complex that we should put the considerations and reasoning from this issue into code.

i.e. into doxygen description of module_enable(), which the patches in here kept on touching.

jhodgdon’s picture

Component: install system » documentation

Excellent idea, and at least put a sentence like

See module_enable() for a description of blah blah blah

in both hook_modules_enabled() and hook_modules_installed().

AaronBauman’s picture

Status: Active » Needs review
FileSize
9.26 KB

OK, in this patch:

  • Update the documentation for module_enable() to describe the order of operations and add references to hook_install, hook_enable, hook_modules_installed, and hook_modules_enabled
  • Update documentation for hook_modules_installed and hook_modules_enabled to reference module_enable()
  • Update documentation for hook_install and hook_enable: add reference to module_enable()

There were also discrepancies in the placement of @see notations in system.api.php, and some trailing whitespace which I couldn't help fixing.

Status: Needs review » Needs work
Issue tags: -D7DX

The last submitted patch, 836748_module_enable_hook_installed_enabled_documentation.patch, failed testing.

jhodgdon’s picture

Status: Needs work » Needs review
Issue tags: +D7DX
jhodgdon’s picture

Status: Needs review » Needs work

A few thoughts:
- All of your @see lines need to have () after the function names, so they can properly turn into links on api.drupal.org.
- Since you've gone to the trouble to define the difference between "enable" and "install", maybe the one-line description of module_enable() should say that it installs and/or enables modules?
- If you think that's a good idea, you could bring the first line into compliance with our standards (start with 3rd person verb) -- see
http://drupal.org/node/1354
- I appreciate the effort you went to to move the @see lines to the end of docblocks, but please do this on a separate issue for functions unrelated to this issue, or we'll get accused of killing kittens.
- It seems like hook_disable() and hook_uninstall() should maybe have @see to each other, and maybe hook_enable and hook_install should reference each other too?
- If you're in there fixing hook_enable(), you could correct the grammar/spelling on the line above too:

  * The hook is called everytime module is enabled.
+ *
+ * @see module_enable
  */
 function hook_enable() {

That line should be:
This hook is called every time the module is enabled.

AaronBauman’s picture

Thanks for the feedback.
Agreed on all points except I'm not a huge fan of "and/or" because I find it's often abused.
In this case it's unnecessary since the definition of "Installing" a module includes "Enabling" it, so we only need "or".

So, changes from the previous patch:

  • Fixes @see's
  • Updates first line of module_enable() to "Enables or installs a given list of modules."
  • Added mutual references between hook_enable and hook_install; and hook_disable hook_uninstall. All reference hook_install.
  • Updated documentation for hook_install for accuracy.
  • Saved kittens from almost certain death.
AaronBauman’s picture

Status: Needs work » Needs review
jhodgdon’s picture

FileSize
7.74 KB

Fixed a couple of typographical/grammatical errors and added some more @see lines (repatch is faster than explaining...).

sun’s picture

Status: Needs review » Needs work

Thanks!

+++ modules/system/system.api.php	25 Jun 2010 19:13:54 -0000
@@ -2050,15 +2050,18 @@
+ * hook_install() is only called on the module actually being installed. See
+ * module_enable() for a detailed description of the order in which install and
+ * enable hooks are performed.

(and elsewhere) "...are invoked."

+++ includes/module.inc	25 Jun 2010 19:13:53 -0000
@@ -286,8 +286,25 @@
+ * - Invoke hook_modules_installed() on the list of installed modules.
+ * - Invoke hook_modules_enabled() on the list of enabled modules.

s/on the list of/in all/

49 critical left. Go review some!

AaronBauman’s picture

Status: Needs work » Needs review
FileSize
8.17 KB

Last one for me today until Monday...

jhodgdon’s picture

FileSize
7.67 KB

I'm not fond of some of the wording you changed since my last patch, such as:
+ * - Invoke hook_modules_installed() in all installed modules.
vs.
+ * - Invoke hook_modules_installed() on the list of installed modules.

Hmmm. I guess that was sun's idae. However, I think my version (the second one) is more accurate, since the list is the argument to the hook, which is invoked once, passing in the array of all the modules as the argument.

Or maybe we should just say "Invoke hook_modules_installed()." and leave it to hook_modules_installed() to document what its arguments are?

Here's yet another patch...

sun’s picture

Status: Needs review » Reviewed & tested by the community

yup, my request. "Invoke hook on the list of ..." sounded like we'd want to call functions on a list, but we are calling functions in certain modules, and "invoke foo in all bar things" is quite often used throughout core and contrib, to my knowledge.

Anyway, I guess we have more pressing issues than these nitpicks ;)

Dries’s picture

Status: Reviewed & tested by the community » Fixed

Nice improvements. Committed to CVS HEAD. Thanks.

Status: Fixed » Closed (fixed)
Issue tags: -D7DX

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