Guide

PHPStan et Rector en 2026 : moderniser une base PHP/Symfony legacy sans casser la prod

PHPStan 2.3.1 expose les incohérences de types qui fragilisent un code PHP historique, tandis que Rector 2.7.0 applique des transformations contrôlées vers une cible PHP ou Symfony. Ce guide explique comment introduire les deux outils par lots vérifiables, configurer l'analyse du conteneur Symfony et sécuriser chaque changement dans la CI.

PHPStan 2.3.1, publié le 8 octobre 2026, analyse les incohérences de types et les appels impossibles ; Rector 2.7.0, publié le 6 octobre 2026, transforme automatiquement le code selon des règles explicites. Sur une application PHP ou Symfony legacy, leur association permet d’abord de rendre les défauts visibles, puis de moderniser des portions ciblées sans confondre automatisation et mise en production aveugle.

Le bon ordre n’est pas négociable : établir un état de l’analyse, corriger les contrats les plus ambigus, appliquer Rector en petits lots, puis tester le résultat dans les conditions du projet. Cette discipline évite qu’une modernisation syntaxique masque un problème métier ou une dépendance implicite du code historique.

Développeur consultant un rapport d'analyse statique PHP sur un ordinateur portable dans un espace de travail
PHPStan rend les contrats implicites visibles avant que Rector ne modifie la structure du code.

PHPStan analyse, Rector transforme : deux responsabilités distinctes

PHPStan et Rector travaillent sur le code source, mais ils ne répondent pas à la même question. PHPStan cherche ce qui est incohérent dans les chemins de code qu’il peut raisonner : mauvais type transmis à une méthode, retour incompatible, propriété utilisée de manière incertaine ou appel sur une valeur potentiellement nulle. Il ne réécrit pas le dépôt : son résultat est un rapport à interpréter et à corriger.

Rector parcourt la structure syntaxique du code afin d’appliquer des transformations. Les règles peuvent remplacer des constructions anciennes, préparer une cible PHP ou accompagner une migration Symfony. Son rôle est donc mécanique, mais son résultat reste du code applicatif : il doit être revu, testé et livré avec la même exigence qu’une modification écrite à la main.

CritèrePHPStanRector
Rôle principalDétecter des incohérences statiquesRéécrire le code selon des règles
ProductionRapport d’erreurs et d’alertesDifférence de code à relire
Moment d’exécutionAvant chaque fusion et lors des montées de niveauEn préparation de lots de modernisation ou en vérification
Risque à maîtriserIgnorer une alerte pertinenteAppliquer une transformation sans valider le comportement

Rector complète une méthode de refactoring de projets PHP legacy, mais ne la remplace pas. Cette méthode doit conserver une cartographie des zones sensibles, des tests de non-régression et des décisions métier. Un outil peut modifier une syntaxe ; il ne peut pas décider qu’un ancien comportement fonctionnel doit disparaître.

Une séquence de travail fiable consiste à identifier une zone, formaliser son comportement par des tests lorsque c’est nécessaire, faire analyser cette zone par PHPStan, puis demander à Rector une proposition de changement. La revue porte alors sur une différence limitée et compréhensible, plutôt que sur un mélange de corrections de types, de migration framework et de nettoyage esthétique.

Installer et configurer PHPStan sur un périmètre utile

L’installation doit rester une dépendance de développement du projet. Commencer par analyser src et tests donne un périmètre cohérent : le code de production et les scénarios qui en décrivent l’usage progressent ensemble. Une configuration de niveau 8 convient à un dépôt qui a déjà absorbé une première phase de nettoyage ; elle peut être abaissée au départ si le volume d’erreurs empêche toute lecture utile.

composer require --dev phpstan/phpstan:^2.3
composer require --dev phpstan/phpstan-symfony
vendor/bin/phpstan analyse

Le fichier phpstan.neon doit être placé à la racine et évoluer avec le projet. Évitez les exclusions générales ajoutées sous pression : chaque exception durable réduit la valeur du rapport et doit être justifiée dans une revue.

parameters:
level: 8
paths:
    - src
    - tests

La configuration est volontairement courte. Elle constitue une base, pas un inventaire de tous les défauts historiques. Les options supplémentaires doivent répondre à un besoin observé : un répertoire généré, un bootstrap propre au projet ou une extension d’écosystème. La documentation officielle de PHPStan reste la référence pour vérifier la syntaxe et le comportement des paramètres selon la version utilisée.

Lancer l’analyse depuis la racine garantit que les chemins et les fichiers de configuration sont résolus de façon identique sur les postes locaux et dans la CI. Dans un dépôt Symfony, il est aussi préférable de ne pas assimiler le cache applicatif au code analysable : ce qui est généré doit être régénéré, non corrigé manuellement pour faire taire un analyseur.

Monter les niveaux sans submerger l'équipe

PHPStan propose les niveaux 0 à 10. Le niveau 0 cible les erreurs évidentes ; les niveaux suivants approfondissent progressivement la vérification des types, des retours et des appels, jusqu’au niveau 10, le plus strict. Une application ancienne qui passe directement à ce niveau maximal produit souvent un bruit tel que l’équipe perd le lien entre chaque signal et le risque réel.

Une progression réaliste démarre aux niveaux 5 ou 6. L’objectif initial est d’éliminer les erreurs qui révèlent des contrats manifestement faux, notamment les retours incohérents, les variables utilisées sans garantie suffisante et les appels dont les arguments ne correspondent pas à la signature attendue. Une fois le rapport stabilisé, viser le niveau 8 ou davantage devient un objectif de qualité vérifiable.

  • Montez d’un niveau lorsque les erreurs du niveau précédent sont traitées ou explicitement isolées.
  • Regroupez les corrections par domaine technique plutôt que par simple ordre d’apparition dans le rapport.
  • Ajoutez ou renforcez les types PHP et les PHPDoc lorsque le code exprime réellement un contrat stable.
  • Faites corriger les signaux dans les mêmes demandes de fusion que les changements fonctionnels concernés.
  • Réévaluez les suppressions temporaires à chaque amélioration de niveau.

Le dépôt rector-symfony configure lui-même PHPStan au niveau 8 dans son fichier phpstan.neon, visible sur GitHub. Ce choix n’est pas une obligation universelle, mais pour un écosystème qui manipule des règles de transformation et des contrats Symfony complexes, ce palier documenté offre un repère concret. Sur un legacy, la question utile est : « quelles garanties pouvons-nous maintenir à chaque fusion ? », non « quel niveau affichons-nous le plus vite ? ».

Une baseline peut absorber les dettes héritées lors de l’introduction de PHPStan. Elle fige les erreurs existantes pour permettre à la CI d’interdire les nouvelles. Elle ne doit toutefois pas devenir un cimetière permanent : traitez-la comme un stock identifié, à réduire par lots, et non comme un fichier que personne ne consulte.

Analyser le conteneur Symfony avec phpstan-symfony

Le conteneur de services est une source classique d’imprécision dans les applications Symfony historiques. Sans extension dédiée, un appel à ContainerInterface::get() laisse facilement l’analyseur avec un type générique, alors que le code attend un service très précis. Cette incertitude favorise les erreurs cachées : identifiant de service invalide, mauvaise classe récupérée ou méthode appelée sur une abstraction trop vague.

L’extension phpstan/phpstan-symfony fournit les types de retour corrects pour ContainerInterface::get() et has(). Elle détecte aussi les services inexistants ainsi que les types incorrects récupérés depuis le conteneur. Lorsqu’il est présent, phpstan/extension-installer enregistre automatiquement l’extension ; à défaut, incluez explicitement vendor/phpstan/phpstan-symfony/extension.neon dans votre configuration.

includes:
    - vendor/phpstan/phpstan-symfony/extension.neon

parameters:
level: 8
paths:
    - src
    - tests

Cette analyse éclaire les zones où l’application dépend encore du conteneur comme d’un registre universel. Elle ne force pas à refondre tous les services immédiatement, mais elle rend visibles les récupérations ambiguës et les usages qui ne correspondent plus à la définition réelle des services. C’est un bon point d’entrée pour remplacer progressivement les accès génériques par des dépendances injectées.

Les résultats doivent aussi nourrir une démarche plus large : auditer un parc applicatif et son plan de montée de version permet de relier les défauts détectés aux contraintes de versions PHP, aux bibliothèques maintenues et aux applications encore exposées à une échéance de support. Un rapport statique devient alors un élément de décision, pas seulement une liste de corrections.

Moderniser vers PHP 8.x avec Rector

Rector 2.7.0 déclare dans son dépôt source rector-src les dépendances phpstan/phpstan en version 2.3 et nikic/php-parser en version 5.9 : l’analyse et la transformation partagent donc la même fondation technique. Il peut appliquer des ensembles de règles orientés vers une version PHP cible grâce aux sets LevelSetList::UP_TO_PHP_80, UP_TO_PHP_81, UP_TO_PHP_82, UP_TO_PHP_83 et UP_TO_PHP_84. Le set choisi doit correspondre à la version réellement visée par l’application, ses dépendances et son environnement de déploiement.

<?php

use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/src',
        __DIR__ . '/tests',
    ])
    ->withSets([
        LevelSetList::UP_TO_PHP_84,
    ]);

Avant toute écriture, exécutez systématiquement Rector avec --dry-run. Cette commande affiche les changements proposés sans toucher aux fichiers ; elle transforme l’outil en support de revue. La différence obtenue doit être découpée par thème : modernisation PHP, correction induite par un type, puis éventuel ajustement de test. Mélanger ces sujets dans une même demande de fusion complique l’identification d’une régression.

composer require --dev rector/rector rector/rector-symfony
vendor/bin/rector process --dry-run
Ingénieure logicielle comparant des modifications de code PHP et les résultats de tests sur deux moniteurs
Le mode dry-run permet d'examiner les transformations Rector avant toute modification du dépôt.

Les échéances de PHP donnent une direction à cette configuration. Le calendrier de fin de vie de l’écosystème PHP, détaillé par endoflife.date et cohérent avec le tableau officiel des versions supportées de php.net, est sans ambiguïté : le support de la branche 8.1 s’est terminé le 31 décembre 2025 et PHP 8.2 ne recevra plus de correctifs de sécurité après le 31 décembre 2026. Les branches 8.3 et 8.4 restent, elles, maintenues en sécurité jusqu’au 31 décembre 2027 et au 31 décembre 2028 respectivement. Une cible PHP n’est donc pas une préférence syntaxique : elle doit résulter d’un plan de compatibilité et d’un calendrier de maintenance.

Appliquer les migrations Symfony avec rector-symfony

L’extension rector/rector-symfony ajoute des sets adaptés aux migrations Symfony. Un exemple est SymfonySetList::SYMFONY_70, mais le nom exact des constantes doit être vérifié dans la version réellement installée. Cette vérification est nécessaire : copier une configuration trouvée dans un article ancien peut produire une constante absente ou sélectionner une migration qui ne correspond pas au projet.

<?php

use Rector\Config\RectorConfig;
use Rector\Symfony\Set\SymfonySetList;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/src',
        __DIR__ . '/tests',
    ])
    ->withSets([
        SymfonySetList::SYMFONY_70,
    ]);

Le dépôt rector-symfony déclare phpstan/phpstan ^2.2 comme dépendance de développement, ce qui illustre le lien étroit entre transformations et analyse. Rector peut modifier une construction dépréciée, mais PHPStan aide à confirmer que les types et les appels restent cohérents après la transformation. Les tests applicatifs restent indispensables pour les comportements dépendants de la configuration, de la base de données ou des intégrations externes.

Symfony 7.4 est la LTS, avec des corrections de bugs jusqu’en novembre 2028 et de sécurité jusqu’en novembre 2029 ; Symfony 8.1 est la stable courante. Le choix entre ces cibles doit être explicite dans la feuille de route. Une application qui cherche d’abord à stabiliser ses contrats peut viser une étape intermédiaire plutôt que d’accumuler, dans une seule livraison, migration PHP, migration Symfony et changements fonctionnels.

Intégrer PHPStan et Rector dans la CI

Dans la CI, PHPStan doit protéger le niveau de qualité atteint : une nouvelle erreur ne doit pas entrer silencieusement. Rector peut être exécuté avec --dry-run afin de signaler qu’une transformation attendue n’a pas été appliquée. Cette approche évite que le pipeline modifie lui-même la branche tout en donnant aux développeurs une indication claire sur l’action à effectuer.

  • Exécutez PHPStan sur src et tests avec la même configuration qu’en local.
  • Lancez Rector en mode --dry-run pour détecter les écarts avec les règles retenues.
  • Faites suivre ces vérifications par les tests du projet.
  • Conservez des jobs séparés pour distinguer erreur de typage, transformation proposée et régression fonctionnelle.
  • Utilisez le cache de manière cohérente, sans accepter qu’il masque un changement de dépendance ou de configuration.

La CI ne remplace pas la revue : elle réduit la quantité d’erreurs répétitives que les relecteurs doivent détecter. Pour structurer les pipelines, les règles de fusion et la responsabilité collective autour des contrôles, consultez ces pratiques DevOps et d’intégration continue. Le principe applicable ici est simple : un contrôle automatisé doit être reproductible, lisible et associé à une action attendue.

Ne rendez pas la CI plus stricte que ce que l’équipe sait traiter. Une baseline peut servir de sas initial, mais les fichiers nouveaux ou modifiés doivent respecter le niveau courant. Au fil des lots de correction, réduisez la baseline et augmentez le niveau plutôt que de maintenir indéfiniment deux standards de qualité dans le même dépôt.

Gérer les limites, faux positifs et dettes héritées

PHPStan ne connaît pas toutes les intentions métier et Rector ne prouve pas qu’une réécriture conserve chaque effet de bord. Les codes dynamiques, les métadonnées, les appels indirects ou les conventions internes peuvent nécessiter une information supplémentaire. Une alerte n’est donc ni une condamnation automatique ni une nuisance à supprimer : c’est une hypothèse technique à vérifier dans le contexte du projet.

Les bonnes pratiques qui limitent le risque sont concrètes :

  • lancez Rector avec --dry-run avant chaque application réelle ;
  • appliquez les transformations par petits lots faciles à annuler ;
  • relisez les différences et exécutez les tests adaptés au domaine modifié ;
  • documentez les suppressions PHPStan réellement justifiées ;
  • utilisez une baseline pour l’existant, puis réduisez-la de manière planifiée ;
  • vérifiez les sets Rector et Symfony disponibles dans les dépendances installées.

Un faux positif récurrent signale souvent un contrat insuffisamment exprimé. Ajouter un type de retour, préciser un tableau avec un PHPDoc ou injecter une dépendance explicite apporte davantage de valeur qu’une suppression locale. À l’inverse, un type artificiel uniquement destiné à satisfaire l’analyseur peut rigidifier le code et dissimuler son vrai comportement : la correction doit refléter la réalité d’exécution.

Une fois la dette de typage réduite et les transformations contrôlées, une cible plus récente devient beaucoup plus accessible. Le guide de migration vers PHP 8.5 peut alors servir à préparer la suite sans faire de la montée de version un saut non maîtrisé. PHPStan fournit des garde-fous, Rector accélère les changements répétitifs, mais la production reste protégée par les tests, la revue et un déploiement progressif.

FAQ

Les réponses ci-dessous traitent les questions opérationnelles qui reviennent lors de l’introduction de PHPStan et Rector dans une équipe responsable d’un applicatif PHP ou Symfony ancien. Elles couvrent notamment l’interprétation des alertes, l’adoption collective, la compatibilité avec un environnement ancien, l’usage encadré de l’IA et la place de ces contrôles dans la CI.

Questions fréquentes

Comment distinguer un faux positif PHPStan d'un vrai défaut de conception ?
Un signal PHPStan mérite d'abord une lecture du contrat concerné : type réel d'une propriété, valeur possible d'un tableau, retour d'un service ou comportement d'une dépendance. Un faux positif provient souvent d'une information absente de l'analyse, par exemple un type trop vague ou une extension manquante. Il ne faut pas le masquer immédiatement : documenter le type, préciser une annotation ou configurer l'extension adaptée est généralement plus durable qu'une suppression locale.
Combien de temps prévoir pour faire adopter PHPStan et Rector à une équipe ?
L'adoption dépend moins de l'installation que du rythme choisi. Une équipe progresse efficacement lorsqu'elle démarre sur un périmètre limité, traite les alertes par petits lots et fait relire les transformations Rector comme toute autre modification. Le niveau PHPStan ne doit pas devenir un objectif abstrait : chaque montée doit correspondre à des règles comprises, à des corrections reproductibles et à une politique claire concernant les dettes héritées.
PHPStan et Rector fonctionnent-ils sur une application qui utilise encore un PHP ancien ?
Oui, ils peuvent aider à préparer une migration, à condition que leur propre environnement d'exécution soit compatible avec le projet et ses dépendances. Rector doit être configuré vers une cible réellement envisagée, sans appliquer aveuglément les règles les plus récentes. PHPStan peut révéler les contrats flous avant le changement de version. La stratégie consiste à stabiliser le code, moderniser par étapes, puis valider l'application sur son environnement cible.
L'IA peut-elle remplacer PHPStan ou Rector pour moderniser un legacy ?
Non. Une IA peut accélérer l'explication d'un rapport, proposer un test ou suggérer une correction locale, mais elle ne fournit pas la garantie déterministe d'un analyseur statique ni les transformations structurées d'un outil fondé sur l'AST. PHPStan et Rector doivent rester les garde-fous automatisés du dépôt. L'IA intervient utilement autour de ces résultats, sous revue humaine et avec une exécution complète de la suite de tests.
Quel est le coût d'exécution de PHPStan et Rector dans une CI ?
Le coût dépend de la taille du dépôt, du cache disponible, des extensions et du périmètre analysé. PHPStan doit être exécuté régulièrement sur les chemins surveillés, tandis que Rector est souvent lancé en vérification avec `--dry-run` dans les pipelines. Il faut mesurer le comportement sur le dépôt réel, conserver les sorties utiles et éviter de transformer automatiquement du code sans revue. Un découpage par jobs peut aussi clarifier les échecs.