Although most PHP developers can use Composer, not everyone does it efficiently or in the best possible way. Therefore, I decided to collect tips that are important for my daily work. Most of them rely on the principle “From sin away”: 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 great, and a few hours of reading will save you a lot of time in the long run. You'd be surprised how much Composer can do.
Tip # 2: Distinguish Project and Library
It is important to know that you are creating a project or 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/orm
or
elasticsearch/elasticsearch
.
')
A project is usually an application dependent on several libraries. Usually it is not used several times (no other project will need it as a dependency). Typical examples are an online store site, a user support system, etc.
Further in the councils I will switch between the library and the project.
Tip # 3: Use specific dependency versions for your application.
If you are creating an application, use as specific a version number as possible to determine the dependency. If you need to analyze YAML files, then define a dependency, for example, like this:
"symfony/yaml": "4.0.2"
.
Even if the library follows the rules of semantic versioning (Semantic Versioning), backward compatibility may still occur in minor and patch versions. Suppose if you use
"symfony/symfony": "^3.1"
, then something obsolete in 3.2 will break your tests. Or in PHP_CodeSniffer there will be a fixed bug, and new formatting errors will be found in your application, which can again lead to a broken build.
Update dependencies deliberately, not impulsively. We'll talk more about this in one of the following tips.
It may seem excessive, but attention to dependency versions will not allow your colleagues to recklessly update all dependencies when adding a new library to a project (which you might have missed when revising the code).
Tip # 4: Use version ranges for library dependencies.
If you are making a library, then determine the most possible range of versions. If you create a library that uses the
symfony/yaml
for YAML parsing, then request it as follows:
"symfony/yaml": "^3.0 || ^4.0"
Then your library will be able to use
symfony/yaml
from any versions of symfony from 3.x to 4.x. This is important because this restriction also applies to the application that accesses your library.
If there are two libraries with conflicting requirements (one, for example, you need ~ 3.1.0, and the other ~ 3.2.0), then installation will fail.
Tip number 5: in applications you need kommitit composer.lock in Git
If you are creating a
project , you need to commit composer.lock in Git. Then everyone - you, your colleagues, your CI server and the working server - will use the application with the same dependency versions.
At first glance, this advice seems superfluous. You have already chosen a specific version, as in council number 3. But there are dependencies of your dependencies that are not bound by these restrictions (for example,
symfony/console
depends on
symfony/polyfill-mbstring
). So without composer.lock, you will not get the same set of dependencies.
Tip number 6: in libraries put composer.lock in .gitignore
If you create a
library (let's call it
acme/my-library
), then you do not need to commit the composer.lock file. This
does not affect projects using your library.
Suppose
acme/my-library
uses a
monolog/monolog
as a dependency. If you commit composer.lock, then everyone who is developing
acme/my-library
will use an older version of Monolog. But when you finish work 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 earlier you didn’t notice this because of composer.lock!
It’s best to put composer.lock in .gitignore so you don’t accidentally commit it.
If you want to be sure that the library is compatible with different versions of its dependencies, read the following tip!
Tip # 7: Run Travis CI builds with different dependency versions.
The advice only applies to libraries (because for applications you use specific versions).
If you build an open-source library, you probably run builds using Travis CI. By default, Composer installs the last possible dependency versions allowed by constraints in composer.json. This means that to limit the dependency
^3.0 || ^4.0
^3.0 || ^4.0
build will always use the latest version of v4 release. And since version 3.0 has never been tested, the library may be incompatible with it, which will sadden users.
Fortunately, Composer has a
--prefer-lowest
to install the oldest dependencies possible (it should be used with
--prefer-stable
to prevent installations of unstable versions).
The updated
.travis.yml
configuration might 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
mhujer/fio-api-php
library and the
Travis CI matrix assembly .
Although this solution will catch most incompatibilities, remember that there are many dependency combinations between the older and younger versions. And they may be incompatible.
Tip # 8: Sort the packages into require and require-dev by name.
A good habit is to keep packages in
require
and
require-dev
sorted by name. This will help prevent unnecessary merge conflicts when relocating a branch. Because if you add a package at the end of the list in two branches, then merge conflicts will occur every time.
Manually doing this is tedious, so it’s best to
configure in composer.json:
{ ... "config": { "sort-packages": true }, … }
When you next
require
(
require
) a new package, it will be added to the right place (not at the end).
Tip # 9: Do not attempt to merge composer.lock when relocating or merging
If you added a new dependency to composer.json (and composer.lock) and another dependency was added to the wizard before merging the branch, you need to relocate the branch. And you get a merge conflict in composer.lock.
Never try to resolve it manually, because the composer.lock file contains the dependency hash defined in composer.json. So, even if you resolve the conflict, you get an incorrect lock-file.
It is better to create in the root of the project .gitattributes 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 feature branches, as suggested in
Trunk Based Development (this should be done anyway). If you have a correctly integrated short-term branch, the risk of a 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 composer.lock has a merge conflict when relocating? Allow it using the version from the wizard, so you will only have to change to composer.json (the newly added package). And then run
composer update --lock
, which wants to update the composer.lock file with changes from composer.json. You can now upgrade the updated composer.lock and continue relocating.
Tip # 10: Remember the difference between require and require-dev.
It is important to remember the difference between the
require
and
require-dev
blocks.
Packages needed to run an application or library must be defined in
require
(for example, symfony, Doctrine, Twig, Guzzle ...). If you create a library, then be careful what you put in
require
. Each dependency in this section is also a dependency of the application using the library.
Packages required for developing an application or library must be specified in
require-dev
(for example, PHPUnit, PHP_CodeSniffer, PHPStan).
Tip # 11: Update dependencies safely.
I think you agree with the statement that dependencies need to be updated regularly. And I advise you to update dependencies transparently and thoughtfully, and not as far as some other work. If you refactor something and at the same time update some libraries, you will not be able to tell if the application is broken, refactoring or updating.
Use the
composer outdated
to see which dependencies can be updated. You can also include
--direct
(or
-D
) to display only the dependencies specified in composer.json. There is also a switch
-m
to display updates only minor versions.
For each outdated dependency, stick to a plan:
- Create a new thread.
- Update the composer.json version of the dependencies to the latest.
- Run
composer update phpunit/phpunit --with-dependencies
(replace phpunit/phpunit
name of the updated library). - Check out
CHANGELOG
in the library repository on GitHub to see if there are any breaking changes. If there is, update the application. - Test the application locally. If you are using symfony, you can find deprecated warnings in the debug panel.
- Check out the changes (composer.json, composer.lock and everything you need for the new version to work).
- Wait until the end of the CI assembly.
- Combine and expand.
It is sometimes advisable to update several dependencies at once, for example, when you update Doctrine or Symfony. Then it is better to list them in the update team:
composer update symfony/symfony symfony/monolog-bundle --with-dependencies
Or you can use a template to update all dependencies from a specific namespace:
composer update symfony/* --with-dependencies
I know it all looks tedious, but for sure you update dependencies on occasion, so it’s best to be safe.
It is possible to facilitate the work only in one thing: to update all dependencies of
require-dev
at once (if they do not require changes in the code, otherwise I suggest using separate branches to simplify code revision).
Tip number 12: you can define other types of dependencies in composer.json
In addition to defining libraries as dependencies, you can also define other things there.
For example, which PHP versions does the application / library support:
"require": { "php": "7.1.* || 7.2.*", },
Or what extensions are needed by the application / library. This is very useful if you are trying to place an application in a container or if your new colleague is setting up an 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 autocomplete and a number of checks when manually changing composer.json.
If you are using another IDE (or just a code editor), you can configure the validation of
its JSON schema .
Council number 15: define working versions of PHP in composer.json
If you, like me, sometimes like
locally running pre-release versions of PHP , you
run the risk of updating dependencies to non-production versions. Now I use PHP 7.2.0, that is, I can install libraries that will not work on 7.1. And since production uses 7.1, the installation will fail.
But there is no need to worry, there is an easy solution. Just define the working PHP versions in the
config
section of the composer.json file:
"config": { "platform": { "php": "7.1" } }
Do not be confused by the
require
section, which 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. 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 repository type, and Composer should determine the correct way to extract packages. For example, if you add a fork from GitHub, it will use its API to download the zip file instead of cloning the entire repository.
But with private installation, Gitlab is a bit more complicated. If you use vcs as the repository type, Composer will define 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 (my system uses SSH for cloning).
First I defined a
git
repository:
"repositories": [ { "type": "git", "url": "git@gitlab.mycompany.cz:package-namespace/package-name.git" } ]
And then used the package, as is usually done:
"require": { "package-namespace/package-name": "1.0.0" }
Tip # 17: How to temporarily use a fork from the fork with a bug fix.
If you found a bug in the 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 ​​combined and a fixed release appears).
This can be easily done using
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
path
as the repository type .
Tip # 18: Install prestissimo to speed up package installation.
The
hirak/prestissimo
speeds the installation of dependencies via 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 version restrictions.
Writing the correct versioning restrictions sometimes becomes a daunting task after reading the
documentation .
Fortunately, there is
Packagist Semver Checker , which allows you to check which versions comply with specific restrictions. Instead of simply analyzing the versioned restrictions, data is downloaded from Packagist to display the current released versions.
See the
result for
symfony/symfony:^3.1
.
Council number 20: use in the production authoritarian class map (class map)
Generate an
authoritative class map in production. This will speed up class loading by including everything you need into the map and skipping any file system checks.
You can do this as part of your build work:
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 by configuring
autoload-dev
(similar to
autoload
):
"autoload": { "psr-4": { "Acme\\": "src/" } }, "autoload-dev": { "psr-4": { "Acme\\": "tests/" } },