Today I found a very useful Composer schema package link option named conflict
, which is useful when third-party code you rely on is too permissive for your needs.
At the time of writing, the conflict
man page 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 aconflict
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, an extension for Behat that incapsulates the PageObject pattern (we have written a blog post about PageObjectExtension, in Italian, here).
When looking at this pull request I’ve opened to add PHP 7.4 support to the library, you may notice a previous Travis CI build with failing test.
The reasons behind the issue are:
- Dropping support for PHP older versions (check the PHP supported versions here)
- Reflecting this change on .travis.yaml (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 in order to tell Composer to use the--prefer-lowest
flag when installing the dependencies.
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, 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 and we noticed a fix had been released with version 6.3 of this library. Hurray, we can bump Guzzle version to 6.3 and overcome the problem! But looking at BehatPageObjectExtension’s 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,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
(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), 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!).
Here 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 by Roave, a library which uses conflict
as shown in this article to block unsafe packages. Give it a look!