Reprendre un legacy PHP : diagnostic et cartographie en 5 axes
Ce qu'on voit vraiment chez un client legacy
Quand nous auditons une plateforme PHP que plus personne ne maîtrise, le décor est toujours le même. Une base de code qui a dix à quinze ans, un Zend Framework 1 ou un Symfony 2 jamais migré, des dépendances Composer bloquées sur des versions abandonnées, zéro test automatisé, une documentation faite de Confluence à moitié morts et de Post-it collés sur les écrans. Les développeurs d'origine sont partis depuis longtemps. L'équipe actuelle ajoute des fonctionnalités en priant pour ne rien casser.
Ce code génère du chiffre d'affaires. Parfois des dizaines de millions d'euros par an. Il ne peut pas s'arrêter. On ne peut pas non plus le réécrire sur un coin de table, le big bang tue un client sur deux qui essaie.
Notre métier consiste à reprendre ces bases sans casser le business, et à les faire évoluer vers un socle moderne en dix-huit à trente-six mois. Cet article détaille la méthode que nous appliquons sur chaque mission de reprise.
Qu'est-ce qu'un legacy, vraiment
La définition populaire est fausse. Un legacy n'est pas "du vieux code". C'est du code qui fait peur à ceux qui doivent le modifier. Cette peur a des causes précises, et identifier la cause dominante oriente le plan d'action.
- Peur fonctionnelle. Personne ne sait ce que le code est censé faire. Les règles métier sont enfouies dans des if imbriqués sur quinze niveaux.
- Peur technique. Le code utilise des patterns abandonnés (globals, registry statique, ActiveRecord maison), des APIs dépréciées, des dépendances que plus rien ne supporte.
- Peur de régression. Pas de test, pas de filet. Chaque déploiement est une loterie.
- Peur opérationnelle. Le build tient avec un script bash de 2014, la prod est sur un serveur physique mal documenté, personne ne sait remonter l'environnement.
Un code Symfony 7.1 écrit il y a six mois peut être un legacy si les développeurs d'origine sont partis, que les tests sont absents et que la logique métier n'est documentée nulle part. À l'inverse, un code PHP 5.6 peut être tout à fait maintenable s'il est couvert par des tests et documenté.
Cartographie en 5 axes
Avant toute décision, nous cartographions la réalité sur cinq dimensions. Les quatre à huit premières semaines d'une mission de reprise sont consacrées à ce travail. Sauter cette étape est la première erreur des équipes internes qui tentent la reprise seules.
1. Cartographie fonctionnelle
Comprendre ce que le code fait côté métier. Le code ne suffit pas : il faut aller voir les humains et les données de prod.
- Interviews utilisateurs. Trois à cinq utilisateurs par rôle métier. Une heure par entretien. Objectif : reconstituer les parcours réels, identifier les workflows critiques.
- Analyse des logs de production. Les 30 derniers jours. Quelles URLs sont appelées le plus ? Quelles jobs cron tournent ? Quels endpoints API sont utilisés en externe ?
- Ateliers avec le métier. On reconstruit le domaine sous forme de diagrammes de contexte, de flux, de données. Même s'il existe une documentation ancienne, elle est généralement fausse.
Livrable : un diagramme C4 niveau 1 et 2 (contexte et containers), plus un inventaire des parcours métier critiques classés par impact business.
2. Cartographie technique
L'inventaire technique complet. Tout ce qui compose le système, y compris ce qui est oublié depuis longtemps.
- Version PHP réelle en prod, extensions installées.
- Framework principal et version. Les bibliothèques majeures (ORM, templating, queue, cache).
- Composer :
composer outdated,composer why, détection des packages abandonnés. - Base de données : version, taille, tables, procédures stockées, triggers.
- Services externes : API consommées, webhooks, SFTP, partages réseau.
- Jobs cron, workers, files d'attente.
- Infrastructure : où ça tourne, comment c'est déployé, qui a les accès.
Outils pour automatiser cet inventaire :
# Volumétrie brute
phploc src/ --progress
# Métriques de complexité
phpmetrics --report-html=metrics src/
# Graphe de dépendances entre classes et packages
pdepend --summary-xml=pdepend.xml --dependency-xml=deps.xml src/
# Couverture actuelle (si tests existants)
phpunit --coverage-text --coverage-html=coverage/
# Audit de sécurité des dépendances
composer audit --format=json > audit.json
symfony security:check
3. Cartographie des risques
Les risques sont la liste des choses qui peuvent faire tomber le système ou la société. On les hiérarchise par probabilité et impact.
| Risque | Détection | Gravité type |
|---|---|---|
| CVE critique sur dépendance | composer audit + Symfony Security Advisories |
Élevée |
| Fin de support PHP en cours | Version EOL sur php.net | Moyenne à élevée |
| Secrets en dur dans le code | gitleaks detect, grep ciblé |
Critique |
| SQL concaténé (injections) | PHPStan custom rules, audit manuel | Critique |
| Fonctions dépréciées ou supprimées | Rector rules PHP8x, DowngradePhp80 inverse |
Moyenne |
Sérialisation unserialize sur input |
Grep + revue | Critique |
| XSS côté templates | Twig/Blade audit, mode strict | Élevée |
| Crons sans monitoring | Cartographie ops | Moyenne |
| Backups non testés | Interview DSI | Critique |
Notre règle : toute ligne de la matrice de gravité critique doit avoir une remédiation planifiée dans les 30 jours, même si le reste du plan prend dix-huit mois.
4. Cartographie de la dette
Mesurer la dette avec des outils, pas au doigt mouillé. Les trois outils du trio minimum :
- PHPStan level max. On lance le niveau 9 (strict), on compte les erreurs, on génère un baseline. Ce baseline devient le compteur de dette.
- Rector dry-run. Avec les sets
PHP81,SYMFONY_64,CODE_QUALITY,DEAD_CODE. Le nombre de transformations proposées est un bon proxy de la dette de modernisation. - Deptrac. Pour mesurer les violations de couches. Plus il y a de violations, plus la réécriture sera douloureuse.
Exemple de fichier deptrac.yaml que nous installons systématiquement dès la première semaine :
parameters:
paths:
- src
exclude_files:
- '#.*test.*#i'
layers:
- name: Controller
collectors:
- type: classLike
value: 'App\\Controller\\.*'
- name: Service
collectors:
- type: classLike
value: 'App\\Service\\.*'
- name: Domain
collectors:
- type: classLike
value: 'App\\Domain\\.*'
- name: Infrastructure
collectors:
- type: classLike
value: 'App\\Infrastructure\\.*'
- name: Legacy
collectors:
- type: classLike
value: 'Legacy\\.*'
ruleset:
Controller:
- Service
- Domain
Service:
- Domain
Domain: ~
Infrastructure:
- Domain
Legacy:
- Controller
- Service
- Domain
- Infrastructure
La couche Legacy est intentionnellement permissive. Elle matérialise la zone ancienne que l'on encercle, puis que l'on rétrécit au fil des mois. Chaque itération de modernisation doit diminuer la taille de cette couche, mesurée en nombre de classes et en kloc.
5. Cartographie humaine
Le facteur humain est celui qui tue le plus de migrations. Qui sait quoi, qui est prêt à partir, qui est motivé par le chantier.
- Bus factor par module. Combien de personnes peuvent maintenir chaque bloc fonctionnel. Un bus factor de 1 sur un module critique est une alerte rouge.
- Turnover prévu. Les départs en cours, les vacances longues, les fins de contrats.
- Documentation tribale. Ce que savent les gens et qui n'est écrit nulle part. On capture cette connaissance en sessions enregistrées, en ADR (Architecture Decision Records), en runbooks.
- Posture vis-à-vis du chantier. Les sceptiques, les moteurs, les résistants. Un changement majeur sans sponsor interne fort échoue.
Outils concrets que nous imposons
Le premier mois d'une mission de reprise consiste à installer et faire tourner cette stack d'observation, sans toucher au code métier.
- PHPStan niveau progressif, avec baseline. Intégré dans la CI dès la première semaine.
- Psalm en complément pour la détection d'impossible code et de taint analysis.
- Deptrac pour la discipline architecturale.
- Rector en dry-run pour estimer la modernisation possible.
- PHPMetrics pour les visualisations de complexité, à montrer au sponsor et au comité de pilotage.
- phploc pour la volumétrie, à comparer de mois en mois.
- PhpDepend pour les graphes de couplage et d'abstraction.
- Composer audit et Symfony Security Advisories pour la CVE hygiene.
- Gitleaks pour détecter les secrets historiques dans le git, et la rotation des crédentiels qui fuitent.
Matrice de décision : reprendre, réécrire, encapsuler, tuer
Une fois la cartographie en main, chaque module du système entre dans l'une des quatre catégories suivantes. Nous construisons cette matrice avec le sponsor métier et la direction technique.
| Module | Impact métier | État technique | Décision | Justification |
|---|---|---|---|---|
| Billing core | Critique | Sale mais fonctionnel | Reprendre + refactor progressif | Trop risqué à réécrire, valeur métier forte |
| Import legacy CSV | Faible, mensuel | Obsolète | Tuer | Remplacer par workflow no-code |
| Module RH interne | Moyenne | Très dégradé | Encapsuler derrière API, réécrire plus tard | Isoler pour ne plus y toucher |
| Recherche full-text maison | Moyenne | Complexe, peu documenté | Remplacer par Elasticsearch managé | ROI immédiat |
| Module paiement | Critique | Normes PCI à respecter | Réécrire proprement | Risque de conformité |
| Portail admin | Faible | Presque pas utilisé | Tuer | Validé avec métier, usage < 5 %/mois |
La règle qui coupe le plus de débats : pas de réécriture sans étude de valeur. Réécrire coûte 1 à 3 fois le coût initial, et pendant la réécriture le legacy continue de bouger. Nous n'autorisons la réécriture que sur les modules où l'encapsulation n'est pas tenable (conformité, dette bloquante, coût de run qui explose).
Quick wins réalisables en une semaine
Même avant la fin de la cartographie, certains gains sont immédiats et rassurent le sponsor. Ils montrent que la reprise est active.
composer audit --lockedet patch des CVE critiques via upgrades ciblés.- Passage au dernier PHP mineur supporté de la branche courante (7.4 → 8.0 minimum, idéalement 8.3).
- Mise en place d'un monitoring d'erreurs (Sentry ou Bugsnag) : on découvre en 48 heures les erreurs silencieuses que personne ne voyait.
- Activation des logs structurés (Monolog JSON) et envoi vers une plateforme centralisée (OpenObserve, Elasticsearch, Loki).
- Activation de
display_errors = Offetexpose_php = Offen prod si ce n'est pas fait (encore vu en 2025). - Rotation des secrets git leakés.
- PHPStan level 1 en CI avec baseline, pour stopper la régression.
- Backup base de données testé en restauration : on déroule la restauration complète sur un env de test.
Plan de sortie recommandé en 5 étapes
Après la cartographie, nous proposons un plan structuré que nous ajustons au contexte client.
- Stabiliser (1 à 3 mois). CVE critiques patchées, CI en place, monitoring actif, tests de caractérisation sur les parcours critiques. Objectif : arrêter la dégradation.
- Encercler (3 à 6 mois). Introduire une couche anti-corruption (anti-corruption layer), exposer les fonctions métier critiques derrière des APIs propres, Deptrac durci. Objectif : isoler le legacy pour pouvoir travailler à côté.
- Moderniser par tranches (6 à 18 mois). Strangler pattern module par module. Chaque trimestre a un objectif mesuré : X % de la couche Legacy réécrite, Y kloc supprimés, Z endpoints migrés. Jamais de big bang.
- Décommissionner (12 à 24 mois). Supprimer les modules remplacés, archiver les anciens, couper les dépendances mortes. C'est l'étape la plus difficile psychologiquement : les équipes hésitent à supprimer du code "au cas où".
- Consolider (en continu après 18 mois). La plateforme est sur un socle moderne, la dette est maîtrisée, les tests couvrent les parcours critiques. On passe en mode évolution normale, avec une discipline de dette stricte.
Cas anonymisé : plateforme métier B2B 400 kloc
Pour donner une idée de ce que peut produire cette méthode, voici un cas réel anonymisé d'une mission portée par notre équipe.
- Contexte. Plateforme métier B2B, secteur services financiers régulés. 400 kloc PHP. Zend Framework 1.12, Doctrine 1.2, PHP 7.2 en prod. Zéro test automatisé. 3 développeurs, aucun présent au démarrage du projet en 2012.
- Cartographie (8 semaines). Inventaire de 47 parcours métier critiques, 12 modules identifiés, matrice reprendre/réécrire/encapsuler/tuer validée avec le COMEX.
- Stabilisation (4 mois). PHP 7.4, monitoring Sentry, 18 CVE critiques patchées, backup testé, CI PHPStan level 2. Zéro incident pendant cette phase.
- Encerclement (6 mois). API REST moderne exposée via un Symfony 6.4 nouveau, toutes les écritures critiques passent par cette couche. Le front legacy continue d'appeler la base directement en lecture seule.
- Modernisation (12 mois). Strangler pattern par module : billing, reporting, workflows, admin. À la fin, 60 % de la base Legacy réécrite, PHP 8.3, Symfony 7.1.
- Résultats sur 18 mois. Zéro régression majeure en production. Temps de livraison d'une feature divisé par 4. Coût d'infrastructure réduit de 35 % par consolidation serveur. Équipe étoffée de 3 à 7 développeurs, tous formés à la nouvelle stack.
Conclusion
Reprendre un legacy PHP n'est ni un acte de foi ni un projet artisanal. C'est une méthode. La cartographie en cinq axes, la matrice de décision et le plan de sortie en cinq étapes nous permettent de ramener à un risque maîtrisé ce que beaucoup considèrent comme un sauvetage impossible.
Pour un audit de reprise ou une mission longue de modernisation, écrivez-nous à contact@your-digital-hub.com ou découvrez notre expertise PHP et nos prestations de migration.