Skip to content

[with patch] requiring a platform package that is provided by another required package fails #5030

@dzuelke

Description

@dzuelke

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions