39

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

timhc22
  • 7,213
  • 8
  • 48
  • 66

2 Answers2

37

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
Pᴇʜ
  • 56,719
  • 10
  • 49
  • 73
  • 4
    Has anyone found any work arounds to this yet? or does the composer team think this is a feature and not a bug? – jdavid.net Mar 31 '15 at 19:40
  • 6
    I guess this won't ever work because the same extension/package in different versions would have the same namespace and almost the same functions which makes them incompatible. This is not a feature nor a bug. You simply can't have 2 times the same function in the same namespace. – Pᴇʜ Mar 31 '15 at 21:13
  • 1
    @peh, yes that is true but you could have two completely different portions of your code need two completely different versions of a library. If they never collide in loading it would technically be possible to have two different versions. But as far as composer is concerned it is not as it autoloads and has no way built in, to my knowledge, to distinguish autoloading different versions. – Shinrai May 05 '19 at 03:09
25

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.

dordal
  • 613
  • 6
  • 8
  • Is it possible that you could have forked the package in question that used the odd version of guzzle and modified it as needed to be compatible, then composer install'd that one? In a *perfect world* that would be the best way to go, it is a lot more work but can save you from having to do these kind of workarounds. – AmbientCyan Aug 05 '19 at 16:33
  • 3
    In our case no -- the parent library that used Guzzle5 was proprietary third-party code that had been encrypted with the Ioncube loader, and wasn't changeable. We had to have a solution to support Guzzle5 and Guzzle6. – dordal Aug 08 '19 at 21:49
  • It's not always as easy as a find and replace for doing that – Steve Moretz Oct 22 '21 at 07:04
  • 1
    @AmbientCyan in a perfect world, Composer would behave like Dart Pub and install all needed versions of a dependency – Spyryto Apr 29 '22 at 08:37