Le dépoussiérage de mon appli de bioinformatique : partie 1 – L’API

Comment une simple erreur 500 sur une API Symfony m’a entraînée dans une modernisation complète — Composer, Deployer, migration PHP 8 et pièges Doctrine compris.

Cette fois, je savais dès le départ à quoi m’attendre. Un projet qu’on laisse dormir plusieurs années ne se réveille jamais avec un simple git pull : il se réveille en mission archéologique, couche après couche. Alors avant même de lancer la première commande, j’avais le bon état d’esprit : creuser, documenter, ne pas m’énerver quand une correction en révèle trois autres en dessous. Et cette fois, j’avais un luxe que je n’avais pas les fois précédentes où j’ai fouillé seule dans un vieux projet : une IA à mes côtés pour diagnostiquer plus vite, remonter aux vraies causes sans tourner en rond, et garder une trace claire de chaque strate retirée.

Au départ de cette fouille : une erreur 500 anodine sur api.amelayes-biophp.net, mon API bioinformatique en Symfony 4 / API Platform. À l’arrivée : une remise à plat de toute ma chaîne, de Composer jusqu’au fond de mon deploy.php, une bibliothèque republiée et une migration Symfony sur trois versions d’un coup. Je vous raconte tout, parce que le vrai fil rouge de cette histoire n’est pas un bug précis : c’est la dette technique et la dépréciation qui s’accumulent en silence sur un projet qu’on laisse vivre sa vie.

Ha oui et pour les dictateurs de la bonne conscience (car j’y ai eu droit un jour à la question) : Mai pourkoua vou ne l’avai pa fini ???? Parce que c’est un side-project donc qui dit side-project dit sur son temps libre, parce que ma maman a eu de graves soucis de santé, parce que mon papa a quitté ce monde, parce que la maison, parce que les clients c’est plus important. Et à moment donné j’ai eu envie de m’évader sur des blocs virtuels empilés les uns sur les autres. Et ça c’est aussi un beau side-project. Sans rire. Ce ne sont pas des excuses, mais juste la vie. Et je vais être honnête : sans l’IA, je n’aurais pas remis les mains dans ce cambouis.

Le déclencheur : Composer 1 n’existe plus

Tout part d’une envie simple : mettre à jour l’API. Je lance un composer install de routine, et là, mur. Une vingtaine de paquets « introuvables ». Symfony, Doctrine, API Platform… tout d’un coup « could not be found ».

La vraie cause, tout en haut de la sortie :

Support for Composer 1 has been shutdown on September 1st 2025.

Mon Composer était resté en version 1, et Packagist a coupé le support. Mon vendor/ était bancal parce qu’un vieux composer install avait échoué à moitié, et c’est ça qui faisait planter l’API. Première leçon, valable pour toute la suite : la dépréciation ne prévient pas, elle attend que vous reveniez sur le projet pour se rappeler à vous.

Le piège s’est doublé aussitôt : sur mon Mac, macOS n’était plus supporté par Homebrew, donc brew cassé, donc composer self-update impossible. Pour réparer un outil, j’avais besoin d’un autre outil que je ne pouvais plus mettre à jour. Solution pragmatique : récupérer le .phar de Composer 2 en direct, sans passer par Homebrew.

Le deploy.php : un fichier plein de décisions devenues obsolètes

C’est ici que l’histoire devient vraiment parlante. Mon fichier de déploiement Deployer n’était pas juste cassé : il était rempli de déclarations qui avaient eu une bonne raison d’exister un jour, et qui étaient devenues du poids mort, voire nuisibles, sans que rien ne me le signale.

Le hostname manquant. host('bioapi-prod') n’avait pas de ->set('hostname', ...). Ça marchait avant, grâce à un alias SSH dans un ~/.ssh/config sur une autre machine — jamais versionné, donc invisible dès que j’ai changé de poste. Rien dans le fichier ne documentait cette dépendance externe.

Le ->stage('prod') devenu un vestige. Syntaxe standard en Deployer 6 pour sélectionner un environnement. En Deployer 7, ce mécanisme de stage disparaît : dep deploy prod devient dep deploy bioapi-prod, via l’alias d’hôte directement. Le ->stage() traînait sans plus rien piloter.

restart_fpm et php7.4-fpm en dur. Une tâche custom rechargeait php7.4-fpm après chaque déploiement. Logique tant que le serveur tournait en PHP 7.4. Après la migration vers PHP 8.1, cette tâche était non seulement inutile, elle était fausse — elle redémarrait un service PHP qui n’était plus celui servant l’application. Il a fallu retirer la définition de la tâche et son hook after(), les deux ensemble, sous peine de faire planter le déploiement sur une tâche fantôme.

Le chemin de recipe périmé. Le require 'recipe/symfony4.php' a dû devenir le recipe générique recipe/symfony.php de Deployer 7. Détail apparemment mineur, mais premier point de rupture classique d’une montée de version majeure.

Le ssh_multiplexing en aller-retour. Mis à false en Deployer 6 pour contourner un souci Windows, il a dû repasser à true pour Deployer 7 et son nouveau système de verrouillage. Une déclaration héritée d’un vieux bug, à inverser pour un bug nouveau.

deploy:lock / deploy:unlock, retirés carrément. Deployer 7 a un bug documenté où deploy:lock se redéclenche seul après deploy:writable, provoquant une GracefulShutdownException sur un déploiement pourtant sain. Comme je suis seule à déployer, j’ai retiré ces tâches de la liste explicite, en gardant le filet after('deploy:failed', 'deploy:unlock').

Ce qui me frappe : chacune de ces lignes avait été juste, un jour, pour une bonne raison. Aucune n’était une erreur de débutante. C’est exactement ça, la dette technique : pas du mauvais code, mais des décisions correctes qu’on n’a pas révisées quand leur contexte a changé.

Les migrations Doctrine : deux pièges de dépréciation coup sur coup

Le déploiement passait enfin la phase code, et calait sur la base de données. Deux problèmes distincts, tous deux liés à l’évolution de Doctrine Migrations.

Premier piège : la config du bundle. La syntaxe de doctrine-migrations-bundle avait changé. Les anciennes clés dir_name et namespace ont été remplacées par migrations_paths. Tant que la config n’était pas mise à jour, le bundle ne trouvait plus ses migrations correctement.

La vraie cause du 500, cachée derrière un fichier vide

Une fois Composer, Deployer et les migrations stabilisés, l’API se déployait — et plantait toujours en 500. Cause réelle : une variable CORS_ALLOW_ORIGIN manquante, bloquée par un .env.local.php vide sur le serveur, prioritaire sur le .env normal (comportement standard de Symfony : le fichier compilé prime, même vide). Un fichier de cache oublié par un ancien déploiement suffisait à masquer toute la config. Il a fallu aussi relancer OPcache après le réchauffement du cache, sinon le serveur continuait de servir l’ancienne version compilée.

Côté demo : biophp lui-même n’était pas épargné

Le site demo.amelayes-biophp.net avait sa propre saga. Des dump() dans les templates Twig faisaient planter la prod (remplacés par une macro tree maison). Plus profond : des objets biophp refusaient de se sérialiser, à cause de types non-nullables sur des getters pouvant renvoyer null selon le schéma Doctrine réel. Les types ont été alignés avec la base (v0.1.15 → v0.1.16), getSeqSet() a cessé de copier en tableau pour renvoyer son ArrayIterator d’origine, et plusieurs DataFixtures ont récupéré un : void de retour manquant. Le bundle EasyAdmin, jamais utilisé, a été retiré — encore une déclaration qui traînait pour rien.

La remise à plat de fond : Symfony 4.4 → 6.4, PHP 7.4 → 8.1

Restait le vrai sujet : le projet tournait encore en Symfony 4.4 et PHP 7.4, deux versions en fin de vie. Migration par paliers : 4.4 → 5.4 → 6.4 côté Symfony, 7.4 → 8.1 côté PHP, avec à chaque étape un audit à sec (composer update --dry-run) avant d’écrire quoi que ce soit — repérer les paquets qui changent, ceux qui refusent de se résoudre, et dans le code les fonctions dépréciées ou supprimées (each(), create_function(), propriétés dynamiques non déclarées). Un platform.php a été ajouté à composer.json pour figer la version PHP ciblée et éviter toute dérive. Au passage, découverte que le site demo tournait en réalité sur PHP 7.3 — pas 7.4 — à cause d’un SetHandler de vhost Apache resté sur l’ancienne version.

La fin : Migration vers PHP 8.5 et mise à jour de tout le composer vers la nouvelle version d’Api Platform

Deuxième piège : une migration qui plante sans rien casser. Voici le message qui m’a fait perdre du temps :

[bioapi-prod] [notice] Migrating up to DoctrineMigrations\Version20260807140000 [bioapi-prod] [warning] Migration … was executed but did not result in any SQL statements. [bioapi-prod] exit code 255 (Unknown error) ERROR: Task database:migrate failed!

Le comportement de doctrine/migrations a évolué : une migration dont le up() ne produit aucun SQL est désormais traitée strictement. Là où une ancienne version passait en silence, la nouvelle émet un warning et renvoie un code d’erreur 255 — ce qui fait échouer la tâche database:migrate de Deployer alors que, sur le fond, rien n’est cassé. Un classique de dette technique : ce n’est pas mon code qui a changé, c’est la sévérité de l’outil autour.

Le vrai fond du problème : array déprécié au profit de json. Sous ce warning se cachait une vraie migration de données. Le type de colonne Doctrine array — qui stocke via serialize() de PHP — a été déprécié puis abandonné au profit du type json. Les anciennes données sérialisées en base ne sont plus lisibles telles quelles par le nouveau type. J’ai donc dû modifier deux entités pour passer leurs propriétés concernées de array à json, et prévoir la conversion des données existantes. Encore une décision parfaitement valable à l’époque (le type array était l’usage courant) devenue une dette le jour où Doctrine a tourné la page.

Une fois que tout est ok, on y est ! Yalla !!! Pour le moment …

Ce que cette histoire dit de la dette technique

S’il faut retenir une chose : sur un projet perso laissé plusieurs mois, ce n’est presque jamais le code métier qui pourrit en premier. C’est l’écosystème autour — gestionnaire de paquets, outil de déploiement, version de PHP, un bundle oublié, une variable dans un fichier de cache, un type de colonne Doctrine — qui se désynchronise petit à petit, une décision correcte à la fois, jusqu’à ce que la dépréciation d’un seul maillon fasse tomber tous les autres.

Le deploy.php en est le symbole : chaque ligne avait une raison d’être, à un instant T, et il a fallu les relire une à une, des années plus tard, pour savoir lesquelles étaient encore vraies. C’est sans doute le vrai travail de fond de la maintenance : pas ajouter des fonctionnalités, mais se demander régulièrement si les décisions figées dans la config tiennent toujours.

La suite de ce chantier est déjà sur ma liste : cette fois-ci terminer pour de bon la librairie et enrichir le site de démo, avec les tests. Mais ça, c’est une autre histoire.

Mes notes du Symfony Live – Jour 2

Vous avez dit Symfony 6.1? – Nicolas Grekas

Ici, un autre « core dev » de Symfony pour cette première conférence, qui nous rappelle qu’au sein même d’une même version majeure, il y a promesse de rétrocompatibilité … D’une version majeure à une autre, le chemin de migration continue. Il est conseillé de faire un composer update tous les mois; et tous les 6 mois, un composer update pour passer en version mineure suivante, pour corriger les dépréciations.
Il est déconseillé de commencer ou rester sur une version LTS, pour ne pas rester « lent » ou « stable ».
En 6.1 on a droit à :
– une modernisation du code via nouvelles syntaxes / fonctions
– une meilleure description des types natifs et génériques
– une simplification par abandon : SwiftMailer, Twig, FlexServer
– une revisitation des classiques : composer runtime, symfony new (l’option –-full pour le website skeleton devient –website)
– des serveurs à longue durée d’exécutions
– une performance accrue.
Retour sur le nombre de PR : 6PR/jour en 12 mois soit 2000PR : 400 features, 700 bugs, +900 mineures)
Je vous laisse le loisir de regarder la rediffusion de la conférence pour avoir l’aperçu complet, et non exhaustif de cette mise à jour !

Mon avis : un bon tour de découverte de cette nouvelle version de SF, bien hâte de la tester en fait !

Code asynchrone dans une application Symfony synchrone – Jérôme Tamarelle

Petit rappel : Composer utilise depuis la version 2 des composants de ReactPHP, et peu de gens le savent.
Il est question de ces différents concepts :
1. I/O non bloquants
Fonctions natives de PHP pour les flux non-bloquants : stream_select, curl_multi_select, Symfony HTTPClient est non-bloquant.
2. Délégation de traitement à des processus parallèles
Effectuer des traitements en parallèle sans bloquer le processus courant, avec Sf Process ou encore Messenger
3. Branches de code concurrentes dans un même thread
Ici , on parle de promesses, coroutine, et fibers pour exécuter plusieurs branches de code en concurrence, sur le même thread.
A retenir :
– Une fonction synchrone peut appeler une fonction synchrone mais pas une fonction asynchrone.
– Une fonction asynchrone peut appeler une fonction synchrone et une fonction asynchrone.

Mon avis : je suis peu familiarisée avec ces concepts. Intéressant à découvrir par la pratique, peut-être avec un projet futur.                                     

Développer une application web décentralisée avec Symfony et API Platform – Kevin Dunglas

SAUVONS LE WEB !!! DECENTRALISONS !!! Tel est le message de cette conférence de Dunglas, connu de tous pour être un des core devs de Api Platform.
Les GAFAM représentent 57% de tout le trafic mondial, la majorité de l’usage du web passe par ces quelques acteurs-là, et leur donne un contrôle quasi total des contenus des internautes. Ce qui était à des milliards de lieues de la volonté de Tim Berners-Lee quand il a crée le web ! Au départ il voulait vraiment créer un espace de libre expression pour les gens qui ne pouvaient pas le faire … le Web devait donner une voie aux personnes marginalisées, les autres médias étant réellement contrôlés, tous ceux qui avaient quelque chose à dire pouvaient le faire, via les URL, qui relient les sources les unes aux autres (on a donc un graphe de connaissances). Or les GAFAM donnent donc pas mal de problèmes de démocratie, vu qu’ils censurent à tout-va.
Aux origines, le Web 1.0, tout était décentralisé.
Puis le Web 2.0 (terme marketing) est arrivé, permettant aux utilisateurs de bénéficier des formulaires pour publier du contenu, créer des mixs de communautés. On a vu apparaitre les API web, ainsi qu’un mix d’acteurs non-commerciaux et commerciaux, ça se passait plutôt bien. Mais l’argent est entré en jeu.
Puis on voit naitre le Web 3, la promesse : re-décentraliser le web, casser le pouvoir des états surveillants. On propose d’utiliser la blockchain d’Ethereum et d’exécuter ce code sur les ordinateurs du réseau pour faire des sites web. Donc base sur les cryptomonnaies, trustless et permissionless. Sauf que des problèmes se posent : consommation énergétique (Ethereum toujours en POW), d’énormes structures américaines sont intéressées par le concept, chaine de Ponzi.
Donc, voici le concept du Web 3.0, apparu avec le web sémantique, crée par Tim Berners-Lee, qui consiste à utiliser le web comme immense base de données, le web est donc considéré comme base de connaissances, les opérations auraient une grande interopérabilité.
Pour cela on utilise un modèles de données abstrait pour représenter n’importe quel set de données : RDF. Mais on a d’autres formats : tableau HTML, N-Tuple dans un fichier texte, Turtle, JSON-LD.
Tim Berners-Lee présente son projet : SOLID. Les considérations physiques, politiques, etc … dont détaillées dans des documents émis en dur. Solid Toolkit permet de créer des application décentralisées et interopérables, la décentralisation des données personnelles, etc …
A mentionner qu’on peut construire une application Symfony avec Solid. Dunglas propose son bundle.

Mon avis : je suis TRES sensibilisée sur sujet, et j'avais envie, avant la pandémie de faire un gros projet lié au web, son évolution et sa centralisation, hélas remis à plus tard. L'idée est interessante et je vais me pencher sur le sujet, et regarder Solid d'un peu plus près ... mais toutefois, je me pose des questions. N'est-il pas mieux de sensibiliser les gens à faire leur propre site internet, comme avant ? et de créer l'arborescence de leur communauté à cet effet ? Leur faire comprendre ce qu'est le logiciel libre ? Si Dunglas passe par là, je serai ravie qu'il puisse répondre à mon interrogation ;) !

Mon avis global sur le séminaire

Une joie de retrouver tout ce beau monde et l’ambiance des séminaires sans entendre les mots maske aubligatouar, pace vaksinale et tout le toutim. J’aspire vraiment à retrouver la vie active d’avant que je menais sur ce point-là. Les différents stands étaient attractifs, et je suis bien contente d’avoir eu un beau t-shirt en cadeau, et un autre gagné sur le stand des Tilleuls.coop. Il faut juste que je pense la prochaine fois à imprimer mon badge avant de venir ! Les thèmes des conférences étaient variées, et la plupart étaient bien intéressantes et donnent envie de creuser le sujet abordé.
Sinon … gros bémol pour les déjeuners. J’adorais le concept du buffet proposé lors de ma dernière venue, qui permettait un éventail varié. Je fais très attention à ce que je mange, surtout quand je ne trouve pas le temps de faire du sport, que je mange au resto le soir avec ma chérie + les copains que je n’ai pas revus depuis un bail, et que j’ai de gros problèmes avec certains aliments (comme le fromage). Là c’était vraiment bourratif, trop riche et pas varié (sérieux les gars, un gâteau au Daim ? Pas un seul fruit !). Le souci c’est que le tarif du SfLive est plutôt conséquent, et pour moi chaque centime compte, la nourriture en fait partie. Et qu’on ne me parle pas de « raisons sanitaires », vu qu’on reste toujours tous mélangés dans les différentes salles … mais bon, on va me répondre qu’on n’a pas eu le choix, c’est ça ou rien … Oui, je suis exigeante 😀 !

Mon projet de refacto BioPHP – Partie 1

J’aime bien la biologie, depuis ma tendre enfance. Je pensais même pouvoir un jour pouvoir faire de la bioinformatique mais j’ai vite été lassée des études en fac, donc j’ai dû faire un peu une croix sur ce projet.

Then, I left to Marseille, tout ça …

Mais dernièrement, j’ai eu pas mal de remous professionnels, quitté la dernière entreprise où j’étais en CDI (plutôt rageusement). J’ai donc passé quelques mois à construire des relations avec des clients, privilégiant l’approche freelance, et j’ai également trouvé un projet perso qui puisse m’occuper et approfondir mes connaissances en Symfony. Parce que depuis Spark, j’ai horreur d’être inoccupée. Et puis quand mon chat est mort, il a fallu un échappatoire pour penser à autre chose. Et puis j’avais une revanche perso à relever (n’est-ce pas, Guillaume ?).

J’ai trouvé donc le projet BioPHP (qui est sans URL Fixe, y’a un site ici aussi), cherchant une librairie de bioinformatique en PHP, par curiosité après avoir vu que des librairies du genre existaient en GO. Et … comment dire ? Voir des 2003, dernières mises à jour en 2009 peut-être, des fonctionnalités en PHP4, du HTML, du PHP, des data mélangées dans le code. Aucun design pattern, du code spaghetti, des fonctionnalités qui se retrouvent un peu partout. Ouch. Pourtant les fonctionnalités sont intéressantes et offrent du potentiel, les algorithmes sont hyper poussés. Cette librairie a été développée par d’excellents biologistes qui ne sont pas familiarisés avec le dev. D’ailleurs en poussant la recherche, le projet est critiqué et mal noté.

L’idée de départ des gars était super sympa : intégrer une librairie pour créer des applis de bioinformatique (plutôt orientées génétique) en PHP, comme il en existe en PERL, Go, Python … Mais il est vrai qu’à l’époque, à moins de télécharger bêtement et utiliser les fonctions require, on ne pouvait pas faire grand-chose d’autre. A part peut-être les intégrer dans PEAR ou PECL, chose que je n’ai jamais faite, donc je ne connais pas le process. PHP était vraiment à l’époque le vilain petit canard du monde des langages, et c’est à ce niveau qu’on voit son évolution fulgurante. Car aujourd’hui les outils ont évolué, nous avons les frameworks, Composer, Packager, Github, les tests unitaires et fonctionnels … en un coup de « composer install », on peut intégrer une librairie.

Comment procéder quand on est face à ce genre de challenges ? A vrai dire je n’ai pas vraiment établi de plan d’action dès le départ, peut-être une mauvaise idée, mais je me suis dit que c’était un projet pour lequel j’avais tout mon temps, et le droit à l’erreur. Car ce projet pris sur mon temps perso a été commencé en février dernier. En gros voici comment je suis en train de procéder :

Fonctionnement global de la librairie est des éléments-satellites après la refacto
  1. Installation de Symfony, création de 3 bundles : AppBundle (core de l’appli, MinitoolsBundle (qui intègre des outils qui pouvaient utiliser la librairie), et DatabaseBundle, qui m’a servi de « bundle temporaire » pour créer mes classes entités, raccordées avec Doctrine (parce que franchement, la classe seq.php … non, non, et non).
  2. Premier coup de machette dans le code. Nettoyage des fonctionnalités, renommage des variables suivant leur type, restructuration des classes originales. Séparation des classes-entités de ce qui sera intégré dans des services.
  3. Mise de côté des data. D’abord j’avais pensé à les inclure en YAML dans des paramètres, très mauvaise idée. Base de données ? Je voudrais plus la réserver à l’appli, pour moi la BDD ne doit servir qu’au stockage des séquences. Reste une idée sympa, les intégrer dans une API, ce que je voulais construire depuis longtemps pour utiliser API-Platform, un outil performant qui permet de pouvoir créer facilement ses propres API Rest. Le plus : on peut créer une couche interface qui permettra de créer un adapter, et donc de changer d’API, au cas où, tout en implémentant le modèle requis. Même si le CRUD n’est pas à 100% respecté. Les données de l’API ne sont pas destinées à être modifiées, dans l’immédiat.
  4. Refacto des « Minitools » pour bien m’imprégner de la logique de ces outils, création de l’interface web en TWIG, des services correspondants : je repère les duplicatas, crée de nouveaux services qui pourront être utiles dans le core. Car à la base, ces tools devaient intégrer les fonctionnalités de BioPHP. De mon côté ils me permettent aussi de me replonger dans les fondamentaux de la biologie : qu’est-ce qu’une protéine ? un acide aminé ? un enzyme etc …
  5. Refacto en test manuel : depuis le temps, les fonctionnalités sont en friche, donc je les fais remarcher, petit à petit, en continuant le labeur de nettoyage et de refacto. Je repère ainsi les fonctionnalités qui seront intégrées dans les « Minitools ». Cette partie sera désolidarisée (renommée en BioTools, pour caler au nommage général) du package à terme, pour des raisons logiques : le multi-repo c’est le bien, je vous dis.
  6. Tests unitaires du core : maintenant que je sais que ça marche, je repasse en mode « semi-TDD » et intègre les tests unitaires. Car un gros travail de refacto m’attend : la définition des différents design patterns de AppBundle. -> je suis ici \o/ .
  7. Découpage du projet pour en créer un projet Packagist. Et certainement montée en version de Symfony, redéfinition des dépendances d’injection etc …
  8. Création des tests unitaires et fonctionnels (avec Behat) de BioTools, refacto et envoi à Packagist.

Au prochain épisode, je rentrerai plus en détails sur la refacto d’AppBundle et des design patterns choisis.

Ha oui, et sinon, toutes les infos du projet sont là … Amelaye’s BioPHP.