[SOLVED!] €500 bounty: autoloaders from different packages being ignored

I’m not 100% sure it’s always caused by Commerce. What I did notice is that FormIt and Commerce have one thing in common: OnManagerPageBeforeRender.

I believe that Commerce is loaded first with the global “loader” in the commerce class. The lead I’m looking into is a Commerce module that’s not being autoloaded. In this case Commerce is also loaded first and the module’s autoloader after it. This is just a quick observation, I hope to research it a bit more this weekend.

As far as I can tell, storing the autoloader instance in Commerce::$loader is not used anywhere but the constructor (at some point the idea was for modules to dynamically add their namespaces to that, but those now ship their own which is require'd in the module class), so that could be safely removed or renamed at the top of the file and constructor around line 192.

So the angle you’re looking at is that it being marked as global may cause something weird? It does look like the generated autoloaders also have a $loader variable but those are within a function (getLoader)

So the angle you’re looking at is that it being marked as global may cause something weird

That’s what I was thinking first yeah, but after a bit more digging I don’t think having it global is causing the issue. If we had a setup where the issue is reproducible that would be great…

@joshualuckers
You could try setting up a basic module for Commerce using the skeleton: https://github.com/modmore/Commerce_ModuleSkeleton

The last two times I built modules I couldn’t get Commerce to recognise the module’s classes without optimising the autoloader.

Yes, I’m experiencing the same at the moment when installing mPDF Writer for Commerce.

I installed mPDF Writer for Commerce but it doesn’t show up in modules. It’s not in the commerce module table in the database either.

In the resolver of mPDF Writer for Commerce the autoloader for the module is included just fine but the class \modmore\Commerce_mPDFWriter\Modules\mPDFWriter isn’t loaded. Calling class_exists returns false (this is also used by the loadModulesFromDirectory function and the reason why it’s not loaded).

Maybe the issue is caused by the various extra’s/modulus using different autoloader optimization strategies. Combining them might cause issues?

Edit: @digitalpenguin installing mPDF Writer for Commerce with the optimized autoloader seems to solve the issue indeed.
Before that I tried to optimize the autoloader of commerce with the various strategies without luck. Extra’s/modules should always release a package with the optimized autoloader to prevent issues.

This goes way over my head, but I ran into the same conflict (after updating Commerce).

I noticed that after the conflict emerged, the site would load correctly again directly after a cache clear. Subsequent requests would fail again with a fatal class not found error.

Optimizing the autoloader of the conflicting package also solved this issue for me.

Brief summary how this issue has been tracked down and solved.

Knowing now Commerce was dealing with a older version (composer < 1.6). Extras with a newer composer version (>= 1.6) assigned the psr-4 class mapping to a attribute which didn’t exists yet in the ClassLoader class of composer < 1.6. Aka the $prefixLengthsPsr4 attribute.

When Commerce is loaded first by MODx. The ClassLoader class of Commerce is leading for the other Extras which are loaded second, third, … (Modules of Ecommerce, Extra Plugin etc.). This because the ClassLoader which have everywhere the same namespace (composer standard) is already resolved via the spl_autoload_register function.

So this was the source of the Class Not Found Exception in the other Extras. Simply the psr-4 directories could never be resolved.

So why the -optimize-autoloader was solving the error?
As @markh mentioned to run the --optimize-autoloader was solving the problem for the other Extras. Why? Because composer < 1.6 contains still the same logic if it comes to class mapping optimizer. I refer to the $classMap attribute in the ClassLoader class. In the ClassLoader class this array is used first to check if a class can be found.

Why ClassLoader class of the first Extra is leading for the other Extras?
This has to do with the nature how multiple autoloading in a project works. Once a class is resolved, this will become leading for the other following code which is using the same namespace class.
So basically within MODx when the first Extra is loaded during the request it’s ClassLoader class will also handle the autoloading for the following Extras in this request. As I mentioned before this is basically also a known issue if multiple autoloaders using different versions of the same dependency.

Why no error logging when assigning wrong mapping attributes to the ClassLoader class?
I’m not sure but I think this has to do how the mappings are assigned to the ClassLoader class. This happens via the Closure:bind. Apparently it doesn’t matter if the attribute in the class instance doesn’t exists when binding happens via the Closure:bind. So no runtime errors/notice are taken place in the error_log of PHP.

Which version of composer should I use?
If you use composer >= 1.6 you should be fine. Composer version < 1.6 apparently has different logic to achieve autoloading (different variables are used in the ClassLoader class). Knowing 1.6 was released somewhere around January 2018. You could say from a common practise point of view to run anyway composer >= 1.6. Aka keep stuff up to date. See PR of the 1.6 version: https://github.com/composer/composer/pull/6976

I hope this give a clear inside what basically happened and if someone faces a similar problem to push him/her in the right direction.

1 Like

Just one addition to that excellent explanation - thanks to @arjentrouwborst’s excellent debugging and working through it with me yesterday, it turned out that the modmore package build process was not using the Composer version I thought it was. While I had updated a bunch of stuff in the course of a server migration sometime last year, that doesn’t help if the build process doesn’t use the composer installation it was supposed to :man_facepalming:

So all modmore packages were actually using a composer version prior to 1.6, which triggers the conflict explained in the post above, and explains why optimising the autoloaders works around the issue.

Following yesterday’s debugging session, I’ve reworked the modmore build process to make sure that uses the composer version it was supposed to, and have updated every package version currently downloadable from the provider with a fresh, up-to-date autoloader. Every package downloaded on Jan 21st 2021 or later will have this fix.

For users that may still experience this issue because they have packages already installed from before this date, I’ve published a help article on the modmore support site with instructions that I’ll also be updating further in the next few days:

As for FormIt’s involvement in this, it turns out that FormIt has its autoloader committed to the repository from a composer version ~1.6-1.8, for which I’ve sent a pull request with an update. Any other extras that may have an autoloader from before 2018 should probably update that, too, just to make sure this doesn’t come back to hunt us later.

@arjentrouwborst Thank you again for your help. This issue has eluded me for months, and with your help it’s now finally figured out and fixed. I’ll be in touch with you via email to sort out the bounty. :moneybag: :smiley:

@wfoojjaec While your replies here didn’t lead directly to this getting resolved, with the benefit of hindsight it turns out you were 100% correct when discussing the autoloader differences. If only that was explored in more depth and had pinpointed the problem in modmore’s build workflow, we could’ve had this sorted out many months ago. So I’d like to give you an honorary €100 bounty as well for your help on that, and will be reaching out to you about that privately shortly.

3 Likes