Jan 7th, 2013

Composer Version Constraints

If you don’t know what composer is, go to the composer homepage and start reading.

I’ve seen many people struggle with the constraints they put on their composer dependencies. Hopefully this post will shed some light on why certain things are bad, and how to avoid them. I will start out with the worst possible scenario, then improve the constraints step by step.

The almighty asterisk

Composer has a dependency resolver, so it should be able to automagically figure out what I need, right? Wrong.

Declaring a version constraint of * is probably one of the worst things you can do. Because you have absolutely no control over what you will get. It could be any version that matches your minimum-stability and other constraints.

Essentially you are playing a game of russian dependency roulette with composer, eventually you will get hurt by it. And then you will probably blame the tool for failing you so badly.

If you’re going to be careless, please at least depend on the latest development version, which is usually labeled as dev-master.

Hard-coded branch names

So now you are using dev-master. The problem is that dev-master is a moving target. For one, you will always get unstable packages (unstable in terms of composer’s stability flags). But the bigger problem is that the meaning of dev-master can change at any time.

Let’s say that it represents the latest 1.0 development version. At some point the author of said library starts working on the 1.1 release, so they branch off a 1.0 branch, and dev-master becomes the latest 1.1 dev version.

Unless you are tracking the development of that library very closely, you will not notice this until you run composer update, it blows up in your face, and ruins your day. That’s why referencing branch names directly is not a sustainable solution. Luckily composer is here to help with branch aliases.

Branch alias

A branch alias is a property that package maintainers can put into their composer.json, that allows branch names to be mapped to versions. For branch names like 1.0, 2.1, etc. this is not necessary – composer already handles those.

But with a branch name like master which produces a version named dev-master, you should definitely alias it. The composer docs have a great article on aliasing that explains how branch aliases can be defined:

{
    "extra": {
        "branch-alias": {
            "dev-master": "1.0.x-dev"
        }
    }
}

This maps the dev-master version to a 1.0.x-dev alias. Which essentially means that you can require the package with a 1.0.*@dev constraint. The nice thing about this is that the meaning of 1.0 is defined and will not change. It will also make switching to stable versions easier.

The caveat of branch aliases is that package maintainers need to put them in. If you are using a library that does not have a branch alias, send them a pull request adding the above extra section to their composer.json.

Stable releases

The 1.0.*@dev constraint is already quite good. The problem however is that there is no stable version yet. This is not problematic for your code - apart from the fact that you are running an unstable version that the maintainer has not committed to.

But if you have other people depending on your package, then your users either need to explicitly require your dependency with a @dev flag to allow composer to install the unstable version, or worse yet lower their minimum-stability, which means they get unstable versions of everything.

To avoid juggling around dev versions it’s much better to just tag releases. If you are using a library that has no tagged releases, go and annoy the maintainer until they tag. Do it, now!

We as the composer community need to take responsibility. We need to tag releases, we should maintain CHANGELOGs. It’s hard to do, but makes a huge difference for the ecosystem as a whole. Remember to tag responsibly and semantically.

When you have a stable release, you can remove the @dev flag and change your constraint to 1.0.*.

Next Significant Release

If the dependency that you’re using is adhering to the rules of semantic versioning and keeps strict BC for point releases, then you can improve the constraint even more.

Right now with 1.0.* there will be some potential compatibility problems as soon as there is a 1.1 release. If you depend on 1.0 but somebody else needs a feature from 1.1 (which is backwards-compatible, remember?), they cannot install it. So you need to resort to do something like 1.*.

That’s great, except when you start depending on features from 1.1, then you can no longer use it, as it will still match the 1.0 version. Which has missing features.

So then you do >=1.1,<2.0, but that’s annoying. Enter the tilde operator, which allows you to express this in a clean way: ~1.1. This means “any 1.* of 1.1 or above”. And there you have it, encourage semantic versioning to take advantage of the tilde and maximise inter-package compatibility.

TLDR

Igor

Brought to you by @igorwhiletrue.

Projects you may be interested in: Silex, Stack, YOLO, React.