Moderniser une application PHP historique sans réécrire tout le système
Réécrire de zéro est la stratégie qui échoue le plus souvent. Comment moderniser une application PHP par étapes : frontières fonctionnelles, strangler pattern, filet de tests, et réduction des risques lot après lot.
Par l'équipe Synapsea · · 5 min de lecture
Une application PHP de dix ou quinze ans fait tourner l'entreprise. Elle est aussi devenue difficile à faire évoluer : version de PHP en fin de vie, framework abandonné ou maison, pas de tests, règles métier enfouies dans des contrôleurs de mille lignes. La tentation est de repartir de zéro. C'est en général la pire option : le temps que la nouvelle application rattrape l'ancienne, le métier a changé, le budget est consommé et les deux systèmes coexistent sans fin.
Voici l'approche progressive que nous appliquons, et ses règles.
1. D'abord, rendre le présent observable
On ne modernise pas ce qu'on ne mesure pas. Avant de toucher au code :
- Inventaire : versions (PHP, framework, extensions), dépendances et leur état de maintenance, points d'entrée (URL, crons, files, interfaces partenaires), schéma de base de données et ses usages réels.
- Journalisation : si l'application ne logue pas, on ajoute un minimum (requêtes entrantes, erreurs, temps de réponse) pour savoir ce qui est réellement utilisé. Une fraction du code est souvent morte.
- Tests de caractérisation : des tests qui décrivent le comportement actuel, y compris ses bizarreries, sur les parcours qui comptent. Ils ne jugent pas le code ; ils figent ce que les utilisateurs attendent.
Ce socle est le livrable du diagnostic. Il permet de décider en connaissance de cause et sert de filet pendant toute la suite.
2. Mettre à niveau le langage avant le framework
PHP d'abord. Passer de PHP 5.6 ou 7.x à PHP 8.4 est une opération en soi : fonctions retirées, typage plus strict, extensions disparues (mysql_*, ereg, mcrypt). Elle se fait sans changer de framework, avec des outils :
- Rector avec les jeux de règles PHP (
->withPhpSets()) pour les changements mécaniques ; - PHPStan à niveau croissant pour trouver ce que Rector ne voit pas ;
- une image Docker à la version PHP cible pour exécuter la suite de caractérisation.
Résultat : une application au comportement identique, sur un langage maintenu (PHP 8.4 est en support actif jusqu'au 31 décembre 2026, puis en sécurité jusqu'au 31 décembre 2028). Beaucoup d'applications s'arrêtent là pour un temps, et c'est une bonne décision.
3. Tracer des frontières fonctionnelles
Le vrai travail de modernisation consiste à découper. Pas par couche technique (« on refait d'abord toute la base de données »), mais par frontière fonctionnelle : facturation, catalogue, comptes utilisateurs, exports. Pour chacune, on identifie :
- les entrées (URL, commandes, messages) ;
- les tables qu'elle possède et celles qu'elle ne fait que lire ;
- les dépendances vers les autres frontières.
Les frontières à faibles dépendances et à forte valeur sont les premiers candidats. Celles qui sont stables et peu touchées peuvent rester dans l'ancien code longtemps.
4. Le strangler pattern, concrètement
Le motif « strangler fig », décrit par Martin Fowler, consiste à faire pousser la nouvelle application autour de l'ancienne, fonctionnalité par fonctionnalité, jusqu'à ce que l'ancienne soit vide. En PHP :
- Une façade de routage reçoit toutes les requêtes. Au début, elle délègue tout à l'ancienne application. C'est souvent un simple
index.phpSymfony dont le dernier fallback inclut l'ancien bootstrap. - Une frontière est réécrite dans la nouvelle base de code (Symfony, Doctrine, tests). La façade route ses URL vers le nouveau code ; tout le reste continue d'aller à l'ancien.
- Les données partagées passent par une seule source de vérité. Tant que les deux codes écrivent dans la même table, on garde le schéma, on ajoute des contraintes et on documente ; les migrations de schéma viennent après, quand une seule application écrit.
- On mesure : erreurs, temps de réponse, écarts fonctionnels signalés. Le lot est validé ; on passe au suivant.
L'ancienne et la nouvelle application coexistent des mois. C'est voulu : à aucun moment il n'y a de bascule globale.
5. Réduire les risques à chaque lot
Quelques règles qui évitent la plupart des accidents :
- Un lot = une frontière = une mise en production. Pas de lot de trois mois.
- Pas de refonte du schéma pendant la réécriture d'une frontière. Une chose à la fois.
- Tests de caractérisation exécutés avant et après chaque lot, en plus des tests de la nouvelle base.
- Interfaces partenaires préservées au bit près : les systèmes qui consomment vos exports ou vos API ne doivent rien voir. Un contrat d'interface (OpenAPI, schéma de fichier) devient un test.
- Retour arrière possible : la façade peut rediriger une frontière vers l'ancien code en quelques minutes.
- Documentation au fil de l'eau : chaque lot laisse une page qui dit ce qui a bougé, où et pourquoi.
6. Quand la réécriture totale est justifiée
Il existe des cas : application très petite, couplage total sans frontière identifiable, ou socle incompatible (PHP 4, extension disparue sans alternative). Même alors, on garde le filet de tests de caractérisation et on livre la nouvelle application par morceaux visibles, pas en un bloc.
Ce qu'il faut retenir
- Observer avant de modifier : inventaire, journaux, tests de caractérisation.
- PHP d'abord, framework ensuite ; les deux opérations ne se mélangent pas.
- Découper par frontière fonctionnelle et faire coexister ancien et nouveau code derrière une façade.
- Livrer chaque lot en production, avec retour arrière possible.
Pour aller plus loin : notre offre de modernisation PHP & Symfony décrit les trois niveaux d'accompagnement, du diagnostic à la migration accompagnée, et notre article sur la migration Symfony 6.4 vers 7.4 détaille l'étape framework.
Sources : « StranglerFigApplication », Martin Fowler (martinfowler.com) ; versions PHP supportées (php.net/supported-versions) ; documentation Rector (getrector.com) ; documentation PHPStan (phpstan.org).