21 tips for using Composer efficiently

Original author: Martin Hujer
  • Transfer
  • Tutorial

While most PHP developers can use Composer, not everyone does it efficiently or in the best way possible. Therefore, I decided to collect tips that are important for my daily work. Most of them rely on the principle “Keep away from sin”: if something can be done in several ways, then I choose the least risky one.

Tip # 1: read the documentation


I'm serious. His documentation is wonderful, and a few hours of reading will save you a ton of time in the long run. You will be surprised how much Composer can do.

Tip # 2: distinguish between a project and a library


It is important to know what you are creating - a project or a library. Each of the options requires its own set of techniques.

A library is a reusable package that needs to be added as a dependency. For example symfony/symfony, doctrine/ormor elasticsearch/elasticsearch.

A project is usually an application that depends on several libraries. Usually it is not used several times (no other project will need it as a dependency). Typical examples: the site of an online store, a user support system, etc.

Further in the tips I will switch between the library and the project.

Tip # 3: Use specific dependency versions for your application


If you are creating an application, then use the most specific version number to determine the dependency. If you want to analyze the YAML-files, it is determined depending, for example, as follows: "symfony/yaml": "4.0.2".

Even if the library follows the rules of Semantic Versioning, minor and patch versions can still cause backward compatibility violations. Suppose if you use "symfony/symfony": "^3.1"something that is deprecated in 3.2 will break your tests. Or in PHP_CodeSniffer there will be a fixed bug, and new formatting errors will be detected in your application, which again can lead to a broken assembly.

Update dependencies deliberately, not impulsively. We’ll talk more about this in one of the following tips.

This may seem excessive, but attention to dependency versions will prevent your colleagues from inadvertently updating all dependencies when adding a new library to the project (which you might have missed when revising the code).

Tip # 4: use version ranges for library dependencies


If you are creating a library, then determine the most possible range of versions. If you create a library that uses the library symfony/yamlfor YAML parsing, request it like this: "symfony/yaml": "^3.0 || ^4.0"

Then your library can use it symfony/yamlfrom any version of Symfony from 3.x to 4.x. This is important because this restriction applies to the application that accesses your library.

If there are two libraries with conflicting requirements (one, for example, needs ~ 3.1.0, and the other ~ 3.2.0), then the installation will fail.

Tip # 5: in applications you need to commit composer.lock in Git


If you are creating a project , then you need to commit composer.lock in Git. Then everyone - you, your colleagues, your CI server and the production server - will use the application with the same dependency versions.

At first glance, this advice seems redundant. You have already chosen a specific version, as in tip number 3. But there are still dependencies of your dependencies that are not bound by these restrictions (for example, it symfony/consoledepends on symfony/polyfill-mbstring). So without the composer.lock commit, you won’t get the same set of dependencies.

Tip # 6: put composer.lock in .gitignore in libraries


If you create a library (let's name it acme/my-library), then you do not need to commit the composer.lock file. This does not affect projects using your library.

Say, acme/my-libraryuses monolog/monologas a dependency. If you commit composer.lock, then everyone who develops acme/my-librarywill use the older version of Monolog. But when you finish working on the library and use it in a real project, a newer version of Monolog may be installed, which will be incompatible with your library. But before you did not notice this because of composer.lock!

It is best to put composer.lock in .gitignore so that you don’t accidentally commit.

If you want to be sure that the library is compatible with different versions of its dependencies, read the next tip!

Tip # 7: run Travis CI builds with different dependency versions


The tip applies only to libraries (because you use specific versions for applications).

If you are building an open-source library, you are probably starting builds using Travis CI. By default, Composer installs the latest possible versions of dependencies allowed by restrictions in composer.json. This means that the ^3.0 || ^4.0assembly will always use the latest version of the v4 release to limit dependency . And since version 3.0 has never been tested, the library may not be compatible with it, which will sadden users.

Fortunately, Composer has a switch --prefer-lowestfor installing the oldest possible version of the dependencies (it must be used together with --prefer-stableto prevent the installation of unstable versions).

The updated configuration .travis.ymlmay look like this:

language: php
php:
  - 7.1
  - 7.2
env:
  matrix:
    - PREFER_LOWEST="--prefer-lowest --prefer-stable"
    - PREFER_LOWEST=""
before_script:
  - composer update $PREFER_LOWEST
script:
  - composer ci

You can see it in the work on the example of my library mhujer/fio-api-phpand matrix assembly Travis CI .

Although this solution will allow you to catch most of the incompatibilities, remember that there are many combinations of dependencies between the older and lower versions. And they may be incompatible.

Tip # 8: sort packages into require and require-dev by name


It’s a good habit to keep packages in require and require-devsorted by name. This will help prevent unnecessary merge conflicts when rebasing the branch. Because if you add a package at the end of the list in two branches, merge conflicts will occur every time.

Manually doing this is tedious, so it’s better to configure it in composer.json:

{
...
    "config": {
        "sort-packages": true
    },
…
}

The next time you request ( require) a new package, it will be added to the correct location (not the end).

Tip # 9: don't try to merge composer.lock when rebasing or merging


If you added a new dependency (and composer.lock) to composer.json and another dependency was added to the merger of the branch, you need to rebase the branch. And you get a merge conflict in composer.lock.

Never try to resolve it manually, because the composer.lock file contains a hash of the dependencies defined in composer.json. So even if you resolve the conflict, you get an invalid lock file.

It’s better to create .gitattributes at the root of the project with the following line, and then your Git will not try to combine composer.lock: /composer.lock -merge

You can solve the problem using short-term feature branches, as suggested in Trunk Based Development(this must be done anyway). If you have a properly merged short-term branch, the risk of merge conflict in composer.lock is minimal. You can even create a branch just to add a dependency and merge it right away.

But what if there is a merge conflict in composer.lock when relocating? Allow it using the wizard version, so you will only have changes in composer.json (recently added package). And then run composer update --lockthat wants to update the composer.lock file with the changes from composer.json. Now you can stage the updated composer.lock and continue rebasing.

Tip # 10: Remember the difference between require and require-dev


It is important to remember the difference between the require and blocks require-dev.

The packages required to run the application or library must be defined in require (for example, Symfony, Doctrine, Twig, Guzzle ...). If you are creating a library, be careful what you put in require. Each dependency in this section is also a dependency of an application using the library.

The packages required for developing an application or library must be defined in require-dev(e.g. PHPUnit, PHP_CodeSniffer, PHPStan).

Tip # 11: Update Dependencies Safely


I believe you agree with the statement that dependencies need to be updated regularly. And I advise you to do dependency updates transparently and thoughtfully, and not as much as any other work. If you refactor something and at the same time update some libraries, you will not be able to tell whether the application is broken by refactoring or updating.

Use the command composer outdatedto see which dependencies can be updated. You can also enable --direct(or -D) to display only the dependencies specified in composer.json. There is also a switch -mfor updating only minor versions.

For each deprecated dependency, stick to the plan:

  1. Create a new branch.
  2. Update composer.json to the latest version of the dependency.
  3. Run composer update phpunit/phpunit --with-dependencies(replace the phpunit/phpunitname of the updated library).
  4. Check CHANGELOG the library repository on GitHub to see if everything is breaking. If there is, update the application.
  5. Test the application locally. If you use Symfony, you can find deprecated warnings in the debug panel.
  6. Commit changes (composer.json, composer.lock and all that is needed for the new version to work).
  7. Wait until the CI build is complete.
  8. Merge and expand.

Sometimes it’s advisable to update several dependencies at once, for example, when updating Doctrine or Symfony. Then it’s better to list them in the update command:

composer update symfony/symfony symfony/monolog-bundle --with-dependencies

Or you can use the template to update all the dependencies from a certain namespace:

composer update symfony/* --with-dependencies

I know, all this looks tedious, but you are sure to update the dependencies on occasion, so it’s better to play it safe.

You can only ease your work in one way: update all dependencies at once require-dev(if they do not require changes in the code, otherwise I suggest using separate branches to simplify the code revision).

Tip # 12: you can define other types of dependencies in composer.json


Besides defining libraries as dependencies, you can also define other things there.

For example, which versions of PHP the application / library supports:

"require": {
    "php": "7.1.* || 7.2.*",
},

Or what extensions the application / library needs. This is very useful if you are trying to put the application in a container or if your new colleague is setting up the application for the first time.

"require": {
    "ext-mbstring": "*",
    "ext-pdo_mysql": "*",
},

Use * for extension versions because they may be inconsistent .

Tip # 13: check composer.json during the CI build


composer.json and composer.lock should always be in sync. Therefore, it is advisable to automatically check their synchronization. Just add this mechanism to your build script:

composer validate --no-check-all --strict

Tip # 14: Use the Composer Plugin in PHPStorm


There is a composer.json plugin for PHPStorm . It adds an autocomplete and a series of checks when manually changing composer.json.

If you use a different IDE (or just a code editor), you can configure the verification of its JSON scheme .

Tip # 15: Define working PHP versions in composer.json


If you, like me, sometimes like to run pre-release versions of PHP locally locally , then you risk updating dependencies to versions that do not work in production. Now I'm using PHP 7.2.0, i.e. I can install libraries that will not work on 7.1. And since production uses 7.1, the installation will fail.

But you don’t need to worry, there is an easy solution. Just define the working versions of PHP in the section of config the composer.json file:

"config": {
    "platform": {
        "php": "7.1"
    }
}

Do not be confused by a section requirethat behaves differently. Your application can run on 7.1 or 7.2, but at the same time 7.1 will be defined as a platform version, i.e., the dependencies will always be updated to a version compatible with 7.1:

"require": {
    "php": "7.1.* || 7.2.*"
},
"config": {
    "platform": {
        "php": "7.1"
    }
},

Tip # 16: use private packages from Gitlab


I recommend choosing vcs as the type of repository, and Composer should determine the correct way to retrieve packages. For example, if you add a fork with GitHub, it will use its API to download the zip file instead of cloning the entire repository.

But with a private installation with Gitlab is a bit more complicated. If you use vcs as the type of repository, Composer will detect it as a Gitlab installation and try to download the package via the API. This will require an API key. I did not want to configure it, so I did it (my system uses SSH for cloning).

First defined a repository like git:

"repositories": [
    {
        "type": "git",
        "url": "git@gitlab.mycompany.cz:package-namespace/package-name.git"
    }
]

And then I used the package, as is usually done:

"require": {
    "package-namespace/package-name": "1.0.0"
}

Tip # 17: how to temporarily use a fork branch with bug fix


If you found a bug in a public library and fixed it in your fork on GitHub, then you need to install the library from your repository, and not from the official one (until the fix is ​​merged and a fixed release appears).

This can easily be done with inline aliasing :

{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/you/monolog"
        }
    ],
    "require": {
        "symfony/monolog-bundle": "2.0",
        "monolog/monolog": "dev-bugfix as 1.0.x-dev"
    }
}

You can test your fix locally before downloading it using the path type of repository .

Tip # 18: install prestissimo to speed package installation


Composer plugin hirak/prestissimoaccelerates dependency installation through parallel download.

It is enough to install it once globally, and it will automatically work for all projects:

composer global require hirak/prestissimo

Tip # 19: If unsure, test your versioned restrictions


Writing the correct versioned restrictions sometimes becomes a daunting task after reading the documentation .

Fortunately, there is a Packagist Semver Checker that lets you check which versions meet specific restrictions. Instead of simply analyzing versioned restrictions, data is downloaded from Packagist to display the current released versions.
See result for symfony/symfony:^3.1.

Tip # 20: Use an authoritarian class map in production


Generate an authoritarian class map in production . This will speed up the loading of classes by including everything you need in the card and skipping any file system checks.

You can do this as part of your working build:

composer dump-autoload --classmap-authoritative

Tip # 21: configure autoload-dev for testing


You do not need to include test files in the working class map (due to file size and memory consumption). This can be done using configuration autoload-dev(similar autoload):

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

Also popular now: