The execution order of multi-user autoloaders

I have a project with several modules. Each module uses Composer internally and is largely independent of other modules.

However, some modules share dependencies that have different versions. These dependencies are in most cases compatible with feedback and use semantic version control.

I would like to make sure that the dependency with the highest semantic version takes precedence. This will allow all modules to share the same dependency and backward compatibility of these dependencies, ensuring that nothing breaks.

My plan was to do this by controlling the order in which I call require_once on individual autoloaders. The code below is an example that is generated in practice.

 require_once(__DIR__ . '/moduleA/vendor/autoload.php'); require_once(__DIR__ . '/moduleB/vendor/autoload.php'); require_once(__DIR__ . '/moduleC/vendor/autoload.php'); 

The main assumption I made was that if an autoloader is required over another, it will take precedence over later ones.

I found that the opposite is true. The autoloader that comes last seems to take precedence over others.

Consider the class Foo\MyClass - this is a dependency shared by these modules. I expect that with the above load order, Foo\MyClass will be raised with moduleA/vendor/...

Instead, it comes in the form of moduleC/vendor/...

I could collapse my generated order to get around this, but I would like to check if there is a predictable order in which PHP autoloaders are.

Is there an order in which PHP executes autoloaders? Does several Composer autoloaders affect this?

Thanks.

+7
php composer-php
source share
1 answer

Actually, you are in a mess, but you are already halfway out of this mess without seeing.

What's bad about your situation is that your modules could potentially depend on incompatible third-party libraries. You mentioned that they use semantic version control, but this only applies to upward compatibility, for example, "a minor version is increased if a new feature is added in a compatible way to an older version." This means that this newer version does not support backward compatibility!

Suppose module A uses version 1.0.7 of the library, and module B uses version 1.2.5. This library received a new method added to the class in version 1.2, and module B uses this method. Can module B work with the class version 1.0.7 of module A? Of course not. You want both modules to work with the highest compatible version for both modules, 1.2.5.

How to get it? Use only one Composer autoloader and only one central dependency definition.

If you could create a composer.json file that contains dependencies for all modules A, B and C, and each module determines its dependencies on other libraries, Composer collects all these libraries, calculates the β€œbest” version used from it, and create an autoloader. which will uniquely load only these libraries.

Added benefit: only one version for each library without duplicates. Only one autoloader object with global knowledge of all available classes (which can optimize autoload bits).

And you're halfway there. Each of your modules must have a local composer.json , which specifies version requirements. Add a definition for the startup of this module and give it a name. You can then reference this name in central composer.json (you probably need to add repositories if they are private), and you're almost done. Perhaps there are some paths with paths if you really need these modules on a specific path.

But about that.

And then you decide one more thing: what if module A needs a small part of module B? With Composer, you can specify this dependency along with all libraries, and even if you forget to install module B, Composer will do it for you or remind you of it.

+8
source share

All Articles