Disclaimer

This article intends to document the research i have done in order to understand how composer works.  It is only my understanding of things, and may contain errors, or be otherwise inaccurate.

It is not a tutorial on how to use composer; the reader is expected to be familiar with composer and its use in general.

Use -vvv with composer to get verbose output that will aid understanding of composer workings.

Package versions

When composer needs to install a package that is listed as a dependency in another package’s composer.json, it needs to know which version of the package to install.  As i have mentioned in a previous article, package repositories of type composer, such as packagist.org, automatically crawl vcs repos to construct different package versions of the same project, based on information from branch and tag names.  So, potentially for a particular package, there will be many versions that can be installable, which is why only the package name is not enough, and the user is also required to give a ‘version constraint’ when listing a dependency.

Composer will try to install the latest version of a package available, as long as that version matches the constraint given.  For example, suppose there are 4 tagged releases of a git-hosted project, leading to 4 versions of the package available:

  • 2.1.4
  • 2.1.5
  • 2.2.0
  • 3.0.0

A version constraint in a dependency requiring that package is written like: 2.1.* – Composer will install the 2.1.5 version, because that is the latest version that matches the constraint.

Stability

Sometimes only version numbers are not enough to choose a package version, because of the way composer infers package versions from vcs information.

Let us take again the previous example with the 4 versions of the package.  We now have a constraint as such:

2.2.*

Since there is only 1 possibility here, there is no ambiguity in choosing a version.

But, let us say, in addition to the tagged release 2.2.0, there existed a branch called 2.2.2, where development was going on for the 2.2.2 version of the project.  As we know, a package version 2.2.2-dev would also be available.  Considering the version numbers only, this one is now also a potential candidate for installation, because 2.2.2 matches the constraint 2.2.*.

Basically, the choice we need to make here is to decide whether we go for a stable version of the package, or we can afford a higher version but one still at a development stage.  In essence, we need to decide the stability of the package we want to allow in our project.

In a version constraint in our composer.json, if we mention only version numbers, composer will look at the ‘minimum-stability’ setting, which if unspecified, defaults to ‘stable’.  In our example above, it would mean the stable 2.2.0 version would be installed, because that is the minimum stable we allow (the dev version is not stable enough).

We can also override for each version constraint by using stability flags, e.g, 2.2.*@dev – this would install a dev version if: it is available, it matches the constraint, and is a newer version than other choices available.  In other words, it permits to consider a less stable package version when considering the latest matching version to take.  In the example, the 2.2.2-dev version would be chosen.

We can even override everything at the install or update level, by using the –prefer-stable option at the composer command-line install/update commands.

dist or source

When it has chosen a package version to install, composer will need to download that version.  Normally there will be both a dist and a source URL to choose from.  The dist is supposed to offer an archive type installable object, like a zip file.  The source will normally point towards a vcs repo, with additional information in the json, so as to be able to pull data from that repo.

If the package version being downloaded is a stable one (without dev/alpha/beta etc prefix/suffix), composer’s default behaviour is to use the dist.

For dev versions, composer chooses the ‘source’.

This behaviour can be overriden in a few ways.  One way is to use the options –prefer-dist and –prefer-source when launching the install or update commands.  The –prefer-X means use X if there is a choice.

Different versions of the same package

You may have encountered systems where different versions of libraries were installable.  For example, your OS might permit different versions of a runtime library to co-exist on the same system, and different applications may use the version they require.

In a project managed by composer, you may have noticed the way library files are arranged in the vendor directory; there is at the top the vendor directory, and then a sub-directory for the package contents.  This makes it clear that composer does not make provision in the file hierarchy for different versions of a package to exist (else there needed to be some way to separate each version, e.g, maybe further sub-directories for each version).  Indeed, composer allows to install a single version of a package.  But it does that by doing a dependency resolution processing, which allows it to wisely choose a version of a package in such a way, so that the version satisfies every dependency of the project.  

Let’s say one of your dependencies depends on version 1.3.* of library A, and another of your dependencies depends on version

This also explains why you cannot specify two different exact version constraints for two different package dependencies in the same project.  For example if dependency 1 requires library A v1.2.3, but dependency 2 requires library v1.3.0, then this is signalled as an error by composer.

This is why version constraints are mostly specified using constraint operators like wildcard (*), tilde (~), caret (^), etc.  This leaves enough freedom for other packages to be able to specify their own dependencies.  If you are too restrictive with your constraint, it may block the installation if another dependency does not agree with the version of the library you are willing to install, as it may require an altogether different version to work properly.  On the other hand, if you are too relaxed with your constraints, other dependencies will dictate the version of the library that is installed, and that may not necessarily be compatible with what you needed.

Branch aliases

Consider the following scenario.  You use as a dependency a library that tags its stable releases on the master branch, while latest development continues on master itself.  You know that for your project, you can safely require any latest version of the library, so you use the constraint ‘dev-master’ to specify your dependency.  A problem occurs when another of your dependencies depends on the same library, but it requires a specific version range, e.g, 1.2.*.  Since it is not possible to find a common version for ‘dev-master’ and the comparable version (as numbered versions are called from composer documentation) 1.2.*, composer fails the install.  What is required is that the author of the library give a comparable version numbering to the current state of the master branch, so that users can use it instead of the fixed branch name in a constraint.  For example, versions 1.3.1 and 1.3.2 have been tagged on the master branch, and development for version 1.3.3 is going on in the same branch.  The author will alias dev-master as 1.3.* in his composer.json.  The alias needs to be a dev version, hence the dev suffix.

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


Now a user will need to use the alias to require the package, instead of using the branch name (dev-master).  If we use this constraint it basically means: get me the latest of the 1.3.x line of development if possible – if another requirement needs a specific 1.3.X version, use that instead, it is fine by me.


Another case where aliases are useful is a scenario like where, e.g, development goes on the master branch, but at some point a specific version branch will be created, while latest dev continues on master.  For example, dev for version 1.2 is ongoing on master.  After some time, a branch named 1.2 is created from master.  Main dev continues on master, where features are added, APIs may change etc, but the branch keeps the APIs and everything else at version 1.2, and basically serves for patches and bug fixes in version 1.2.  If a user had required ‘dev-master’ in his project, and he relied on the API for 1.2, on the next composer update he might find his application break, since composer will update to the latest ‘dev-master’, which now of course has evolved and may no longer be version 1.2 compatible.  If originally, the author had provided an alias for dev-master (for example, 1.2.x-dev), and the user had used the alias in composer.json instead of the branch name, a composer update would have taken the branch package version for 1.2, and it would work.  But since an alias is always a development version, sooner or later the author will surely stabilise a 1.2.x version, and the user can then modify his composer.json to require that stable version.









Advertisements