Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Order of execution of Multiple Composer Autoloaders

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.

like image 447
Darshan Sawardekar Avatar asked May 14 '14 06:05

Darshan Sawardekar


People also ask

What is Classmap in Composer?

Classmap# The classmap references are all combined, during install/update, into a single key => value array which may be found in the generated file vendor/composer/autoload_classmap. php . This map is built by scanning for classes in all .

What is autoloading classes in PHP?

The spl_autoload_register() function registers any number of autoloaders, enabling for classes and interfaces to be automatically loaded if they are currently not defined. By registering autoloaders, PHP is given a last chance to load the class or interface before it fails with an error.

How do I run Composer autoload?

Define the composer. json file in the root of your project or library. It should contain directives based on the type of autoloading. Run the composer dump-autoload command to generate the necessary files that Composer will use for autoloading.

How does autoload PHP work?

The PHP Autoloader searches recursively in defined directories for class, trait and interface definitions. Without any further configuration the directory in which the requiring file resides will be used as default class path. File names don't need to obey any convention. All files are searched for class definitions.


1 Answers

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.

like image 136
Sven Avatar answered Sep 25 '22 05:09

Sven