Composer is probably one of the most significant tools for the PHP ecosystem. It is the de facto standard for package management in PHP. Still, Composer has some hidden or not so obvious features an functionalities of which many developers are unaware. Whether you are a package author and maintaner, or you are consuming some package in your own project, there are some standards and best practices when using Composer that you should be familiar with and which you should follow. This article provides some useful advices in this regard.

Package authors

As a developer of some PHP library, you tend to offer to a community, to make it useful for other developers. However, that needs to be done responsibly and appropriately. Here are some things that are most often neglected, but which all authors should adhere to.

Tag releases

Please tag releases of your package! And when tagging, do it semantically. Do not force other developers to depend on the dev-master, which is unstable, in terms of Composer's stability flags, but also in sense that it can change at any time.

You can tag releases manually, by creating/pushing VCS tags, or if your project is hosted on GitHub, you can create a release on your project’s page.

Autoloading

Composer has its own autoloading mechanism, and besides autoloading specification for your sources, you can define it in case of classes from your tests namespace (you write unit tests for your library, right?). Many developers do not care and set autoloading in their composer.json this way:

{
    "autoload": {
        "psr-4": { 
            "MyLibrary\\": "src/",
            "MyLibrary\\Tests\\": "tests/"
        }
    }
}

But this is bad. Do NOT pollute the autoloader in production when your package is used as a dependency. Composer also has the autoload-dev property for development purposes, and here is the proper autoloading specification for this example:

{
    "autoload": {
        "psr-4": { "MyLibrary\\": "src/" }
    },
    "autoload-dev": {
        "psr-4": { "MyLibrary\\Tests\\": "tests/" }
    }
}

Optionally, you can optimize autoloading of your library sources, by specifying classmap rules:

{
    "autoload": {
        "psr-4": { "MyLibrary\\": "src/" },
        "classmap": ["src/"]
    }
}

Branch alias

A branch alias is a property that can be put into the composer.json, for the purpose of mapping non-version-like branch names (i.e. master, develop) to comparable version names (i.e. 1.0, 2.1.0). This is useful in cases when someone wants to require the latest dev-master, and there may be a problem if other packages require 1.0.*, which will result in a conflict, since dev-master does not match the 1.0.* constraint. Aliases can be defined under the extra section in composer.json, for example:

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

Composer documentation has a great article on aliasing itself.

Branch aliasing is not so crucial thing to do, because package users can still bypass this obstacle by requiring inline aliases.

Package users

We PHP developers enjoy the benefits of having plethora of useful libraries that can be easily integrated into our project using Composer. Still, one should pay attention to some important details.

Accurate version requirements

When setting version requirements of your dependencies, you want to be as accurate as possible. Declaring a version constraint of * is probably the worst thing you can do. Also, depending on the latest development version, in other words using dev-master constraint, is in my opinion careless and risky, and such approach should be avoided, even if package does not have releases, which is a flaw for which its maintainer is responsible for.

At the very least you should specify constraints in form of patterns with a * wildcard, for example: 1.0.*, 2.* and similar. But besides wildcard operator, Composer offers so called “next significant release” operators – tilde and caret, which are mostly useful for projects respecting semantic versioning and are recommended for maximizing inter-package compatibility.

Still, there is always a risk that some sort of bug or BC break will eventually emerge in some of your dependencies that will break your code. After all, we’re humans. Of course, you can be strict and specify exact version of a package, but as a consequence, this approach can lead to a situation where dependency resolution will ultimately fail and abort any install or update procedures, if some other dependency requires a different version.

composer.lock

The purpose of the lock file is to record the exact versions of dependencies in your project. This means that after dependencies are installed and composer.lock file is committed, when your colleague runs composer install, he/she will get same versions of dependencies, even if newer versions were released in the meantime.

Question that often arises regarding composer.lock file is whether it should be committed to the repo at all. This is the everlasting debate, huh? But in my case, there’s no dilemma, I follow this simple rule:

Always commit composer.lock in case of applications.

Do not commit composer.lock in case of components.

In other words, this means that you should commit the lock file only for applications, but you should never commit it in case of a library someone else is installing and using in his/her project.

Reasons in case of applications are obvious. Putting composer.lock into version makes sure that you and your teammates are using the exact same versions of your dependency packages. What’s more important, it ensures that the same dependencies are used in both production and development environments. It also means that there is no need for version lookups nor dependency resolution, which speeds up the deployment itself.

As for the reasons for not committing composer.lock in case of components development, if you do lock dependencies to a certain version, you might won’t realize that your library is not working with some newer version, even if your CI build is passing, because it will always run with exact same version of a dependency.

Production deploy

Ok, now there is no doubt, composer.lock is committed, so deploy should be pretty straightforward, you only need to run composer install command which will read locked versions of dependencies and install them, right? Not quite. In production environment, you want to make sure that all the development stuff is excluded. You do that by using –no-dev flag:

composer install –no-dev

This will completely omit installation of all the packages required for development (require-dev packages), but also, Composer autoloader generation will skip autoload-dev rules.

One more flag that is very desirable in the production is --optimize-autoloader. It will generate a class-map based on regular autoloading rules (PSR-0/4) which will speed up autoloading in your application.

With these in mind, here the recommended way of running composer install when performing a production deploy:

composer install --no-ansi --no-dev --no-interaction --no-progress --optimize-autoloader

TL;DR

Tips for package authors:

Tips for package users: - Be as accurate as possible when defining version requirements; use Next significant release operators. - Always commit composer.lock in case of applications, never in case of components - Production deploy install command: composer install --no-ansi --no-dev --no-interaction --no-progress --optimize-autoloader

References