Pregunta

I have a project with multiple modules. Each module uses Composer inside itself and is mostly independent of the other modules.

However some modules share dependencies that have different versions. These dependencies are backwards compatible for the most part and use semantic versioning.

I would like to ensure that the dependency with the highest semantic version take precedence. This would allow all modules to share the same dependency and backwards compatibility of these dependencies would ensure nothing breaks.

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

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 was making was that if an autoloader is required before another, it would take precedence over later ones.

What I've found though is that the opposite is true. The autoloader that comes last seems to be taking precedence over the others.

Consider a class Foo\MyClass was a dependency shared between these modules. I'm expecting with the above load order, Foo\MyClass would be picked up from moduleA/vendor/....

Instead it is coming form moduleC/vendor/....

I could flip my generated order to workaround this, but I'd like to verify if there is a predictable order in which the PHP autoloaders.

Is there an order in which PHP executes Autoloaders? Do multiple Composer autoloaders affect this in any way?

Thanks.

¿Fue útil?

Solución

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

What's bad about your situation is that your modules potentially CAN depend on incompatible third party libraries. You mention they use semantic versioning, but this only covers upwards compatibility, like "minor version increases if a new feature gets added in a compatible way to the older version". Which means that this newer version is not backwards compatible!

Assume module A is using version 1.0.7 of a library, and module B is using version 1.2.5. That library got a new method added to a class in version 1.2, and module B is using that method. Can module B run with the class version 1.0.7 of module A? Of course not. You want both modules to run with the highest compatible version for both modules, 1.2.5.

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

If you could create a composer.json file that contains the dependencies for all the modules A, B and C, and each module states it's dependencies on other libraries, Composer would collect all these libraries, calculate the "best" usable version from it, and create an autoloader that would unambiguously load these libraries only.

Added benefit: Only one version per library without duplicates. Only one autoloader object with global knowledge about all available classes (which could optimize autoloading a bit).

And you are half way there. Each of your modules must already have a local composer.json which states the version requirements. Do add a definition for autoloading that module itself, and give it a name. You can then reference that name in the central composer.json (you'd probably need to add the repositories if they are private), and you are almost done. Maybe there is some fiddling with paths if you really need these modules in a defined path.

But that's about it.

And then you solve one other thing: What if module A needs some small part of module B? With Composer you can state that dependency together with all the libraries, and even if you forget to install module B, Composer would do it for you, or remind you about it.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top