composer: [with patch] requiring a platform package that is provided by another required package fails

First, this issue is not entirely trivial to reproduce, because everyone has different PHP versions and extensions installed. To reproduce the steps and behaviors below, please ensure that you run php -n $(which composer) instead of plainly composer, or that with composer show --platform | grep mongo, nothing shows up 😃

If you’re on Ubuntu or something, php -n will also prevent loading ext-phar, so nothing will work. In that case, just make sure you have no ext-mongo, and run composer directly instead of php -n $(which composer).

Let’s say your code needs ext-mongo (via Doctrine ODM, for instance), but that’s not available on PHP 7, so you want to use https://github.com/alcaeus/mongo-php-adapter, which in its composer.json says "provide": { "ext-mongo": "1.6.12" }, and which is a wrapper around ext-mongodb to provide an interface that’s compatible with the old ext-mongo.

Start with a basic composer.json, to mock the existence of ext-mongodb (again, you do not want ext-mongo installed when following along, so always run with php -n later):

$ composer init -n
$ composer config platform.php "7.0.4"
$ composer config platform.ext-mhash "7.0.4"
$ composer config platform.ext-mongodb "1.1.3"

I will use the composer-provide branch of https://github.com/alcaeus/mongo-php-adapter explicitly because the master branch has changed between using provide and replace a few times recently. There is also a composer-replace branch that uses replace instead of provide for ext-mongo.

Remember, php -n will prevent ini loads, so if you have ext-mongo locally, it should not get loaded:

$ php -n $(which composer) require alcaeus/mongo-php-adapter:dev-composer-provide
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing mongodb/mongodb (1.0.1)
    Loading from cache

  - Installing alcaeus/mongo-php-adapter (dev-composer-provide 698d301)
    Cloning 698d3012c306af299491aa166dc9aa29a4bce5b8

Writing lock file
Generating autoload files

Now there is a package that provides ā€œext-mongoā€ installed, and that info is in the lock file. Which works as expected if you now require that extension:

$ php -n $(which composer) require ext-mongo:*
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Nothing to install or update
Writing lock file
Generating autoload files

A subsequent composer update also works. Great, so let’s try a case that fails - having the requirements already in place, but no lock file:

$ rm -rf composer.lock vendor
$ php -n $(which composer) update
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - The requested PHP extension ext-mongo * is missing from your system. Install or enable PHP's mongo extension.

Weird, right? The same happens if you require both in one go obviously:

$ php -n $(which composer) remove ext-mongo
$ php -n $(which composer) remove alcaeus/mongo-php-adapter

$ php -n $(which composer) require alcaeus/mongo-php-adapter:dev-composer-provide ext-mongo:*
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - The requested PHP extension ext-mongo * is missing from your system. Install or enable PHP's mongo extension.

Installation failed, reverting ./composer.json to its original content.

You could of course also manually put both packages into composer.json and run composer update, same effect (that’s actually what happened when we removed lock file and vendor dir earlier and ran an update).

For the steps above, instead of requiring ext-mongo, you can also use doctrine/mongodb, which in turn requires ext-mongo. Same behavior.

There is a workaround: if you put the ā€œalcaeus/mongo-php-adapterā€ package into a ā€œpackageā€ repo inside composer.json, things work. Take this composer.json:

{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/alcaeus/mongo-php-adapter"
        }
    ],
    "require": {
        "alcaeus/mongo-php-adapter": "dev-composer-provide",
        "doctrine/mongodb": "^1.2"
    },
    "config": {
        "platform": {
            "php": "7.0.4",
            "ext-mhash": "7.0.4",
            "ext-mongodb": "1.1.3"
        }
    }
}

That works:

$ rm -rf composer.lock vendor
$ php -n $(which composer) update
Loading composer repositories with package information
Updating dependencies (including require-dev)     
…                         

This pointed to a problem with information from Packagist, and indeed, it’s due to behavior in (and data available to) ComposerRepository.

There is no information from Packagist on provided platform packages such as ext-mongo, so without a lock file, the info that one of the other required packages provides ext-mongo is apparently not there, and that’s why things fail.

Here is the fix:

diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php
index 5bd4e8a..91d6d58 100644
--- a/src/Composer/Repository/ComposerRepository.php
+++ b/src/Composer/Repository/ComposerRepository.php
@@ -371,7 +371,9 @@ public function whatProvides(Pool $pool, $name)
                         // override provider with its alias so it can be expanded in the if block above
                         $this->providersByUid[$version['uid']] = $package;
                     } else {
-                        $this->providers[$name][$version['uid']] = $package;
+                        foreach($package->getNames() as $nameName) {
+                            $this->providers[$nameName][$version['uid']] = $package;
+                        }
                         $this->providersByUid[$version['uid']] = $package;
                     }

However, that feels like a band-aid treating a symptom of the real underlying issue, which is that Packagist does not have a list of provider packages for platform packages:

$ ls ~/.composer/cache/repo/https---packagist.org/provider-ext*
ls: .composer/cache/repo/https---packagist.org/provider-ext*: No such file or directory

/cc @alcaeus @holtkamp @stof

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 8
  • Comments: 24 (17 by maintainers)

Commits related to this issue

Most upvoted comments

Any progress on this? 😢

It’s an issue for me, since I have to run around telling people that they need to add provide: { "ext-mongo": "..." } in their composer.json at root level in order to get this to work. Even if it’s a workaround, I’d appreciate a fix here.

What is the status of this? Trying to use any library that polyfills extension behavior is impossibly difficult and complex to get right. It shouldn’t be. Correct me if I am wrong, but it looks like this issue is continually being pushed to the next release…but this should be a ā€œblockerā€ as it quite literally describes a broken implementation for a feature that is documented involving the most important aspect of Composer: dependency resolution.

I’ll be dead before then.

This should work in 2.0 so closing