Installing and using php-cs-fixer

Published on 2024-01-15 • Modified on 2024-01-15

In this post, we see different methods to install and use php-cs-fixer. We see that it's not as obvious as it seems. Let's go! 😎

Prerequisite

I assume you have at least a basic knowledge of PHP, Symfony and Composer.

Introduction

Php-cs-fixer is an excellent tool that allows you to fix the coding style of your PHP code automatically. It's trendy among the PHP community and has become one of the favourite dev tools of many PHP developers. However, installing it in a project can ask questions and lead to problems, as seen in the following chapters.

Goal

We review all the ways to install and use php-cs-fixer for a project and their pros and cons.

The problem

What? A blog post about installing php-cs-fixer? Are you kidding me? Isn't it as simple as running:

composer require --dev friendsofphp/php-cs-fixer

Let's check out.

Symfony 7.0 beta has been out for several days (I started to write this blog post at this moment). Let's try; we can create a new Symfony 7 project with the Symfony binary:

symfony new my_project_directory --version=7.0.x-dev

Now, let's install php-cs-fixer:

cd my_project_directory
composer require --dev friendsofphp/php-cs-fixer
./composer.json has been updated
Running composer update friendsofphp/php-cs-fixer
Loading composer repositories with package information
Restricting packages listed in "symfony/symfony" to "7.0.*"
Updating dependencies
Your requirements could not be resolved to an installable set of packages.

Problem 1
- friendsofphp/php-cs-fixer v0.1.0 requires symfony/console 2.1.* -> found symfony/console[v2.1.0, ..., 2.1.x-dev] but it conflicts with your root composer.json require (7.0.*).
- ...
- friendsofphp/php-cs-fixer v3.4.0 requires symfony/console ^4.4.20 || ^5.1.3 || ^6.0 -> found symfony/console[v4.4.20, ..., 4.4.x-dev, v5.1.3, ..., 5.4.x-dev, v6.0.0-BETA1, ..., 6.4.x-dev] but it conflicts with your root composer.json require (7.0.*).
- friendsofphp/php-cs-fixer[dev-release_notes_template, dev-master, v3.5.0, ..., v3.37.1] require symfony/console ^5.4 || ^6.0 -> found symfony/console[v5.4.0-BETA1, ..., 5.4.x-dev, v6.0.0-BETA1, ..., 6.4.x-dev] but it conflicts with your root composer.json require (7.0.*).
- Root composer.json requires friendsofphp/php-cs-fixer * -> satisfiable by friendsofphp/php-cs-fixer[dev-release_notes_template, dev-master, v0.1.0, ..., v0.5.7, v1.0, ..., v1.13.3, v2.0.0-alpha, ..., v2.19.3, v3.0.0-beta.1, ..., v3.37.1, 9999999-dev].

Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.
You can also try re-running composer require with an explicit version constraint, e.g. "composer require friendsofphp/php-cs-fixer:*" to figure out if any version is installable, or "composer require friendsofphp/php-cs-fixer:^2.1" if you know which you need.

Installation failed, reverting ./composer.json and ./composer.lock to their original content.

Boom πŸ’₯! It does not work; Composer can't resolve the dependencies as php-cs-fixer uses "symfony/console": "^5.4 || ^6.0" which can't be resolved when using Symfony 7.0 beta.

I know what you think: πŸ€”

β€œThis is a Symfony BETA version; that's why it does not work. ”

It seems a valid explanation, but it is wrong: the problem comes from the php-cs-fixer vendor, not Symfony. And we have absolutely no guarantee when it will be fixed. It could be fixed quickly, but you could also wait a long time, and perhaps worse, have to do a fork of the library itself.
PS: It is now fixed ! πŸŽ‰

It is a reminder that php-cs-fixer is a PHP library and has its dependencies. Let's look at its own composer.json file:

"require": {
    "php": "^7.4 || ^8.0",
    "ext-json": "*",
    "ext-tokenizer": "*",
    "composer/semver": "^3.3",
    "composer/xdebug-handler": "^3.0.3",
    "sebastian/diff": "^4.0 || ^5.0",
    "symfony/console": "^5.4 || ^6.0",
    "symfony/event-dispatcher": "^5.4 || ^6.0",
    "symfony/filesystem": "^5.4 || ^6.0",
    "symfony/finder": "^5.4 || ^6.0",
    "symfony/options-resolver": "^5.4 || ^6.0",
    "symfony/polyfill-mbstring": "^1.27",
    "symfony/polyfill-php80": "^1.27",
    "symfony/polyfill-php81": "^1.27",
    "symfony/process": "^5.4 || ^6.0",
    "symfony/stopwatch": "^5.4 || ^6.0"
},
"require-dev": {
    "facile-it/paraunit": "^1.3 || ^2.0",
    "justinrainbow/json-schema": "^5.2",
    "keradus/cli-executor": "^2.0",
    "mikey179/vfsstream": "^1.6.11",
    "php-coveralls/php-coveralls": "^2.5.3",
    "php-cs-fixer/accessible-object": "^1.1",
    "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2",
    "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1",
    "phpspec/prophecy": "^1.16",
    "phpspec/prophecy-phpunit": "^2.0",
    "phpunit/phpunit": "^9.5",
    "symfony/phpunit-bridge": "^6.2.3",
    "symfony/yaml": "^5.4 || ^6.0"
},

We see the Symfony component (symfony/console) that prevents the installation in the project. We also realise that php-cs-fixer comes with many other vendors, especially in the -dev section, as it is a tool.
So, yes, there are chances of having conflicts with your project. The more dependency you have, the more likely you can have dependency problems when wanting to install a new vendor. But of course, this is not specific to php-cs-fixer; we also have this problem with all other PHP libraries.

So, this method does not work for this shiny new Symfony 7 BETA project; let's see other methods and how we can fix this. But first, what about the Symfony community?

What do other developers do?

I was curious and wanted to know what the other Symfony developers use. I created a small poll on Symfony Slack . Even though there are not a lot of answers (30), we can see trends. Here is what I posted:

Hello everyone, quick poll: how do you install the wonderful php-cs-fixer library?

  • 1. In the `require-dev` section of the composer file of my project β€”> composer require --dev friendsofphp/php-cs-fixer
  • 2. In a dedicated composer.json file β€”> composer require --working-dir=tools/php-cs-fixer friendsofphp/php-cs-fixer
  • 3. With a global composer installation composer global require friendsofphp/php-cs-fixer
  • 4. With docker β€”> docker run --rm -v $(PWD):/data cytopia/php-cs-fixer fix --dry-run --diff --allow-risky=yes` ($PWD with fishshell)

The results were the following:


php-cs-fixer installation method poll
Generated with maxtables.com

So, installing php-cs-fixer in the require-dev section of the project is still most Symfony developers' favourite method. Let's try the other ones.

Method 2: use a dedicated composer.json file inside the project

I must admit I have not used this method (until now). What is the difference with the require-dev method? This time, we don't install the dependencies in the project but in a sub-directory. If we look at the php-cs-fixer documentation , this is even considered the best practice. Let's try:

mkdir -p tools/php-cs-fixer
composer require --working-dir=tools/php-cs-fixer friendsofphp/php-cs-fixer
/composer.json has been created
Running composer update friendsofphp/php-cs-fixer
Loading composer repositories with package information
Updating dependencies
Lock file operations: 25 installs, 0 updates, 0 removals
  - Locking composer/pcre (3.1.1)
  ...
  - Installing friendsofphp/php-cs-fixer (v3.38.0): Extracting archive
Generating autoload files
22 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
No security vulnerability advisories found.
Using version ^3.38 for friendsofphp/php-cs-fixer

As the tool is installed in its own directory, of course there is no possible conflict. And we can use it:

tools/php-cs-fixer/vendor/bin/php-cs-fixer fix src
Loaded config default.
Fixed 0 of 1 files in 0.007 seconds, 14.000 MB memory used

The MicroSymfony configuration file is a good start when creating a new rule set for your project. It is concise; I just put the essentials. Don't hesitate to use it and adapt it to your project. OK, we now have a working configuration for our Symfony project. Let's see the remaining methods.

Method 3: with a global composer installation

composer global require friendsofphp/php-cs-fixer

This method also avoids conflicts with your project and is not even included in it. Instead of using a relative path, you can add the php-cs-fixer in your path and then call:

php-cs-fixer fix src

I'm not too fond of this method because php-cs-fixer is disconnected from your project, but it works.

Method 4: with Docker

As we saw in the previous poll, it is not the most popular method but a very pragmatic approach. The goal is to use an existing Docker image containing all the required stuff to run php-cs-fixer. We can use, for example, the cytopia/docker-php-cs-fixer repository. We can run the fix task with the following Docker command:

docker run --rm -v ./:/data cytopia/php-cs-fixer fix --allow-risky=yes

It uses the last available image (currently version 3 for PHP 8.1). As this project contains multiple images, we can also use it to run php-cs-fixer on a legacy code base using PHP 7.4; in this case, we can run:

docker run --rm -v ./:/data cytopia/php-cs-fixer:3-php7.4 fix --allow-risky=yes

The pro is that we have nothing to install; we only need Docker. The con is that we don't control the image's content; as we can see, the last version available isn't the latest version of php-cs-fixer (currently 3.42).

docker run --rm -v ./:/data cytopia/php-cs-fixer:latest-php8.1 --version
PHP CS Fixer 3.17.0 (3f0ed86) Brazilian Kangaroo by Fabien Potencier and Dariusz Ruminski.
PHP runtime: 8.1.20

Method 5: with the composer bin plugin

One thing I like when I write a blog post is that you share your knowledge and learn. When I posted my article on Symfony Slack, tarlepp told me there was a composer plugin for this: bamarni/composer-bin-plugin . It exactly tackles the problem we have here. Let's see how it works. First, we must install the composer plugin:

composer require --dev bamarni/composer-bin-plugin

Then we can install php-cs-fixer with the new bin argument of composer:

composer bin php-cs-fixer require --dev friendsofphp/php-cs-fixer

It will automatically install php-cs-fixer in the vendor-bin directory. There are several options for this plugin:

    "extra": {
        "bamarni-bin": {
            "bin-links": true,
            "target-directory": "vendor-bin",
            "forward-command": true
        }
    },

Thanks to the bin-links option, the php-cs-fixer entry script still lives in the vendor/bin folder, which means the path to call the tool stays the same as the 1st method, and we have nothing to change in our scripts/Makefile. There is an extra setup in the composer post-install-cmd section, so the composer also installs the php-cs-fixer dependencies after those of the main project (the forward option is supposed to handle this; I must check why it doesn't work).

"post-install-cmd": [
    "@auto-scripts",
    "@composer bin php-cs-fixer install --ansi"
],

That's it. It's like the 2nd, method except the composer bin plugin handles all for you! I have made a PR on MicroSymfony to try, and it works well; the CI was βœ… on the 1st attempt.

Summary

We can summarize the pros and cons in the following table.

\ Method 1: In the `require-dev` section of the composer file of my project Method 2 (and 5): In a dedicated composer.json file Method 3: With a global composer installation Method 4: With Docker
Do not mess with project dependencies ❌ βœ… βœ… βœ…
Fast analysis βœ… βœ… βœ… ❌ (1)
Fine control of the php-cs-fixer version βœ… βœ… ❌ ❌
Still connected to the project βœ… βœ… ❌ ❌
No local installation required ❌ ❌ ❌ βœ…

(1) Using Docker has a slight performance impact at launch, but the analysis time difference seems insignificant. I'll do other more precise tests to confirm this. The problem is the version differences (24 minor versions late), so comparison isn't fair.

Conclusion

We saw different methods to install php-cs-fixer. I changed my mind; before, it was evident that installing this tool in the main composer of the project was the good practice and the way to go. Now, I think installing the tool in a separate directory with its own composer.json file is more safe and elegant (2nd and 5th methods). With the 5th method, we don't even need to modify the calling path of php-cs-fixer. We keep the project dependencies as concise as possible. Still, the tool is installed in the project repository, easy to update and fast; we have the best of both worlds.

That's it! I hope you like it. Check out the links below to have additional information related to the post. As always, feedback, likes and retweets are welcome. (see the box below) See you! COil. 😊

  Read the doc  More on the web

They gave feedback and helped me to fix errors and typos in this article; many thanks to tarlepp and jmsche . πŸ‘

  Work with me!


Call to action

Did you like this post? You can help me back in several ways: (use the Tweet on the right to comment or to contact me )

Thank you for reading! And see you soon on Strangebuzz! πŸ˜‰

COil