PHP-CS-Fixer : le tyran du format qui va te faire aimer la discipline

Temps de lecture : 2 min

Transformez votre code PHP chaotique en chef-d'œuvre cohérent sans effort avec PHP-CS-Fixer. Disciplinez votre syntaxe maintenant!

Tu connais cette sensation quand tu ouvres un projet PHP et que tu te retrouves face à 15 styles de code différents ? Ici un mec qui met ses accolades sur la même ligne, là un autre qui indente avec 2 espaces, et là-bas quelqu'un qui a décidé que mettre 17 lignes vides consécutives c'était "plus aéré". C'est le bordel, et ça me fout la gerbe.

Alors oui, on peut débattre pendant des heures pour savoir si la parenthèse ouvrante doit être sur la même ligne ou sur une nouvelle ligne. On peut se battre sur l'utilisation des tabs versus les espaces. On peut même s'engueuler sur la présence d'un espace avant la virgule ou pas.

Mais tu sais quoi ? On s'en tamponne les paupières avec des pelles à tartes. Ce qui compte, c'est la cohérence. Et c'est exactement ce que PHP-CS-Fixer va t'apporter.

C'est quoi PHP-CS-Fixer au juste ?

PHP-CS-Fixer, c'est comme avoir un designer d'intérieur obsessionnel-compulsif qui réorganise ton appartement pendant que t'es pas là. Sauf que là, c'est ton code qu'il range.

C'est un outil qui va inspecter ton code PHP et le reformater selon un ensemble de règles prédéfinies ou personnalisées. Il va standardiser les indentations, les espaces, les retours à la ligne, les commentaires, et tout un tas d'autres trucs dont tu n'avais même pas conscience qu'ils pouvaient être standardisés.

En gros, c'est comme si quelqu'un suivait derrière toi avec un balai, une serpillière et un chiffon à poussière pour nettoyer la merde que tu laisses dans ton code.

Mon fichier de config

Voilà ma config PHP-CS-Fixer, parce que j'ai bon cœur :

<?php

declare(strict_types=1);

use PhpCsFixer\Config;
use PhpCsFixer\Finder;
use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;

$header = <<<'EOF'
Ce fichier fait partie de Lecodeestdanslepre

(c) Lecodeestdanslepre <https://lecodeestdanslepre.fr>

Ce fichier source est soumis à la licence MIT incluse
avec ce code source dans le fichier LICENSE.
EOF;

$projectRoot = __DIR__;

$finder = Finder::create()
    ->in($projectRoot)
    ->exclude([
        'var',
        'vendor',
        'node_modules',
        'public/build',
    ])
;

return (new Config())
    ->setRules([
        'header_comment' => ['header' => $header],
        // Règles Symfony
        '@Symfony' => true,
        '@Symfony:risky' => true,

        // Modifications ciblées pour éviter les conflits avec Rector/PHPStan/Psalm
        'protected_to_private' => false,
        'nullable_type_declaration' => true,
        'trailing_comma_in_multiline' => [
            'elements' => [
                'arrays',
                'match',
                'parameters',
                'arguments',
            ],
        ],

        // Style de code cohérent avec Symfony 7
        'phpdoc_to_comment' => false,
        'yoda_style' => false,
        'declare_strict_types' => true,
        'global_namespace_import' => [
            'import_classes' => false,
            'import_constants' => true,
            'import_functions' => false,
        ],
        'ordered_imports' => [
            'imports_order' => ['class', 'function', 'const'],
            'sort_algorithm' => 'alpha',
        ],
        'no_unused_imports' => true,
        'fully_qualified_strict_types' => true,
        'native_function_invocation' => false,
        'no_superfluous_phpdoc_tags' => [
            'allow_mixed' => true,
            'remove_inheritdoc' => true,
        ],
        'single_line_throw' => false,
    ])
    ->setRiskyAllowed(true)
    ->setFinder($finder)
    ->setCacheFile($projectRoot . '.php-cs-fixer.cache')
    ->setParallelConfig(ParallelConfigFactory::detect())
    ;

Le Header, parce-que c'est important de marquer son territoire

$header = <<<'EOF'
Ce fichier fait partie de Lecodeestdanslepre

(c) Lecodeestdanslepre <https://lecodeestdanslepre.fr>

Ce fichier source est soumis à la licence MIT incluse
avec ce code source dans le fichier LICENSE.
EOF;

Ça, c'est le Header qui va être mis en haut de chaque fichier. C'est comme pisser sur un arbre pour dire "c'est mon code". Ça sert à rien techniquement, mais ça fait plaisir.

Le Finder, pour dire où cherche et où ne pas mettre son nez

$finder = Finder::create()
    ->in($projectRoot)
    ->exclude([
        'var',
        'vendor',
        'node_modules',
        'public/build',
    ])
;

Le Finder, c'est comme quand tu dis à ton chat : "Va dans ton panier, mais monte pas sur le clavier de mon ordinateur". Tu lui indiques où il doit faire le ménage (in($projectRoot)) et où il doit pas foutre les pieds (exclude([...).

Ici, on évite de formater le code des dépendances (vendor, node_modules), les fichiers générés (var) et les assets compilés (public/build). Parce que si tu commences à reformater les libs externes, t'es bon pour passer ta vie à réappliquer les mêmes fixes à chaque composer update.

Les règles, parce-que la liberté c'est l'anarchie

->setRules([
    'header_comment' => ['header' => $header],
    // Règles Symfony
    '@Symfony' => true,
    '@Symfony:risky' => true,
    // ...
])

Les règles, c'est le cœur du truc. C'est là que tu définis comment ton code doit être formaté. Et crois-moi, y'en a des tonnes.

Les preset Symfony

'@Symfony' => true,
'@Symfony:risky' => true,

Là, on dit "Applique toutes les règles de Symfony". Parce que les gars de Symfony, ils ont déjà passé des heures à débattre de ces conneries, donc autant réutiliser leur travail. Le :risky c'est pour les règles qui peuvent potentiellement changer le comportement du code, mais bon, on vit dangereusement ou on vit pas.

Les règles personnalisées

// Modifications ciblées pour éviter les conflits avec Rector/PHPStan/Psalm
'protected_to_private' => false,

Ici, on dit "Ne transforme pas les propriétés protected en private" parce que Rector et PHPStan peuvent avoir des avis différents, et on veut pas que ces outils jouent au ping-pong avec notre code.

'trailing_comma_in_multiline' => [
    'elements' => [
        'arrays',
        'match',
        'parameters',
        'arguments',
    ],
],

Ça, c'est pour les virgules à la fin des listes. Par exemple :

$array = [
    'item1',
    'item2',  // <- Cette virgule à la fin
];

C'est plus propre dans les diffs git, et ça évite d'avoir à modifier deux lignes quand t'ajoutes un élément.

'yoda_style' => false,

Parce que if ($variable === 42) est plus lisible que if (42 === $variable). Désolé, Maître Yoda, mais ton style de code, en 2025, ridicule il est.

'declare_strict_types' => true,

Parce que PHP sans types stricts, c'est comme conduire sans frein à main : ça marche, mais dès que t'as une pente un peu raide, t'es dans la merde.

'ordered_imports' => [
    'imports_order' => ['class', 'function', 'const'],
    'sort_algorithm' => 'alpha',
],

Pour trier tes imports par ordre alphabétique et par type. Parce que chercher une classe importée au milieu de 50 lignes non triées, c'est comme chercher une aiguille dans une botte de foin.

Les options finales, pour les finitions

->setRiskyAllowed(true)
->setFinder($finder)
->setCacheFile($projectRoot . '.php-cs-fixer.cache')
->setParallelConfig(ParallelConfigFactory::detect())

On active les règles risquées, on utilise le Finder qu'on a défini plus haut, on met en cache les résultats (pour pas avoir à tout rescanner à chaque fois), et on active le parallélisme pour que ça aille plus vite. Parce que le temps, c'est de l'argent, bordel.

Comment utiliser PHP-CS-Fixer

Installation

D'abord, installe-le :

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

Configuration

Crée un fichier .php-cs-fixer.dist.php à la racine de ton projet avec la config que je t'ai donnée plus haut (ou la tienne, si t'es un rebel).

Exécution

Pour voir ce que PHP-CS-Fixer va changer sans qu'il touche à ton code :

vendor/bin/php-cs-fixer fix --dry-run --diff

Et quand t'es prêt à faire les changements pour de vrai :

vendor/bin/php-cs-fixer fix

Dans mon projet, j'ai intégré PHP-CS-Fixer à mon Makefile pour pouvoir l'exécuter facilement :

fix-php: ## Fix PHP files locally with php-cs-fixer (ignore PHP version warning)
	@docker run -v $(PWD):/app -w /app -t --rm -e PHP_CS_FIXER_IGNORE_ENV=1 php:8.4-cli-alpine php -d memory_limit=-1 ./vendor/bin/php-cs-fixer fix src tests --config=.php-cs-fixer.dist.php $(PHP_CS_FIXER_ARGS)
	@docker run -v $(PWD):/app -w /app -t --rm php:8.4-cli-alpine php -d memory_limit=-1 ./vendor/bin/rector process --config=rector.php --clear-cache

Les règles les plus utiles

PHP-CS-Fixer a plus de 250 règles disponibles. C'est comme un buffet à volonté, sauf que là, c'est pas de la bouffe, c'est des règles de formatage. Voici quelques-unes des plus utiles :

Intégration à Git

Tu connais les développeurs, ils oublient. Ils oublient d'exécuter les tests, ils oublient de mettre à jour la doc, et ils vont certainement oublier d'exécuter PHP-CS-Fixer.

La solution ? Git pre-commit hooks.

Installe par exemple husky, puis configure-le pour exécuter PHP-CS-Fixer automatiquement avant chaque commit.

Ou, si t'es un peu plus old-school, crée un fichier .git/hooks/pre-commit :

#!/bin/sh
FILES=$(git diff --cached --name-only --diff-filter=ACMR "*.php" | sed 's| |\\ |g')
[ -z "$FILES" ] && exit 0

# Formatage du code
echo "PHP CS Fixer pre-commit hook début..."
echo "$FILES" | xargs vendor/bin/php-cs-fixer fix --quiet
echo "$FILES" | xargs git add

echo "PHP CS Fixer pre-commit hook terminé !"
exit 0

N'oublie pas de le rendre exécutable :

chmod +x .git/hooks/pre-commit

Les erreurs fréquentes (que tu vas faire quand même)

  1. "J'ai lancé PHP-CS-Fixer sur vendor/" : Bravo Einstein, t'as tenté de reformater le code des autres. Maintenant, supprime ton dossier vendor et refais un composer install.

  2. "J'ai commité les changements de PHP-CS-Fixer dans la même PR que mes fonctionnalités" : Et maintenant, ton diff a 200 fichiers modifiés, dont 198 sont juste des modifications de formatage. Bon courage pour la review.

  3. "J'ai créé un conflit de configuration entre PHP-CS-Fixer et Rector/PHPStan" : Un outil veut les propriétés protected, l'autre les veut private. Résultat : tes outils jouent au ping-pong avec ton code. Choisis ton camp.

  4. "J'ai lancé PHP-CS-Fixer sans cache" : Et maintenant, il refixe tout à chaque fois, même les fichiers qui n'ont pas changé. Bravo pour la performance.

Pourquoi c'est important

Tu te demandes peut-être pourquoi se faire chier avec tout ça? Voici pourquoi :

  1. La cohérence : Un codebase cohérent est plus facile à lire et à maintenir. C'est comme si tous les panneaux de signalisation utilisaient la même police et le même code couleur.

  2. Les revues de code : Quand tout le monde utilise le même style, les revues de code se concentrent sur ce qui compte vraiment : la logique, la sécurité, la performance. Pas sur "t'as mis un espace de trop après la virgule".

  3. L'intégration des nouveaux : Un nouveau développeur qui arrive sur ton projet peut comprendre le code plus rapidement si le style est cohérent.

  4. Les conflits inutiles : Fini les discussions interminables sur "Est-ce qu'on met un espace avant l'accolade?" ou "Est-ce qu'on indente avec des tabs ou des espaces?". La réponse est dans la config.

  5. La détection des vrais problèmes : Quand tous les détails de formatage sont gérés automatiquement, les anomalies de style qui restent sont souvent des signes de problèmes plus profonds.

Le mot de la fin

PHP-CS-Fixer, c'est un peu comme faire le ménage : personne n'aime ça, mais tout le monde aime quand c'est propre.

Ça prend 10 minutes à configurer, et ça te sauve des heures de débats stériles et de modifications manuelles. C'est un no-brainer.

Il fera son travail en silence, et ton code sera beau, cohérent, et professionnel. Et ça, c'est pas rien.