TECHNOLOGIES / PHP CS Fixer

PHP CS Fixer : le formateur de notre code PHP

PHP CS Fixer applique à tout notre code PHP le ruleset @Symfony, celui du framework, et corrige au lieu de signaler. La forme du code sort de la revue et de la discussion : elle est décidée une fois, appliquée par l'outil.

Cette fiche documente notre configuration, pourquoi il a remplacé PHP_CodeSniffer, et sa place dans la chaîne de conventions de code à côté de PHPStan et Psalm.

PRÉSENTATION

Qu'est-ce que PHP CS Fixer ?

PHP CS Fixer est un outil open source, né chez SensioLabs et maintenu par la communauté PHP, qui corrige la forme du code pour la rendre conforme à un standard : PSR, PER Coding Style, ou les conventions d'un framework comme Symfony.

Il lit chaque fichier, applique une liste de règles, et réécrit ce qui s'écarte : indentation, espaces, guillemets, ordre des imports, forme des commentaires de documentation. Le code qu'il produit est identique en comportement, homogène en apparence.

Chez SmartBooster, c'est le seul outil qui décide de la forme du code PHP. Le fond est vérifié par PHPStan, la sécurité par Psalm.

Qu'est-ce que PHP CS Fixer ?

POURQUOI PHP CS FIXER

Ce qui en fait notre outil de forme

PHP CS Fixer tourne sur tous nos projets Symfony grâce à notre standard-bundle. C'est la première brique de notre démarche qualité : celle qui coûte le moins et qui retire le plus de bruit de la revue de code.

Il corrige, il ne se contente pas de signaler

php-cs-fixer fix réécrit le fichier. Une règle de forme n'a aucune raison de rester une liste d'erreurs à traiter à la main : l'outil applique la décision, le développeur n'y pense plus.

Le ruleset @Symfony, natif

C'est l'outil que Symfony recommande pour ses propres standards, et le ruleset est maintenu par le projet. Nos conventions sont celles du framework, pas une liste maison à défendre.

PER Coding Style, pas seulement PSR-12

Le ruleset couvre PER Coding Style 3.0, le standard qui succède à PSR-12 et suit les nouveautés du langage : enums, propriétés promues, match. Un standard figé en 2019 ne dit rien de ces syntaxes.

Un seul outil pour la forme

Nous avons longtemps fait tourner PHP_CodeSniffer et PHP CS Fixer côte à côte, sur le même sujet. Deux outils qui vérifient la même chose finissent par se contredire, et l'équipe corrige l'un pour faire échouer l'autre. Il n'en reste qu'un.

Une migration règle par règle

Sur un projet existant, chaque règle en défaut est désactivée puis réactivée une par une, un commit par règle. L'historique reste lisible, la revue reste possible, et le projet avance à son rythme vers le ruleset complet.

En CI, il vérifie ; en local, il corrige

make cs lance check : la CI échoue si un fichier n'est pas conforme, sans rien modifier. make cs-fix corrige sur le poste du développeur. Le dépôt ne reçoit jamais de commit de formatage écrit par la machine.

NOTRE USAGE

Comment nous configurons PHP CS Fixer

Notre configuration tient en une vingtaine de lignes. Elle est reproduite à l'identique sur chaque projet par la recette du standard-bundle, et chaque écart au ruleset est commenté dans le fichier.

Deux écarts, et pas un de plus

Notre .php-cs-fixer.dist.php pose @Symfony et ajoute deux règles : class_definition avec multi_line_extends_each_single_line, pour lire une classe qui implémente plusieurs interfaces, et concat_space à one, parce que PER conserve l'espace autour du point de concaténation.

Les règles risquées sont autorisées

setRiskyAllowed(true) : une règle risquée peut changer le comportement du code, pas seulement sa forme. Nous l'acceptons parce que PHPStan et les tests tournent derrière, dans la même chaîne de qualimétrie. Un changement de comportement ne passerait pas inaperçu.

Un périmètre explicite

Le finder cible bin/, config/, public/, src/ et tests/, et exclut var/, vendor/ et node_modules/. Le code d'un tiers ne se formate pas, il se met à jour.

Quatre commandes, livrées par le standard-bundle

make cs vérifie, make cs-diff montre ce qui changerait, make cs-fix corrige, make cs-report compte les violations par règle. La première fait partie de make qa. Configuration et cibles arrivent par un recipe flex du standard-bundle.

LE CHOIX

Pourquoi nous avons retiré PHP_CodeSniffer

Nous avons utilisé PHP_CodeSniffer pendant des années, et il a été retiré du standard-bundle en juillet 2026. Pas parce qu'il était mauvais : parce qu'il portait deux responsabilités, et que chacune avait trouvé un meilleur outil.

Ce que faisait notre phpcs.xml

Deux choses sans rapport. Les sniffs de sécurité de pheromone/phpcs-security-audit, et le ruleset PSR-12 pour la forme. Or PHP CS Fixer était déjà là, depuis la version 1.2 du bundle, pour le ruleset Symfony. La forme était vérifiée deux fois.

La sécurité a trouvé un meilleur outil

Les sniffs de sécurité n'avaient plus de version depuis 2019 et détectaient par motif, avec beaucoup de faux positifs. Psalm en analyse de teinte suit le flux de la donnée jusqu'à la requête, et PHPStan bannit les fonctions dangereuses. Chaque sniff a été repris, un par un.

Il ne restait qu'un doublon

La sécurité partie, phpcs.xml ne portait plus que PSR-12, déjà couvert. Nous avons gardé PHP CS Fixer pour PER 3.0 et le ruleset Symfony, que PHP_CodeSniffer n'a pas nativement. La décision est un ADR public, daté du 9 juillet 2026.

BONNES PRATIQUES & DOCUMENTATION

Nos conventions PHP CS Fixer

Documentation de référence pour l'équipe SmartBooster.

Notre configuration

Le fichier `.php-cs-fixer.dist.php` livré par le standard-bundle

Le fichier est copié dans chaque projet par la recette Symfony Flex du standard-bundle. Il tient en deux parties : le périmètre, et les règles.

$finder = (new PhpCsFixer\Finder())
    ->in(__DIR__)
    ->exclude('var')
    ->exclude('vendor')
    ->exclude('node_modules')
    ->append([
        'bin/console',
    ])
    ->path([
        'bin/',
        'config/',
        'public/',
        'src/',
        'tests/',
    ])
;

return (new PhpCsFixer\Config())
    ->setRiskyAllowed(true)
    ->setRules([
        '@Symfony' => true,
        'class_definition' => [
            'multi_line_extends_each_single_line' => true, # https://cs.symfony.com/doc/rules/class_notation/class_definition.html#example-4
        ],
        'concat_space' => ['spacing' => 'one'], # the @PER like PSR12 preserve space
    ])
    ->setFinder($finder)
;
  • ->path([...]) : seuls les répertoires listés sont formatés. bin/console est ajouté explicitement parce qu'il n'a pas d'extension .php.
  • setRiskyAllowed(true) : autorise les règles qui peuvent changer le comportement du code. PHPStan et les tests, dans la même chaîne, servent de filet.
  • @Symfony : le ruleset du framework, qui inclut PER Coding Style et donc PSR-12.
  • class_definition : une classe qui implémente plusieurs interfaces les écrit une par ligne, ce qui rend les diffs lisibles quand on en ajoute une.
  • concat_space : un espace autour du . de concaténation, comme PER le prévoit. Sans cette ligne, @Symfony les retire.

Toute règle ajoutée ici doit porter son motif en commentaire, sur la même ligne. Une règle sans motif est retirée à la revue.

Les commandes

Les cibles Make de `qualimetry.mk`

Commande Ce qu'elle fait Quand
make cs php-cs-fixer check -v : liste les fichiers non conformes, ne modifie rien Avant de pousser, et en CI via make qa
make cs-diff Même chose avec le diff de ce qui serait corrigé Pour comprendre une violation
make cs-fix php-cs-fixer fix -v --diff : corrige les fichiers En local, avant de commiter
make cs-report Écrit un rapport JSON des violations Pour compter les règles en défaut sur un projet existant

Le rapport de make cs-report se lit avec jq, hors du conteneur, pour obtenir le nombre de violations par règle :

cat php_cs_fixer_report.json | jq -r '.files[] | select(.appliedFixers != null) | .appliedFixers[]' | sort | uniq -c | sort -nr

C'est la liste de départ de la migration règle par règle.

Activer PHP CS Fixer sur un projet existant

Règle par règle, un commit à la fois

Un projet qui n'a jamais eu de formateur remonte des milliers de violations. Les corriger d'un coup produit un commit qui touche tous les fichiers, rend git blame inutilisable et bloque toutes les branches ouvertes. Nous procédons autrement.

1. Rendre la CI verte le jour même

Lancer make cs-report, lister les règles en défaut, et passer chacune à false dans .php-cs-fixer.dist.php :

->setRules([
    '@Symfony' => true,
    'phpdoc_tag_type' => false,
    'phpdoc_indent' => false,
    'single_quote' => false,
    // ...
])

Le ruleset est complet, les règles qui échouent sont suspendues, la chaîne de qualimétrie passe. Tout code nouveau est déjà contrôlé par les règles restées actives.

2. Réactiver les règles une par une

Retirer une ligne => false, lancer make cs-fix, relire le diff, commiter. Une règle par commit, avec le nom de la règle dans le message. Commencer par celles qui touchent peu de fichiers : le rapport de l'étape 1 donne l'ordre.

Chaque commit est relisible en revue, et l'historique dit exactement quand chaque convention est entrée en vigueur.

3. Dans le flux de la maintenance technique

Cette résorption n'est pas un chantier à part : elle entre dans le flux non prioritaire de la maintenance technique, le travail mené en autonomie entre deux demandes. Une session prend quelques règles, les réactive, et le projet rejoint progressivement le ruleset complet sans jamais bloquer une livraison.

Le même principe s'applique à la baseline de PHPStan et à celle de Psalm : voir la page conventions de code.

FAQ

Les réponses à vos questions

Et si vous ne trouvez pas ce que vous cherchez, nous serons ravis de vous répondre en direct lors d'un rendez-vous entre humains !

Non, et c'est important de le dire. Le dépôt d'origine, squizlabs, a cessé d'être maintenu, mais le projet a été repris par PHPCSStandards et publie régulièrement. Nous ne l'avons pas quitté parce qu'il était mort : nous l'avons quitté parce qu'il faisait deux fois le même travail que PHP CS Fixer, qui couvre en plus PER Coding Style 3.0 et le ruleset Symfony. Un projet qui n'a que PHP_CodeSniffer et PSR-12 n'a pas de problème, il a juste un outil qui ne corrige pas.

PHP_CodeSniffer est un détecteur : phpcs liste les écarts, et phpcbf en corrige une partie. PHP CS Fixer est un correcteur : chaque règle sait réécrire le code qu'elle contrôle. Les deux lisent des standards PSR, mais PHP CS Fixer porte le ruleset @Symfony maintenu par le projet Symfony, que PHP_CodeSniffer n'a pas nativement. Sur le fond, ni l'un ni l'autre ne vérifie les types ou la logique : c'est le rôle de PHPStan.

@Symfony inclut @PER-CS, qui inclut PSR-12, et ajoute les conventions du framework : ordre des éléments d'une classe, forme des PHPDoc, imports. Nos projets sont des projets Symfony, autant écrire le code comme le framework écrit le sien : un développeur qui lit le code source de Symfony ou d'un bundle retrouve les mêmes conventions dans le nôtre.

Pas en formatant tout d'un coup. On lance make cs-report pour compter les violations par règle, on passe chaque règle en défaut à false dans la configuration, et la CI est verte le jour même. Ensuite, on réactive les règles une par une, un commit par règle, en commençant par celles qui touchent peu de fichiers. L'historique reste lisible et git blame continue de montrer l'auteur réel de chaque ligne. Cette résorption entre dans le flux de la maintenance technique, entre deux demandes.

Les règles non risquées, non : elles ne touchent qu'à la forme. Les règles risquées peuvent changer le comportement, par exemple en remplaçant une fonction par une autre ou en rendant une comparaison stricte. Nous les autorisons parce que PHPStan et la suite de tests tournent dans la même chaîne : un changement de comportement se voit avant la livraison. Sur un projet sans tests, on commence par les règles sûres.

Twig et YAML ont un lint de syntaxe dans la chaîne, via les commandes Symfony, mais pas de formateur : le besoin ne s'est pas présenté. Le code front, JavaScript et Vue.js, a le sien, Prettier, avec ESLint pour le fond. Même principe qu'en PHP, un outil par responsabilité, et la page ESLint en donne le détail.

Pour aller plus loin

Approfondir votre réflexion

Conventions de code

La chaîne complète, PHP et front, le principe d'un outil par responsabilité et la commande make qa qui les enchaîne.

ESLint et Prettier

Le même partage des rôles côté front : Prettier pour la forme, ESLint pour le fond, et pourquoi nous n'avons pas confié les deux à ESLint.

PHPStan

L'outil qui vérifie le fond, au niveau maximal, avec la liste des appels interdits qui a repris une partie des anciens sniffs de sécurité.

Vous avez un projet ?

Contactez-nous pour savoir comment nous pouvons vous aider.