📜 ⬆️ ⬇️

21 tips to make better use of Composer


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:

  1. Create a new thread.
  2. Update the composer.json version of the dependencies to the latest.
  3. Run composer update phpunit/phpunit --with-dependencies (replace phpunit/phpunit name of the updated library).
  4. Check out CHANGELOG in the library repository on GitHub to see if there are any breaking changes. If there is, update the application.
  5. Test the application locally. If you are using symfony, you can find deprecated warnings in the debug panel.
  6. Check out the changes (composer.json, composer.lock and everything you need for the new version to work).
  7. Wait until the end of the CI assembly.
  8. 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/" } }, 

Source: https://habr.com/ru/post/346488/


All Articles