How does composer handle multiple versions of the same package?

This may (should) have been asked before somewhere but I can't seem to find an answer. If someone provides a link I can delete this post!:

Just trying to get my head around some of composer's (probably applies to other package managers too) functionality.

Basically I just want to know what composer does in the following scenarios:

1.

My main project has a dependency:

"guzzlehttp/guzzle": "5.0.*",

My external bundle has a dependency on

"guzzlehttp/guzzle": "5.0.*",

Does composer install guzzlehttp/guzzle one time because it knows it only needs it once?

2. Same scenario but in the future if someone updates the main project to use:

"guzzlehttp/guzzle": "6.0.*",

Will composer now install 2 versions of guzzle (5 and 6) (I presume this is what it should do), or will it take the highest version (i.e. 6)? Also if there are 2 versions will this cause any conflicts because namespaces might be the same?

Thanks


Solution 1:

To question 1

Yes Composer can only install one version of each extension/package.

To question 2

Because of answer 1: Composer would consider your main project and the external package as incompatible.

In this case you could

  • stay with version 5 at your main project too.
  • ask the external package owner to upgrade to version 6 too if it's compatible to.
  • fork the external package and make it compatible to version 6 yourself

Solution 2:

We had a situation today where we were using multiple libraries, and one used Guzzle v5 and the other Guzzle v6. Upgrading (or downgrading) was not a viable option, as it was third party code, so we had to be able to install both versions of Guzzle.

Here's what we did. This is a TOTAL FRACKING HACK, and I'd advise doing this only as an absolute last resort. It works, but updating your calling code to use just one version is a much better option.

The trick is that you need to re-namespace one of the two versions. In our case we decided to change v6 to GuzzleHttp6. Here's how to do that:

  1. Make sure your composer.json has v6 enabled:

"require": {
        "guzzlehttp/guzzle": "^6.2"
        // possible other stuff
    },
  1. composer install to get Guzzle v6 all its dependencies installed.
  2. Move the /vendor/guzzlehttp directory over to a new /vendor-static/guzzlehttp directory.
  3. Do a case-sensitive find & replace on the /vendor-static directory to replace GuzzleHttp with GuzzleHttp6. This effectively brings the Guzzle 6 code into a new namespace.
  4. Now update your composer.json to include Guzzle's own dependencies manually, and then autoload the code in the /vendor-static folder. Note you'll want to REMOVE the main guzzle require statement (or change it include guzzle 5);

"require": {
            "guzzlehttp/guzzle": "~5",
            "psr/http-message": "~1.0",
            "ralouphie/getallheaders": "^2.0.5"
        },
        "autoload": {
            "files": ["vendor-static/guzzlehttp/guzzle/src/functions_include.php",
                "vendor-static/guzzlehttp/psr7/src/functions_include.php",
                "vendor-static/guzzlehttp/promises/src/functions_include.php"],
            "psr-4": {
            	"GuzzleHttp6\\": "vendor-static/guzzlehttp/guzzle/src/",
            	"GuzzleHttp6\\Psr7\\": "vendor-static/guzzlehttp/psr7/src/",
            	"GuzzleHttp6\\Promise\\": "vendor-static/guzzlehttp/promises/src/"
            }
        },
  1. composer update to remove the old Guzzle v6, and install Guzzle v5. This will also install the psr/http-message and ralouphie/getallheaders dependencies.

  2. You may need to do a composer dump-autoload to force the autoloader to add the new include paths. In theory this should happen on composer update but I had to force it.

  3. Now update your calling code; instead of calling \GuzzleHttp, you'll call \GuzzleHttp6 .

And that's it. You should be able to run both concurrently. Note that whatever version of Guzzle v6 you've got in the /vendor-static directory will be there forever-more, so you may want to update that from time-to-time.