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.
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
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/consoleest 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,@Symfonyles 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`
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
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
La chaîne complète, PHP et front, le principe d'un outil par responsabilité et la commande make qa qui les enchaîne.
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.
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.