merveilleux des outils de qualité : celui où l’outil censé détecter les problèmes devient lui-même le principal problème de la journée.
Et avec Symfony, on analyse quoi exactement ?
Dans un projet Symfony, SonarQube peut rapidement se retrouver devant un joyeux mélange de contrôleurs, de services, d’entités Doctrine, de formulaires, de commandes, de migrations et de fichiers de configuration. Sans oublier le dossier var/, qui contient suffisamment de cache pour faire croire à SonarQube que votre application compte trois millions de lignes de code.
La première étape consiste donc à lui expliquer ce qu’il doit regarder. En général, le code intéressant se trouve dans src/, les tests dans tests/, et le reste ne doit pas venir polluer les résultats. Le cache Symfony, les logs, les dépendances de vendor/, les assets compilés et les migrations Doctrine ne sont pas vraiment les meilleurs endroits pour juger la qualité de votre logique métier.
sonar.projectKey=mon-projet-symfony sonar.projectName=Mon projet Symfony sonar.sourceEncoding=UTF-8 sonar.sources=src sonar.tests=tests sonar.test.inclusions=tests/**/*Test.php sonar.exclusions=\ vendor/**,\ var/**,\ public/build/**,\ assets/vendor/**,\ migrations/** sonar.php.coverage.reportPaths=var/coverage/clover.xml sonar.php.tests.reportPath=var/coverage/junit.xml
Avec cette configuration, SonarQube arrête de vous reprocher la qualité du JavaScript généré par Webpack Encore, du cache compilé par Symfony ou d’une migration Doctrine créée automatiquement il y a quatre ans. C’est déjà un bon début.
PHPUnit, Xdebug et la grande fabrique du fichier XML
SonarQube ne lance pas vos tests PHP à votre place. Il attend tranquillement qu’un autre outil fasse le travail, puis il vient lire le rapport en prenant un air concerné. Dans un projet Symfony, c’est généralement PHPUnit qui exécute les tests, avec Xdebug ou PCOV pour mesurer les lignes parcourues.
Il faut donc produire deux fichiers : un rapport d’exécution des tests et un rapport de couverture au format Clover. SonarQube récupère ensuite ces fichiers pendant son analyse.
mkdir -p var/coverage
XDEBUG_MODE=coverage php bin/phpunit \
--log-junit var/coverage/junit.xml \
--coverage-clover var/coverage/clover.xml
Sur le papier, c’est simple. Dans la pratique, vous oublierez probablement XDEBUG_MODE=coverage, PHPUnit produira bien un joli rapport de tests, mais aucune couverture. SonarQube affichera alors fièrement 0%, comme si personne n’avait jamais testé une seule ligne du projet.
Vous relancerez la pipeline, constaterez que Xdebug n’est pas installé dans l’image PHP du runner, reconstruirez l’image, relancerez encore la pipeline, puis découvrirez que le dossier var/coverage appartient à root. À ce stade, la qualité du code sera peut-être excellente, mais celle de votre humeur beaucoup moins.
Les entités Doctrine, championnes du code non couvert
SonarQube adore compter les getters et les setters. Votre entité Customer contient trente propriétés, trente getters, vingt-cinq setters et quelques méthodes de collection générées par Doctrine ? Parfait : voilà une centaine de lignes supplémentaires à couvrir.
Vous pouvez alors écrire des tests passionnants pour confirmer que setFirstname('Jean') suivi de getFirstname() retourne bien Jean. C’est rassurant. Sans ce test, personne n’aurait pu prévoir un tel comportement.
Le problème n’est pas que ces tests soient toujours inutiles. Une entité Symfony peut contenir de vraies règles métier, des transitions d’état, des calculs ou des contraintes importantes. Mais tester mécaniquement chaque accesseur uniquement pour faire grimper une jauge transforme rapidement la suite de tests en collection de formalités administratives.
Il est souvent plus intelligent de concentrer la couverture sur les services, les traitements métier, les validateurs, les gestionnaires de commandes et les parties réellement sensibles. Une méthode qui calcule un prix, applique une remise ou autorise un changement d’état mérite un test. Un getter qui retourne une chaîne de caractères peut probablement continuer sa vie sans escorte policière.
Les contrôleurs Symfony et le syndrome du couteau suisse
SonarQube devient beaucoup plus utile lorsqu’il tombe sur un contrôleur de 400 lignes qui récupère la requête, valide un formulaire, interroge Doctrine, calcule une remise, envoie un e-mail, génère un PDF et met à jour le stock. Là, pour une fois, ses avertissements ne sont pas uniquement là pour décorer le tableau de bord.
Une complexité cognitive trop élevée dans un contrôleur Symfony est souvent un bon indice : une partie de la logique devrait probablement vivre dans un service. Non pas pour satisfaire aveuglément SonarQube, mais parce qu’un contrôleur qui gère la comptabilité, la logistique et la météo locale finira inévitablement par devenir impossible à tester.
#[Route('/commande/{id}/valider', methods: ['POST'])]
public function validate(
Order $order,
OrderValidator $orderValidator
): Response {
$orderValidator->validate($order);
return $this->redirectToRoute('admin_order_show', [
'id' => $order->getId(),
]);
}
Le contrôleur orchestre la requête, le service porte la logique, et les tests peuvent viser directement OrderValidator. Tout le monde est content : Symfony, PHPUnit, SonarQube, et surtout le développeur qui devra modifier cette fonctionnalité six mois plus tard sans invoquer un esprit ancien.
SonarQube, PHPStan et PHP-CS-Fixer : chacun son métier
Une erreur classique consiste à installer SonarQube et à considérer que tous les autres outils deviennent inutiles. Pourtant, il ne remplace ni PHPStan, ni PHP-CS-Fixer, ni les outils propres à Symfony.
PHPStan est souvent plus précis pour comprendre les types PHP, les génériques, les collections Doctrine et les retours complexes. PHP-CS-Fixer s’occupe du style et du formatage. PHPUnit vérifie le comportement. La commande symfony lsp:check peut détecter certains problèmes liés aux routes, aux templates et à la configuration Symfony. SonarQube, lui, rassemble des indicateurs, détecte certaines failles, mesure la duplication, suit le coverage et met tout cela dans une interface suffisamment colorée pour impressionner une réunion de pilotage.
Le plus cohérent est donc de laisser chaque outil travailler dans son domaine :
php bin/phpunit vendor/bin/phpstan analyse src tests vendor/bin/php-cs-fixer check --diff symfony lsp:check sonar-scanner
Cela peut sembler faire beaucoup de policiers pour quelques fichiers PHP. Mais leurs contrôles ne sont pas identiques. Le vrai danger serait plutôt de lancer cinq fois la même analyse avec cinq outils différents, puis de recevoir cinq variantes du même avertissement sur une variable qui aurait pu s’appeler autrement.
Le Quality Gate, ce videur à l’entrée de la production
Le Quality Gate décide si l’analyse est acceptable. Il peut bloquer une merge request lorsque le nouveau code contient une vulnérabilité, trop de duplication, une couverture insuffisante ou une quantité jugée excessive de mauvaises pratiques.
L’idée est bonne : empêcher la dette technique d’augmenter. Le piège, c’est d’appliquer des seuils irréalistes à un vieux projet Symfony qui transporte dix ans d’histoire, trois refontes avortées et un bundle abandonné par son auteur depuis 2018.
Exiger immédiatement 80% de couverture sur l’ensemble d’un projet legacy est le meilleur moyen de rendre SonarQube détestable en moins d’une semaine. L’équipe ne corrigera pas magiquement dix ans de dette technique. Elle ajoutera des exclusions, désactivera des règles et finira par ignorer complètement le tableau de bord.
Une approche plus intelligente consiste à surveiller principalement le nouveau code. L’ancien reste imparfait, mais chaque modification doit éviter d’aggraver la situation. C’est moins spectaculaire qu’une grande opération de nettoyage annoncée sur PowerPoint, mais beaucoup plus efficace sur la durée.
Les Security Hotspots, ou l’art de faire peur avec un mot de passe
Dans une application Symfony, SonarQube peut attirer l’attention sur les requêtes SQL construites manuellement, les algorithmes cryptographiques faibles, les mots de passe codés en dur, les entrées utilisateur mal filtrées ou certaines redirections dynamiques. Ces alertes méritent clairement d’être examinées.
Mais un Security Hotspot n’est pas automatiquement une faille. C’est un endroit que quelqu’un doit vérifier. SonarQube vous montre une boîte posée au milieu de la route et vous demande de confirmer qu’elle ne contient pas une bombe. Parfois, c’est une bombe. Parfois, c’est simplement un carton rempli de vieux câbles HDMI.
Le développeur doit donc examiner le contexte : la valeur vient-elle réellement d’un utilisateur ? Doctrine utilise-t-il des paramètres ? La route est-elle protégée par une autorisation Symfony ? Le secret signalé est-il une vraie clé ou simplement la chaîne password utilisée dans un test ?
Cliquer sur « Safe » sans rien lire ne sert à rien. Tout classer comme vulnérabilité critique non plus. La sécurité automatisée reste un assistant, pas un exorciste.
Le code dupliqué, ennemi public numéro un
SonarQube repère aussi les blocs de code similaires. Dans Symfony, cela arrive souvent avec des contrôleurs d’administration, des DTO, des formulaires ou des traitements d’import qui se ressemblent beaucoup.
La duplication peut signaler qu’une logique commune devrait être extraite dans un service. Mais il faut éviter de transformer trois lignes faciles à lire en une architecture abstraite composée de six interfaces, quatre factories et un resolver générique uniquement pour faire disparaître une tache orange du tableau de bord.
Deux morceaux de code identiques ne représentent pas toujours le même concept métier. Les fusionner artificiellement peut créer un couplage encore plus pénible que la duplication d’origine. La bonne question n’est donc pas seulement « est-ce que ces lignes se ressemblent ? », mais « est-ce qu’elles doivent évoluer ensemble ? ».
Une pipeline Symfony raisonnable
Dans une CI GitLab, l’ordre logique consiste à installer les dépendances, préparer l’environnement de test, lancer PHPUnit avec le coverage, puis exécuter le scanner SonarQube. Le rapport doit exister avant l’analyse, sinon SonarQube ne pourra pas l’inventer, même avec beaucoup de bonne volonté.
sonarqube:
stage: quality
variables:
APP_ENV: test
XDEBUG_MODE: coverage
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
script:
- composer install --no-interaction --prefer-dist
- mkdir -p var/coverage
- php bin/phpunit
--log-junit var/coverage/junit.xml
--coverage-clover var/coverage/clover.xml
- sonar-scanner
-Dsonar.projectKey=mon-projet-symfony
-Dsonar.sources=src
-Dsonar.tests=tests
-Dsonar.php.coverage.reportPaths=var/coverage/clover.xml
-Dsonar.php.tests.reportPath=var/coverage/junit.xml
Il faudra bien sûr adapter l’image Docker, l’URL du serveur, le token d’authentification et les services nécessaires au projet. Car rien ne dit « analyse statique du code » comme le besoin soudain de démarrer PostgreSQL, Redis, RabbitMQ et un faux serveur SMTP pour que le kernel Symfony accepte simplement de booter.
Alors, SonarQube, utile ou usine à gaz ?
Un peu des deux. Mal configuré, SonarQube devient une machine à produire des chiffres, des alertes inutiles et des discussions interminables sur la couverture d’un getter Doctrine. Bien configuré, il permet de suivre l’évolution d’un projet Symfony, de repérer des zones trop complexes, de surveiller la duplication et d’éviter que le nouveau code ajoute encore une couche de dette technique.
Il ne remplacera jamais une revue de code humaine. Il ne sait pas si votre architecture correspond au besoin du client, si une règle métier est absurde ou si votre service parfaitement testé calcule soigneusement le mauvais montant. Il sait compter, comparer, repérer certains motifs dangereux et lever le doigt lorsqu’un morceau de code commence à sentir le vendredi soir.
Le vrai objectif n’est donc pas d’obtenir un tableau de bord entièrement vert. On peut produire du code médiocre avec 100% de coverage, aucune duplication et toutes les règles SonarQube validées. Il suffit d’écrire des tests qui confirment parfaitement un comportement mal conçu.
SonarQube doit rester un outil d’aide à la décision. On écoute ses alertes, on examine leur contexte, on corrige ce qui apporte une vraie valeur et on classe proprement le reste. Le jour où l’équipe commence à modifier du code uniquement pour satisfaire une note, sans comprendre ce qu’elle améliore, le thermomètre a officiellement pris le contrôle de l’hôpital.
Sur un projet Symfony, la meilleure configuration n’est pas celle qui active toutes les règles et exige 100% partout. C’est celle qui bloque les vraies régressions, surveille le nouveau code et laisse les développeurs se concentrer sur ce qui compte : livrer une application fiable, maintenable et compréhensible. Si, en plus, le tableau de bord est vert, tant mieux. Mais il ne faut pas oublier qu’un feu vert indique seulement que vous avez l’autorisation d’avancer. Il ne garantit absolument pas qu’il y ait encore une route devant vous.