Composer packages do provide their own autoload.php. But it can be more efficient to stick all namespaces into one shared class loader.

Two modules exist for D7 which provide PSR-0 class loading: classloader and xautoload.

xautoload-7.x-3.x already has support for sites/*/vendor as composer vendor directory.
#1923724: Composer support for sites/all/vendor and sites/$sitename/vendor
I am not really sure about the future of this. I don't know if sites/all/vendor is the correct place, and I don't know if xautoload should do this by itself or if another module should register those namespaces.

Comments

donquixote’s picture

Issue summary: View changes

Updated issue summary.

cpliakas’s picture

Thanks for pointing out the other projects.

I'd love to explore a soft dependency with existing class loaders. It is only a few lines of code to require the autoloader generated by Composer so I don't think a hard dependency would be warranted, but it would be nice to give people the option of using a single autoloader if they wanted to.

As a side note, I think all of the current solutions (including this module) have benefits and drawbacks, and I am also unsure if sites/*/vendor is the best place as well. The goal of Composer Manager is to ease people into Composer and Packagist by defaulting to common Drupal workflows, although they probably don't make sense in the long run. We have also been experimenting with another structure that I illustrated in #1929626: Composer, Composer Autoload, Composer Vendor, Composer Manager?. There are a lot of different possibilities, and I would love to hear what people thing are best practices in doing all this stuff.

Thanks for the post!
Chris

Grayside’s picture

  • Treat all autoloaders as swappable, including composer's.
  • Make composer's default.
  • Parameterize --autoloader to select from all available autoloaders, to auto-generate any specific integration code.
donquixote’s picture

Another option:
- Always register composer class loader (I think the registration itself is not too expensive)
- Provide a reliable way for other modules to know about the composer directory (which contains the autoload_classmap.php), or to know directly about the map of classes / namespaces.
- Provide a reliable way for other modules to know if new namespaces need to be registered
- Let the other module do the rest.

E.g. for xautoload I could imagine
- A setting in xautoload to let it handle composer namespaces (configured on performance settings page).
- Let xautoload replace the registered composer loader, if the setting is enabled.
- xautoload will directly check autoload_classmap.php to get the namespaces to register.

Motivation:
I want to absolutely avoid that xautoload becomes or is perceived as a possible risk to the system.
E.g. if someone has the wrong version of xautoload, etc.

donquixote’s picture

swappable

What kind of "swappable" are you thinking of? The native spl_autoload_unregister() ?

cpliakas’s picture

Title: Use existing contrib class loaders? » Use existing contrib class loaders
Category: task » feature

Let's start with Xautoload since it seems to be the most popular D7 solution.

Any guidance as to how we can start using this?

donquixote’s picture

I think it would be a good idea to wait until D8 gets PSR-4, and xautoload gets a new branch which supports that.
Maybe that new branch will have simplifications compared to the existing branch.

donquixote’s picture

cpliakas’s picture

Status: Active » Postponed

Wasn't aware of PSR-4. Thanks for pointing it out.

As per recommendation, postponing until progress is made on the issue mentioned above.

donquixote’s picture

I once had the idea to provide this convenient method when implementing.
But apparently this never made it into the module.
Would you say this is useful?

/**
 * Implements hook_xautoload()
 */
function composer_manager_xautoload(xautoload_InjectedAPI_hookXautoload $api) {
  $api->composerVendorDir('sites/default/vendor');
}
cpliakas’s picture

Definitely. To me if someone chooses to use xautoload, then I am fine implementing a hook in this module that leverages that system. I just want to make sure that only one autoloader is registered at a time to prevent any weirdness, so I would be interested to hear your thoughts on how to handle if it even is an issue.

cpliakas’s picture

Issue summary: View changes

Updated issue summary.

donquixote’s picture

I want to mention that xautoload has a new branch 7.x-4.x with PSR-4 support, and an improved API.
This is going to be in alpha until Dries makes a decision on #1971198: [policy] Drupal and PSR-0/PSR-4 Class Loading, but this should not be an obstacle for 3rd party integration.
Also some interface names should be fine-tuned, and compatibility with the 7.x-3.x branch needs to be tested.

The new xautoload API can do this, similar to what i already proposed in #9:

/**
 * Implements hook_xautoload()
 */
function composer_manager_xautoload($api) {
  $api->composerDir('sites/default/vendor/composer', FALSE);
}

xautoload is designed to do everything pretty early in the request. This particular stuff happens on hook_init() or hook_custom_theme() whichever is first, and/or when new modules are enabled.
On the other hand, if APC cache or another cache is enabled, then all the registration stuff will only happen if there is a cache miss. This means, composer_manager_xautoload() will not fire at all on an average request. I think this is pretty awesome :)
There could even be ways to do this in hook_boot() or even earlier..

The real question is, how early is desirable?
#2157583: Fatal errors thrown when a module requires a dependency in composer.json that other modules include in their code base this one is a bit confusing to me, I think we need to get to a conclusion there before we talk about xautoload integration.

cpliakas’s picture

Title: Use existing contrib class loaders » Integrate with contributed class loader modules
Issue summary: View changes
tobiasb’s picture

With this fix.

/**
 * Implements hook_xautoload().
 */
function composer_manager_xautoload(\xautoload_InjectedAPI_hookXautoload $api) {
  $api->composerDir(composer_manager_vendor_dir() . '/composer', FALSE);
}

I can use classes via composer in rules hooks (hook_rules_data_info()) again. \o/ Because _rules_rebuild_cache() is called before composer_manager_init(). I do not why, but it happens.

heddn’s picture

Status: Postponed » Needs work

[#2246699] is in now. Removing postponed tag. I'd love to see something in the contrib space go in for composer so I can use it for htmlpurifier on D8

cpliakas’s picture

Status: Needs work » Postponed

Thanks for your interest.

Re-setting the status just so we are consistent with the statuses in https://drupal.org/node/156119. If anyone wants to move this issue forward I would love to get a patch, however re-marking as postponed since I do not indent to work on it in the near term (reiterating that I would be happy to commit a patch it someone did want to actively work on this.

Thanks,
Chris

donquixote’s picture

Because _rules_rebuild_cache() is called before composer_manager_init(). I do not why, but it happens.

If you look at a stack trace, often such hooks are called as a side effect of things other modules do in hook_init(), hook_custom_theme() or hook_boot(). xautoload "cheats" by implementing hook_custom_theme() in addition to hook_init(), because hook_custom_theme() fires earlier in the request.

I can use classes via composer in rules hooks (hook_rules_data_info()) again.

Can you confirm this still works with xautoload 7.x-5.x?

donquixote’s picture

One problem with xautoload is that someone might have the wrong version of xautoload installed, and gets a WSOD for some features not being supported yet.

Not sure how we can solve this, if we don't want to make it an explicit dependency..

donquixote’s picture

Some kind of feature detection would be nice.
Maybe xautoload should define constants such as

define('XAUTOLOAD_API_7X5X');

Or define a class, so you can do class_exists()..
Or something with class constants?

Also consider that the version in xautoload.info is not available if you have it from git.

cpliakas’s picture

@donquixote,

xautoload "cheats" by implementing hook_custom_theme() in addition to hook_init(), because hook_custom_theme() fires earlier in the request.

Not to derail the thread, but would you suggest Composer Manager do the same thing in terms of registering its autoloader?

donquixote’s picture

Not to derail the thread, but would you suggest Composer Manager do the same thing in terms of registering its autoloader?

You could even consider to use hook_boot().
The question is, which side effects can this have? E.g. if at some point this triggers another hook to fire, then all kind of nonsense can happen. Some module might call watchdog(), and this might trigger theme initialization.
Or even if that does not happen, a hook to fire would cause premature inclusion of all *.module files.

For hook_custom_theme(): I am not sure this happens in every request, e.g. maybe it does not happen in Drush. So, the safe bet would be to implement both hook_init() and hook_theme(), and then whichever goes first.

You should also consider what to do when new modules are enabled.

donquixote’s picture

After a lot of talking and some silence:

There is no fundamental problem in having both Composer's autoload.php and XAutoload working alongside.

However, the XAutoload cache (APC or other) works better if there are no other class loaders in play.
Especially, the "postpone initialization until first cache miss" stuff does not really work, if other modules/libraries want to use classes that xautoload doesn't know. This is because the cache does not store classes that failed to load (e.g. if you call class_exists('NonexisitingClass')). So every first unknown class in a request triggers a "first cache miss" behavior.

This could be mitigated by registering other class loaders (Composer) before xautoload's class loader. However, then we waste some microseconds in Composer's class loader, for classes that are only known by xautoload.

Still, all of this is about a few milliseconds or less. (just a wild guess, it's been a while that I benchmarked it)

There is also a risk involved: There might be a bug in xautoload, or someone might have the wrong xautoload version, or stuff is happening in the wrong time of the request. Or something might go wrong that is not caused by xautoload at all, but people still think it is.
Even if this risk is low, I still don't want to be blamed for "why this crap, when autoload.php works just fine".

The conclusion is to make this opt-in.
Even with both modules enabled, the admin should have to actively do something to enable this behavior.
And, there should be a reliable way out, if Drupal does not boot anymore.
The opt-in could be a setting, or an integration module to be enabled.
If we go for a setting, it would be nice if you can override it from settings.php.

Implementation:
1. Register Composer manager stuff in xautoload.
2. Stop the regular Composer class loader.

1. can be done like in #13.
(I hope that composer_manager_vendor_dir() does not have any unpleasant side effects and can be called at any time in the request)
2. How?

B-Prod’s picture

For the 2. question, I would suggest to modify the composer_manager_register_autoloader() function, so it does not use a static variable but drupal_static(). That allows to modify the static variable elsewhere.

/**
 * Registers the autoloader for all third-party packages.
 */
function composer_manager_register_autoloader() {
  $registered = &drupal_static(__FUNCTION__, FALSE);

So the code suggested in the comment #1943536-13: Integrate with contributed class loader modules could be:

/**
 * Implements hook_xautoload().
 */
function composer_manager_xautoload(\xautoload_InjectedAPI_hookXautoload $api) {
  if (variable_get('composer_use_xautoload_autoloader', FALSE)) {
    if (!$composer_init = &drupal_static('composer_manager_init', FALSE)) {
      try {
        $api->composerDir(composer_manager_vendor_dir() . '/composer', FALSE);

        // Set the static variable only here, so if something went wrong while registering the
        // composer libraries through X-Autoload, the composer autoloader could however
        // be included.
        $composer_init = TRUE;
      }
      catch (\Exception $e) {
        watchdog_exception('xautoload', $e);
      }
    }
    else {
      // @todo We could add a log message there?
    }
  }
}

The purpose of the test if (!$composer_init) { is to avoid adding the mapping to X-Autoload if the composer had already included the composer aulotoad file. This does not seem possible as now, but if the composer_manager module code evolves, there could be a potential issue here.

I assumed in my proposal, by the name of the configuration variable, that the override choice was managed by the Composer Manager module (composer_use_xautoload_autoloader). This means that there should be a extra settings (checkbox) in the administration page that allows to choose X-Autoload autoloader instead of the native autoload file (with a module_exists()).

The FALSE default value means that this settings would be disabled by default. This way, this is the responsibility of the Drupal developer/integrator to enable the X-Autoload override. In edge cases he could disable this feature.

markhalliwell’s picture

Version: 7.x-1.x-dev » 7.x-2.x-dev