Aller au contenu
symfonyphpmigrationdette technique

Migrer sans geler l'équipe : l'ordre des paliers, et celui de PHP

Une application en production, Symfony 4.4, un composer.json qui réclame PHP 7.2, et la phrase qu'on entend à chaque fois : « on ne peut pas monter PHP tant qu'on n'a pas monté Symfony ». C'est presque toujours faux, et ça se vérifie en une commande. Voici l'ordre dans lequel je fais les paliers, et pourquoi celui-là permet de déployer chaque semaine au lieu de disparaître six mois.

Le diagnostic, et il surprend à chaque fois

L'application dont je parle a tourné près de sept ans, d'août 2019 à avril 2026, et je l'ai écrite moi-même : ce n'est pas une horreur trouvée chez quelqu'un d'autre, c'est ce que devient n'importe quel code qui fait vivre un commerce assez longtemps.

Son composer.json demande "php": "^7.2.17". Son serveur tournait sous PHP 7.2. La question de départ était donc : qu'est-ce qui l'empêche de monter ?

Le fichier composer.lock répond, et il faut le lire au bon endroit. La version installée de symfony/framework-bundle est la 4.4.47, et sa contrainte est php: >=7.1.3. Pas de plafond. La page officielle de Symfony 4.4 dit la même chose : « PHP 7.1.3 or higher », et elle n'annonce aucune version maximale (https://symfony.com/releases/4.4, relue le 1er septembre 2026).

Autrement dit : ce n'est pas Symfony qui épinglait PHP 7.2, c'est le projet lui-même, dans son propre composer.json.

Et la vérification tient en une ligne. Ce matin, sur cette base de code inchangée depuis avril, sous PHP 8.5.7 :

$ php8.5 bin/console --version
Symfony 4.4.48 (env: dev, debug: true)

Huit avertissements de dépréciation au démarrage, et un code retour 0. L'application démarre sous PHP 8.5 alors que son composer.json réclame PHP 7.2.

⚠️ Ce que cet essai prouve, et ce qu'il ne prouve pas. Il prouve que le noyau se charge : le conteneur se compile, les bundles s'enregistrent, rien n'explose au démarrage. Il ne prouve pas que les 79 fichiers de code métier passent tous sous PHP 8, et il ne dit rien des chemins qui ne sont pris qu'en production. C'est un feu vert pour commencer, pas un certificat. Mais c'est exactement le feu vert qui manque à la plupart des équipes qui reportent depuis trois ans.

Ce qui bloque vraiment : quatre dépendances qui n'ont rien à voir avec le numéro de version de Symfony

En lisant le composer.lock ligne à ligne, le vrai travail apparaît, et il est ailleurs.

Swiftmailer. Sa maintenance a été arrêtée fin novembre 2021, annoncée par son auteur sur le blog de Symfony le 19 août 2021 (https://symfony.com/blog/the-end-of-swiftmailer). Douze modèles d'e-mails transactionnels passaient par lui.

SensioFrameworkExtraBundle. Le paquet est marqué abandonné sur Packagist, sa dernière version est la 6.2.10 du 24 février 2023, et le remplacement recommandé est Symfony lui-même : ses annotations sont devenues des attributs natifs du framework.

wkhtmltopdf, derrière knp-snappy, qui générait les factures et les cartes cadeaux. Le dépôt a été archivé par son propriétaire le 2 janvier 2023, il est en lecture seule. Détail savoureux relevé à la reprise : le binaire n'était même pas installé sur le serveur cible, donc la fonctionnalité était à refaire de toute façon.

La chaîne d'assets, enfin : Webpack Encore, node-sass, jQuery, Bootstrap 4, et 515 dossiers dans node_modules.

Aucune de ces quatre choses ne s'améliore en montant Symfony d'une version. Ce sont quatre chantiers indépendants, et c'est le vrai budget de la migration. Le numéro de version du framework, lui, est la partie la moins chère : Symfony 4.4 a perdu ses correctifs de sécurité en novembre 2023, ce qui est une raison sérieuse d'y aller, mais ce n'est pas ce qui coûte.

L'ordre, et pourquoi c'est celui-là

1. PHP d'abord. Trois raisons. La version du framework ne l'interdit presque jamais, on vient de le voir. Tout ce qui vient ensuite en dépend : Doctrine, PHPUnit, l'analyse statique, et la fenêtre de support de votre hébergeur. Et c'est le seul palier où, la plupart du temps, on ne touche pas une ligne de code métier. Il se mesure, en plus : le nombre d'avertissements de dépréciation au démarrage est un chiffre qui descend, donc une avancée qu'on peut montrer à quelqu'un qui paie.

2. Ensuite les dépendances mortes, une par déploiement. Swiftmailer vers symfony/mailer, c'est un DSN et un nom de classe. Les annotations Doctrine vers les attributs PHP, c'est mécanique et outillé. SensioFrameworkExtraBundle se dissout dans le framework. Chacune de ces trois-là part seule, en production, sans attendre les autres.

3. Puis les versions de Symfony, une mineure à la fois. À ce stade seulement, parce qu'alors les dépréciations qui restent sont vraiment celles de Symfony, et pas le bruit de fond des paquets abandonnés. La liste des dépréciations devient une liste de tâches au lieu d'être un mur illisible.

4. La chaîne d'assets n'est pas un palier, c'est un projet à part. Webpack Encore continue de fonctionner pendant tout ce qui précède. La mettre sur le chemin critique d'une montée de version, c'est se donner une raison de plus de ne jamais livrer.

« Sans geler l'équipe » veut dire une chose précise

Une seule : aucune branche ne vit plus de quelques jours.

Le test, à chaque palier : est-ce que je peux déployer ça ce soir ? Si la réponse est non, ce n'est pas un palier, c'est un big-bang sur lequel on a dessiné des étapes. Une migration qui vit six mois dans une branche à part n'a pas un problème technique, elle a un problème de découpage, et elle finira par un week-end de bascule et un lundi matin difficile.

Ce que ça coûte, et il faut le dire au client : pendant quelques semaines, l'application a deux façons de faire la même chose. Un service migré et l'autre non, un modèle d'e-mail passé au nouveau moteur et les onze autres encore à l'ancien. C'est inconfortable, ça se voit dans le code, et c'est le prix de pouvoir livrer autre chose en même temps. Le confort d'une base de code homogène se paie en gel, et le gel est toujours plus cher.

Ce que cet ordre ne résout pas

Il faut finir par l'honnêteté, sinon ce texte serait une brochure. Sur cette application-là, je n'ai pas monté les paliers. J'ai reconstruit à côté, sur Symfony 8, et j'ai basculé en une nuit, le 1er juillet 2026.

Et la raison n'est pas le numéro de version. Les paliers marchent quand le code qu'on fait monter est le code qu'on veut garder. Ici, un contrôleur de 3 620 lignes, des formulaires construits à la main dans les contrôleurs, une couche de présentation en jQuery et Bootstrap 4 : monter ces paliers, c'était payer pour transporter du code destiné à la benne. La base de données, elle, tenait parfaitement, et c'est elle qui a été gardée.

Le critère, en une phrase : la montée par paliers est la bonne réponse quand ce qui coûte cher est la version, et la mauvaise quand ce qui coûte cher est la façon dont c'est écrit. Les deux se ressemblent de l'extérieur, et c'est précisément ce qu'un état des lieux sert à distinguer avant de chiffrer quoi que ce soit.

Dans les deux cas, la première chose à faire est la même, et elle est gratuite : ouvrir le composer.lock, chercher la contrainte PHP réelle du framework, et lancer la console sous la version de PHP que vous visez. Vous saurez en cinq minutes si le mur que vous contournez depuis trois ans existe.

C'est ce diagnostic que je livre en début de mission, et c'est aussi par là que commence une montée de version Symfony chiffrée : tant qu'on ne sait pas ce qui épingle vraiment l'application, tout devis est une devinette.

Montée de version

Dites-moi sur quelle version vous êtes coincé

Donnez-moi la version de Symfony, celle de PHP, et depuis combien de temps l'application n'a pas été mise à jour. Je réponds sous 24 h avec un premier avis sur l'ampleur du chantier, et sur ce qui doit passer en premier.