Composer conflict, how can we use it?
Tags: php, todayilearned
Published: 2021-11-19 09:59:01

Today I found a very useful Composer schema [package link](https://getcomposer.org/doc/04-schema.md#package-links) option named [`conflict`](https://getcomposer.org/doc/04-schema.md#conflict), which is useful when third-party code you rely on is too permissive for your needs.

At the time of writing, the [`conflict`](https://getcomposer.org/doc/04-schema.md#conflict) [man page](https://getcomposer.org/doc/04-schema.md#conflict) states

 > Lists packages that conflict with this version of this package. They will not be allowed to be installed together with your package.

Note that when specifying ranges like `<1.0 >=1.1` in a `conflict` link, this will state a conflict with all versions that are less than 1.0 _and_ equal or newer than 1.1 at the same time, which is probably not what you want. You probably want to go for `<1.0 || >=1.1` in this case.

In order to explain how we can take advantage of this, we’ll use a concrete example.

##The problem with a Guzzle dependency

At Madisoft we love open source and believe in its power, not only because we have third-party code on which our application is based upon (BTW, thanks to everyone who makes our daily job easier!), but also because we like to contribute and have a commitment to OSS.

One of the packages we use and help improve is [BehatPageObjectExtension](https://github.com/sensiolabs/BehatPageObjectExtension), an extension for Behat that incapsulates the [PageObject pattern](https://martinfowler.com/bliki/PageObject.html) (we have written a blog post about PageObjectExtension, in Italian, [here](https://labs.madisoft.it/uso-del-page-object-pattern-con-behat-esempio-pratico/)).

When looking at this [pull request](https://github.com/sensiolabs/BehatPageObjectExtension/pull/128) I’ve opened to add PHP 7.4 support to the library, you may notice a [previous Travis CI build](https://travis-ci.org/sensiolabs/BehatPageObjectExtension/builds/624031440?utm_medium=notification&utm_source=github_status) with [failing test](https://travis-ci.org/sensiolabs/BehatPageObjectExtension/jobs/624031441?utm_medium=notification&utm_source=github_status).

The reasons behind the issue are:

- [Dropping support for PHP older versions](https://github.com/sensiolabs/BehatPageObjectExtension/pull/128/files#diff-b5d0ee8c97c7abd7e3fa29b9a27d1780L23) (check the PHP supported versions [here](https://www.php.net/supported-versions.php))

- Reflecting this change on [.travis.yaml](https://github.com/sensiolabs/BehatPageObjectExtension/pull/128/files#diff-354f30a63fb0907d4ad57269548329e3) (a file that tells the CI platform how to create the environment to run your tests on)

- [Shifting the deps=low flag to the lowest supported PHP version](https://github.com/sensiolabs/BehatPageObjectExtension/pull/128/files#diff-354f30a63fb0907d4ad57269548329e3R6) in order to tell Composer to use the [`--prefer-lowest` flag when installing the dependencies]((https://github.com/sensiolabs/BehatPageObjectExtension/blob/f4613bf3eabc3b6e41d613be60358aae7f56482f/.travis.yml#L13)).

Pay attention to last point on the list: when working on OSS, it’s important to try to keep everyone’s code - that depends on your OSS - working (at least until a bump to a major version) so, in sight of this, it’s common to check if the lowest possible dependencies your code accepts will still make the tests pass.

Once we shifted the `deps=low` flag to PHP 7.2, we found the above issue. It was already there, but we hadn’t noticed as the flag was only on PHP 7.1, meaning that tests never ran against PHP 7.2 with lowest dependencies. As PHP 7.2 introduced [a warning on certain `count()` usages](https://www.php.net/manual/en/migration72.incompatible.php#migration72.incompatible.warn-on-non-countable-types), our code didn’t manage to pass the tests.

But if you look closely, you’ll probably notice that the warning was not raised by our code directly, but by third-party code we’re requiring with Composer:

 > Warning: count(): Parameter must be an array or an object that implements Countable in /home/travis/build/sensiolabs/BehatPageObjectExtension/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php line 66

[We found the related issue on the Guzzle GitHub repository](https://github.com/guzzle/guzzle/issues/1973) and we noticed [a fix had been released with version 6.3 of this library](https://github.com/guzzle/guzzle/issues/1973#issuecomment-350534938). Hurray, we can bump Guzzle version to 6.3 and overcome the problem! But looking at [BehatPageObjectExtension’s `composer.json`](https://github.com/sensiolabs/BehatPageObjectExtension/blob/master/composer.json) we’ve soon realized that Guzzle isn’t a direct dependency managed by us. “It’s not a big deal” I thought as Guzzle was required by Goutte and we require Goutte through [its Mink driver](https://github.com/minkphp/MinkGoutteDriver),so I expected to find some tagged version of Goutte and the Mink Driver with this requirements bump. Sadly I found it wasn’t the case [as Guzzle is required in Goutte with `^6.0`](https://github.com/FriendsOfPHP/Goutte/blob/v3.2.3/composer.json#L19) (so, basically every version between 6 and 7) and this includes the versions with the warning described above. Therefore in our case the warning was displayed as a “side effect” of `deps=low` that requires indirectly Guzzle 6.0 as is the lowest dependency accepted.

Without discussing if it’s right or wrong to keep a version of third party code that could break everthing (take a look at [this PR](https://github.com/FriendsOfPHP/Goutte/issues/393)), we were suddenly at a crossroads: drop the `deps=low` and give up on this kind of tests or dig in the Composer manual and look for something that could possibly help us: you guessed right, `conflict` is what we needed ([thanks to jakzal!](https://github.com/sensiolabs/BehatPageObjectExtension/pull/128#issuecomment-564910876)).

[Here](https://github.com/sensiolabs/BehatPageObjectExtension/pull/128/files#diff-b5d0ee8c97c7abd7e3fa29b9a27d1780R54-R55) are the details of the commit with `conflict`.

##Conclusions

With `conflict` you can *force* the minimum version of a dependency you cannot control directly, avoiding headaches or third party code forks.

Hope this little story helped you to learn something new, personally I did!

Happy coding!

##Bonus

In order to avoid accepting third-party code with well-known security issues you can take advantage of [SecurityAdvisories](https://github.com/Roave/SecurityAdvisories) by Roave, a library which uses `conflict` as shown in this article to block unsafe packages. Give it a look!