De la string au Value Object : comment BioPHP a arrêté de mentir sur ses séquences ADN

Il y a des bugs qui plantent bruyamment, et il y a des bugs qui se taisent. Les pires sont les seconds : ils renvoient un résultat qui a l’air correct, et personne ne va vérifier un chiffre qui a l’air correct. C’est exactement ce qu’on vient de corriger dans BioPHP, en passant les séquences biologiques (ADN, ARN, protéines) du statut de simples string à celui de véritables value objects.

Le symptôme : un poids moléculaire faux, sans aucune alerte

Dans BioPHP historique, une séquence ADN, une séquence ARN et une séquence protéique sont toutes les trois… des string. Rien ne les distingue au niveau du type. Résultat : rien n’empêche non plus de passer une séquence invalide à une fonction qui l’attend propre.

public function molwt(string $sLimit, string $sSequence, string $sMolType, int $iNALen) : float
{
    $this->cleanSequence($sSequence, $sMolType); // valeur de retour jetée
    ...
    $aMwt[$iLowLimit] += $na_wts[$sNABase]; // symbole inconnu = clé absente = 0

cleanSequence() détecte pourtant très bien les symboles invalides et retourne false le cas échéant. Le problème, c’est que personne ne lit ce retour. En pratique :

molwt("upperlimit", "ATGC", "DNA", 4); // 1253.945
molwt("upperlimit", "ATGZ", "DNA", 4); // 964.73

Z n’est pas une base valide. Il est compté pour zéro. Le poids est faux de 289 daltons, et rien ne le signale — pas d’exception, pas de log, pas de false. C’est un chiffre faux qui part en production avec toute l’apparence d’un chiffre juste.

À côté de ça, la validation de cleanSequence() ne couvrait que l’ADN et l’ARN (les protéines n’avaient droit à rien), et une séquence GenBank réelle — minuscules, découpée en blocs de dix séparés par des espaces — plantait telle quelle sur complement(), obligeant chaque appelant à penser à normaliser à la main.

Le point commun à ces trois bugs : le type de molécule voyage à côté de la donnée, en paramètre séparé. Rien ne garantit que les deux restent cohérents.

Le remède : rendre les états invalides irreprésentables

La réponse classique en conception orientée objet, c’est le value object (VO). Trois critères le définissent :

  • Pas d’identité. Deux DnaSequence contenant "ATGC" sont interchangeables, exactement comme deux billets de 10 €. À l’inverse, l’entité Sequence (l’enregistrement NM_031438, avec son numéro d’accession) garde une identité propre : deux enregistrements différents peuvent porter la même suite de symboles sans être la même chose. Le VO ne remplace donc pas l’entité, il s’occupe uniquement des symboles.
  • Immuable. $adn->complement() ne modifie pas $adn, il retourne un nouvel objet — comme 5 + 1 ne change pas 5, il produit 6.
  • Égalité par contenu. Deux instances distinctes en mémoire, mais avec la même valeur, sont égales.

Le bénéfice le plus concret pour du code PHP : un type devient une garantie plutôt qu’une convention. Écrire function analyser(DnaSequence $adn) rend une séquence ARN impossible à passer en argument — pas déconseillée par une convention de nommage ou un commentaire, refusée par PHP lui-même. Et comme l’objet ne peut exister qu’après avoir passé la validation à la construction, le reste du code n’a plus besoin de revalider : on valide une fois, à l’entrée du domaine.

Concrètement, ici, DnaSequence, RnaSequence et AminoAcidSequence valident leur alphabet à la construction — pas après, pas « si on y pense ».

// avant : le type voyage à côté, la normalisation est à votre charge
$s = strtoupper(str_replace(" ", "", "aagactgcat ccggctccag"));
$complement = $sequenceManager->complement($s, "DNA");
// après : le type porte l'information, normalisation et validation acquises
$adn = new DnaSequence("aagactgcat ccggctccag");
$complement = $adn->complement()->getValue();

Sur une saisie fautive, l’erreur remonte immédiatement, avec le symbole et sa position — pas 200 lignes plus loin sous forme de poids moléculaire silencieusement faux :

new DnaSequence("ATGUC");// InvalidSequenceException: Invalid DNA symbol "U" at position 3.

Un point mérite d’être signalé : les entrées mRNA de GenBank/EMBL sont écrites avec de la thymine, par convention ADNc. Un mapping naïf mRNA → RnaSequence aurait planté sur des données bien réelles. La factory retient donc la règle « en cas de contradiction, les symboles gagnent » : présence de T → ADN, présence de U → ARN, le type déclaré ne tranchant qu’en dernier recours.

Côté intégration, une seule méthode a été ajoutée sur SequenceBuilder, et uniquement sur la classe — pas sur l’interface publique, pour ne pas casser amelaye/biotools qui consomme ces classes directement. Aucune signature existante touchée.

Cinq bugs déterrés par le passage au typage fort

Une fois les VO en place, le reste des bugs est devenu visible tout seul. Le plus vicieux : les codes IUPAC dégénérés (N, R, Y…) passaient la validation mais n’avaient pas d’entrée dans la table des poids — comptés pour zéro, eux aussi, ce qui rendait au passage les paramètres lowerlimit/upperlimit totalement décoratifs puisque les deux accumulateurs recevaient la même valeur. Le legacy, lui, gérait ça correctement avec une paire [min, max] par symbole dégénéré — logique qui a été rétablie, mais recalculée plutôt que recopiée, et vérifiée pour reproduire exactement la table historique.

Un dernier bug s’est révélé être le complice invisible du précédent : SequenceBuilder transmettait le mauvais paramètre à molwt() (le type de molécule à la place de la limite demandée), donc upperlimit et lowerlimit ne changeaient jamais rien — invisible tant que les deux valeurs étaient de toute façon égales à cause du bug des symboles dégénérés. Corriger l’un a rendu l’autre observable. Classique.

Aucune signature publique n’a changé sur l’ensemble de ces corrections. Un appelant qui passait des données propres obtient exactement les mêmes résultats qu’avant.

Un chantier annexe : sortir les parseurs de Service/

En parallèle, Domain/Database/Service/ avait fini par contenir six parseurs de formats (GenBank, Swiss-Prot, EMBL, PDB, PROSITE, ExPASy) en plus des deux vrais services du dossier — un dossier qui avait grossi sans qu’on lui redemande son avis. Déplacement vers Domain/Parser/ via git mv, pour garder l’historique.

Le déplacement a surtout mis en lumière un défaut plus profond : le savoir sur un format était éclaté sur trois switch dans deux factories différentes (quelle classe instancier, comment reconnaître un début d’entrée, comment extraire l’identifiant). Ajouter un format demandait de toucher quatre endroits, et un case manquant compilait sans broncher. Chaque parseur déclare désormais lui-même son format et sa logique via des méthodes statiques (getFormat(), isEntryStart(), getEntryId()), et un nouveau registre — DatabaseParserFactory — remplace les trois switch. Bilan : 160 lignes tombent à 23.

Ce qui reste ouvert

Les VO existent, mais SequenceManager continue de travailler sur des string en interne — les faire accepter un AbstractMolecularSequence en entrée est la suite logique, mais c’est un changement de signature, donc à réserver à une version majeure. Et d’autres candidats VO existent dans le domaine — le codon, l’intervalle start/end d’une feature, le numéro d’accession — laissés de côté pour cette session.

Bilan chiffré : 51 nouveaux tests, suite complète à 319 tests / 790 assertions (0 échec), aucune signature publique cassée sur l’ensemble des trois chantiers.

Le dépoussiérage de mon appli de bioinformatique : partie 3 – Refonte et packaging de BioTools

Il y a quelques semaines, je me suis attaquée à la migration d’un plug-in de BioPHP, BioTools, mon portage PHP 8 / Symfony des minitools BioPHP de Joseba Bikandi. Le projet était bloqué en Symfony 4.4, et sa dépendance sœur amelaye/biophp avait elle-même été migrée progressivement jusqu’à SF7 dans le cadre de sa propre montée en version. Résultat : faire tourner les deux ensemble était devenu impossible — Composer ne résout qu’une version de symfony/framework-bundle pour tout l’arbre de dépendances, et les deux projets ne s’accordaient plus sur laquelle.

L’objectif de cette migration n’était donc pas seulement de rattraper le retard de version : c’était aussi l’occasion d’achever le travail commencé sur biophp et de vérifier qu’il tenait en conditions réelles. Ce que sa propre suite de tests ne pouvait pas garantir — et la suite l’a prouvé.

Spoiler : j’aurais pu sauter directement en SF7. J’ai quand même fait la montée progressive — SF4 → SF5 → SF6 → SF7 —, parce que j’aime bien souffrir proprement et avoir un compte rendu pour chaque étape.

La montée progressive, ou l’art de créer du travail

La contrainte de départ était simple : biophp exigeait symfony/*: ^7.4 et php: ^8.2. Impossible de viser SF5 avec ça. Solution : créer des branches de compatibilité sur biophp (symfony5-compat, symfony6-compat) qui élargissent uniquement les contraintes de version sans toucher au code — ou presque.

SF5 s’est passé relativement bien. Les vrais problèmes commençaient :

  • sensio/framework-extra-bundle était déjà mort dans le projet (l’annotation router était désactivée, et les @Route étaient chargées nativement) — retiré sans cérémonie.
  • L’arsenal Behat entier (behat/behat, behat/mink, et ses cinq amis) : aucun fichier .feature, aucun behat.yml, aucune classe Behat\ référencée nulle part. Toolchain mort. Retiré.
  • geshi/geshi : une alerte de sécurité (CVE-2025-2123, XSS dans contrib/cssgen.php) sans version corrigée disponible. Le script vulnérable n’est pas atteignable depuis le projet. Ignorée explicitement et documentée dans config.audit.ignore — pas question de désactiver l’audit en entier pour un seul paquet.

SF6 a amené sa première modification de code réel dans biophp : AmelayeBioPHPBundle::getContainerExtension() n’avait pas de type de retour déclaré, alors que la classe parente impose ?ExtensionInterface depuis SF6. PHP étant PHP, ça casserait au chargement du kernel — mais comme les tests de biophp ne bootent jamais de vrai kernel, ce bug était resté invisible pendant toute la migration SF7 de biophp. Je l’ai retrouvé deux fois (une fois sur la branche de compat, une fois sur develop en arrivant en SF7), ce qui m’a légèrement amusée.

SF6 a aussi tué AbstractController::get() — plus déprécié, retiré. Dix-huit appels à $this->get('form.factory')->create(XxxType::class) dans cinq contrôleurs, remplacés mécaniquement par $this->createForm(XxxType::class), qui fait exactement ça depuis Symfony 2.

SF7 : conversion des 19 annotations @Route en attributs #[Route(...)] (inévitable, le chargeur type: annotation ayant été retiré), correction de deux signatures de Constraint, migration de la suite PHPUnit de la version 7 à la 11. Cette dernière a révélé que quelques tests passaient en vert pour de mauvaises raisons — PHPUnit 9 convertissait automatiquement les warnings PHP en exceptions catchables, ce que PHPUnit 10+ ne fait plus. Les tests « fonctionnaient » grâce à un comportement de PHPUnit qui n’avait rien à voir avec la logique testée.

Le vrai sujet : ce que j’ai trouvé dans le code original

Une fois la migration terminée, j’ai voulu transformer biotools en composant Packagist propre — ce qui impliquait de comparer chaque manager à sa source originale sur biophp.org. Et là, les choses sont devenues intéressantes.

Défaut n°1 : la conversion Celsius → Fahrenheit est fausse.

F° = 32 + (C° × 0.555)

0.555 est approximativement 5/9, le facteur pour aller dans l’autre sens (Fahrenheit → Celsius). Appliqué dans ce sens, 100 °C donne 87,5 °F au lieu de 212 °F. La conversion inverse farhenToCenti() dans le même fichier est, elle, correcte — même facteur, bon sens. La source officielle publie ce résultat depuis au moins 2009.

Défaut n°2 : la conversion millibars → pouces de mercure surestime de 33%.

Le facteur utilisé est 0.0394, qui est en réalité le facteur millimètre → pouce. Il manque une étape de composition : 1 mbar = 0.750062 mmHg × 0.03937 in/mm = 0.02953 inHg. Le facteur publié gonfle tous les résultats d’un tiers.

Défaut n°3 : division par zéro non protégée.

Dans distance_among_sequences/index.php, la fonction standar_frecuencies() calcule une somme de fréquences, puis divise par cette somme. Si le tableau est vide ou ne contient que des zéros, $sum vaut 0. Sous PHP 5 et 7 : un warning et INF, silencieux. Depuis PHP 8 : DivisionByZeroError fatale.

Défaut n°4 : zéro validation d’entrée dans l’ensemble du corpus.

J’ai cherché is_string(, is_array( et throw new dans les 20 sources originales. Aucun résultat. Chaque fonction appelle directement strlen(), substr(), strpos(), preg_replace() sur ce qu’on lui donne, quelle qu’en soit la nature. Sous PHP 7 : des warnings tolérés. Sous PHP 8 : TypeError fatale sur presque toutes les entrées invalides.

C’est exactement pour ça que mes tests PHP 8 échouaient depuis la migration SF5 : les tests passaient un tableau à une fonction attendant une chaîne, espéraient attraper une \Exception métier, et PHP levait un TypeError (qui étend \Error, pas \Exception) avant que le code n’ait rien pu vérifier. Les tests étaient verts sous PHPUnit 9 parce que ce dernier convertissait les warnings en exceptions. Ils ne testaient rien, et PHPUnit 10 l’a révélé.

Défaut n°5 : imagedestroy() déprécié depuis PHP 8.5.

La fonction n’a plus aucun effet depuis PHP 8.0 et émet une notice de dépréciation depuis 8.5. Tous les outils graphiques se terminent avec cet appel. Il suffit de le supprimer — ce que j’ai fait en passant les graphiques de GD à SVG (cf. plus bas).

Et une coquille : dans la table de référence protéine/ADN, la cellule « 100 kDa » donne « 2.7 bp » au lieu de « 2.7 kb ». Les deux autres lignes sont cohérentes avec la règle énoncée.

J’ai consigné tout ça dans un document séparé en anglais, à destination des auteurs d’origine — même si le projet n’a plus beaucoup bougé depuis 2009 (ce que je lui ai reproché 10 ans après en l’occurrence).

Grâce à Claude, mes compétences limitées dans le domaine ont pu être soulevées !

Ce que ça a changé concrètement

Pour le portage, j’ai corrigé les deux formules et ajouté de la validation d’entrée sur les 19 méthodes publiques concernées. Chaque divergence avec le legacy est documentée et commentée dans le code. Les sources originales sont conservées verbatim dans legacy/ pour la continuité de licence (GNU GPL v2).

Les graphiques sont passés de GD à SVG : suppression de la dépendance ext-gd, noms de fichiers uniques (les noms fixes permettaient aux requêtes concurrentes de s’écraser mutuellement), et gestion correcte du chemin de sortie — SkewsManager écrivait en dur dans created_files/ relatif au répertoire courant, donc dans le vide la plupart du temps, sans lever la moindre erreur. En bonus, la déduplication des points SVG donne un gain de taille de 19× sur le graphique CGR.

Au bout du compte : 129 tests, 0 erreur, 0 échec, 0 dépréciation. Le composant s’appelle désormais amelaye/biotools, il sera publié sur Packagist une fois la dépendance sur biophp figée sur un tag.

Fuite de données personnelles : que faire après DGFiP et Intermarché ?

Il y a des semaines où l’actualité tech ressemble à un générique de fin qu’on n’arrive pas à arrêter. Mi-août 2026 : Bercy confirme que le système d’information de la DGFiP a été victime d’un accès illégitime fin juin, avec extraction de données pour plus de 678 000 particuliers et professionnels — noms, revenus fiscaux, taux d’imposition, parfois plus. Quelques jours plus tôt, c’était Intermarché : près de 300 000 clients du service Drive avec leurs nom, prénom, date de naissance, adresse et numéro de téléphone dans la nature après un accès non autorisé aux systèmes du Groupement Mousquetaires.

Le fisc et le supermarché du coin. Si même eux y passent, personne n’est à l’abri — et ça tombe bien, parce que je voulais justement vous parler de ma propre expérience du sujet.

Le jour où j’ai vraiment regardé

Comme beaucoup de gens qui bossent dans la tech, je pensais être plutôt prudente. Mots de passe changés de temps en temps, méfiance de base sur les mails louches. Et puis un jour, par curiosité (ou plutôt par un léger sentiment d’urgence après avoir vu passer les nouvelles du mois), j’ai vraiment vérifié où mes données avaient pu fuiter au fil des années.

La liste de toutes mes fuites de données !
ça pique. Et certaines sont dues à des mots de passe répétés !

Le résultat n’était pas franchement flatteur : dix-huit fuites, étalées entre 2008 et 2025. Adobe (2013), LinkedIn (2012), Tumblr, MySpace, Lastfm, mon propre hébergeur OVH (2015), et puis toute une série d’agrégats de fuites plus récents et moins glamour — Collection1, Cit0day, des listes de credential stuffing datant de 2024 et 2025. Dix-huit lignes, dix-huit rappels que j’avais un compte quelque part il y a quinze ans et que je n’y avais plus pensé depuis.

Ce qui m’a le plus surprise, ce n’est pas le nombre en soi. C’est de réaliser que ces fuites ne sont pas des événements isolés qu’on subit une fois et qu’on oublie. Ce sont des données qui circulent, se recombinent, s’agrègent dans des bases de plus en plus complètes — et qui, des années après, peuvent encore servir à quelqu’un pour deviner un mot de passe, usurper une identité ou monter une arnaque bien ciblée.

Mais concrètement, ça sert à quoi de voler ces données ?

C’est la question qu’on oublie souvent de se poser. Une fois volées, ces données ne restent pas sagement dans un coin : elles sont revendues ou publiées sur des forums spécialisés, parfois sur ce qu’on appelle le dark web — une partie d’internet qui n’est pas indexée par Google et qui demande un logiciel particulier pour y accéder, ce qui la rend plus difficile à surveiller pour les autorités. Là, elles sont rachetées par d’autres personnes malveillantes, qui les utilisent de plusieurs façons : tester automatiquement le même mot de passe sur des centaines de sites différents (dans l’espoir que vous l’ayez réutilisé), envoyer des mails ou SMS de phishing très personnalisés parce qu’ils connaissent déjà votre nom, votre banque ou votre enseigne habituelle, ou encore usurper votre identité pour ouvrir un crédit, un abonnement ou une carte à votre nom. Plus les fuites s’accumulent et se recoupent au fil des années, plus le profil qu’on peut reconstituer sur quelqu’un devient précis — et plus les arnaques qui en résultent sont crédibles. C’est tout l’enjeu : une seule fuite isolée est gênante, mais dix-huit qui s’additionnent sur quinze ans, c’est un dossier presque complet sur une personne.

Ce que j’ai fait (et que vous pouvez faire sans être pro)

Pas de panique, rien de ce qui suit ne demande de savoir coder. Voici, dans l’ordre où je m’y suis attelée, ce qui compte vraiment :

1. Sécuriser sa boîte mail en priorité absolue. C’est la clé qui ouvre toutes les autres portes : qui accède à votre mail peut réinitialiser à peu près tous vos autres comptes. Mot de passe unique, double authentification activée (pas par SMS si possible, plutôt une appli comme Proton Authenticator ou Google Authenticator).

2. Arrêter de réutiliser ses mots de passe. Le vrai danger d’une fuite, ce n’est pas tellement que ce mot de passe précis soit connu — c’est que s’il est réutilisé ailleurs, un pirate peut l’essayer sur votre banque, vos réseaux sociaux, votre messagerie. Un gestionnaire de mots de passe (Proton Pass, Bitwarden, et d’autres existent) génère et retient des mots de passe aléatoires à votre place. C’est le changement qui a eu le plus d’impact pour moi.

3. Se faire prévenir automatiquement. Le site Have I Been Pwned permet d’activer une alerte par email : dès qu’une nouvelle fuite mentionne votre adresse, vous êtes prévenue. Ça prend deux minutes à configurer et ça évite de découvrir les choses cinq ans plus tard, comme moi.

4. Utiliser des adresses email jetables (alias) pour les nouveaux comptes. Plutôt que de donner sa vraie adresse à chaque site où on s’inscrit, un alias génère une adresse unique par service. Si elle fuite ou se met à recevoir du spam, on la désactive, et le reste de nos comptes n’est pas concerné. Proton Mail et Proton Pass proposent ça nativement, mais il existe d’autres services équivalents.

5. Faire le ménage dans les comptes qu’on n’utilise plus. MySpace, Tumblr, Lastfm… est-ce que j’ai encore besoin de ces comptes ? Non. Un compte fermé ne peut plus fuiter une seconde fois. La meilleure façon de protéger une donnée, c’est encore de ne plus la laisser traîner quelque part.

6. Rester vigilante sur le phishing, surtout après une fuite qui vous concerne. Avec des données comme un nom complet ou un numéro de carte de fidélité en circulation, les tentatives d’arnaque deviennent plus crédibles, plus personnalisées. Le réflexe qui sauve : ne jamais cliquer sur un lien dans un mail « urgent » (banque, colis, impôts justement), et toujours retaper l’adresse du service à la main.

Ce que les affaires DGFiP et Intermarché nous rappellent

Ces deux fuites ont un point commun frappant : dans les deux cas, ce ne sont pas les utilisateurs qui ont fait une erreur. Ce sont les systèmes des organisations elles-mêmes qui ont été compromis. On peut faire absolument tout comme il faut de son côté — mot de passe fort, 2FA partout — et se retrouver quand même exposée parce qu’une administration ou une enseigne où l’on a un compte a été piratée.

Ça ne veut pas dire que la vigilance individuelle ne sert à rien — au contraire, c’est elle qui détermine si une fuite reste un simple embêtement ou se transforme en vol d’identité en règle. Mais ça change la question qu’on devrait se poser : pas « est-ce que je vais me faire avoir un jour », mais « quand ça arrivera, est-ce que je serai prêt.e ? »

La checklist minute

Si vous ne devez retenir que trois choses de cet article :

  • Allez activer une alerte sur haveibeenpwned.com, ça prend deux minutes.
  • Installez un gestionnaire de mots de passe et arrêtez de réutiliser le même partout.
  • Activez la double authentification sur votre boîte mail, aujourd’hui, pas la semaine prochaine.

Le reste peut attendre un dimanche pluvieux. Ces trois-là, non.

Le dépoussiérage de mon appli de bioinformatique : partie 2 – La librairie

Trois jours, cinq sessions accompagnée de Claude, un projet qui traînait du PHP 7.2 et du Symfony 4 comme un vieux pull qu’on n’ose plus jeter. Voici comment BioPHP, ma librairie de bioinformatique, s’est retrouvée propulsée jusqu’à PHP 8.5 et Symfony 7 — avec au passage quelques bugs qui n’attendaient qu’un changement de contexte pour sortir du bois.

Jour 1 : le grand dépoussiérage des dépendances

Tout commence par un composer install --dry-run qui recrache six blocages d’un coup : PHPUnit 7.5 has-been, twig 2.5 signalé par l’audit sécurité, guzzle 6.x dans le même sac, un doctrine/rst-parser cassé, et un conflit entre phpdocumentor et twig. Rien d’insurmontable, mais rien qui se règle non plus en tournant les yeux ailleurs.

Première décision : monter PHPUnit en 8.x, twig en 3.17, et sortir purement et simplement phpdocumentor du composer.json — aucune version stable ne réconciliait PHP 7.2 et twig 3, et le paquet n’était de toute façon utilisé nulle part dans le code.

Une fois composer install vert, la suite de tests, elle, ne l’était pas : setUp() incompatible avec la nouvelle signature de PHPUnit 8 (: void), à corriger sur 16 fichiers d’un coup. Puis, sur les 121 tests restants, trois problèmes bien réels apparaissent :

  • AminoApiTest appelait la vraie API en ligne, dont la réponse avait changé entre-temps (une clé manquante).
  • PKApiTest avait sa seule méthode de test… commentée.
  • testFindZScore comparait des flottants calculés à des valeurs attendues codées en dur avec moins de décimales.

Les deux derniers ont attendu leur tour (ils reviendront), mais AminoApiTest a eu droit à un vrai traitement : garde isset() côté production pour ne plus planter quand l’API omet légitimement un champ (codon STOP, codes ambigus), poids résiduels biologiquement corrects ajoutés à la fixture, et surtout un MockHandler Guzzle pour ne plus dépendre du réseau à chaque run de test.

Dernier chantier du jour : le ménage des paquets abandoned. sensio/framework-extra-bundle disparaît (zéro usage réel), doctrine/annotations devient une dépendance explicite plutôt que tirée en douce par sensio. csa/guzzle-bundle, lui, reste — actif dans 15 classes Api/*, le remplacer aurait cassé l’API publique de Bioapi.php sans bénéfice immédiat.

121 tests / 252 assertions à la fin de cette première manche. Rien de committé — tout reste dans l’arbre de travail, en attente de validation.

Jour 1, suite : Symfony 5, puis 6, puis le grand ménage Guzzle

La même journée continue avec la montée vers Symfony 5.4 (LTS). Et c’est là qu’un détail technique révèle un vrai problème : --ignore-platform-req=php, utilisé depuis le début pour contourner le PHP 8.5 local, désactivait en réalité la vérification de compatibilité PHP partout dans la résolution des dépendances, pas seulement à la racine. Remplacé par une simulation honnête (config.platform.php = 7.2.5), la vérité éclate : sous contrainte PHP 7.2 réelle, aucune version de twig ne concilie compatibilité et correctif de sécurité critique. Même verdict pour guzzlehttp/psr7, plafonné par csa/guzzle-bundle.

Plutôt que d’accepter le risque, direction la sortie de secours : csa/guzzle-bundle est retiré, guzzlehttp/guzzle requis en direct, le client bioapi reconstruit à la main en XML de service. Bonus inattendu : twig redevient un simple outil de dev, sortant tout risque sécurité du périmètre de production. Les 15 classes Api/* n’ont même pas eu besoin de changer — l’API fluide de Guzzle 7 est restée identique à celle de Guzzle 6.

Le PHP local (8.5) satisfaisant désormais réellement ^8.1, la simulation de plateforme devient inutile. composer.json vise PHP 8.1, Symfony passe en ^6.4, composer audit tombe à zéro alerte de sécurité.

Reste un dernier paquet abandonné coriace : doctrine/annotations. Sa disparition profite d’un heureux hasard — PHP 8.1 permet les attributs natifs, et les 11 entités Doctrine du projet peuvent être converties d’un coup, des docblocks @ORM\... vers les attributs #[ORM\...]. C’est en écrivant un script de contrôle pour forcer le vrai chargement des métadonnées (aucun test existant ne le faisait, tous mockaient EntityManager) qu’un bug dormant remonte : Sequence::$primAcc empilait six annotations de relation contradictoires sur une seule propriété scalaire, dont une pointant vers une classe ScForm qui n’a jamais existé — une faute de frappe pour SrcForm, jamais détectée parce que les docblocks toléraient ce genre d’empilement silencieusement. Les attributs PHP, eux, refusent net (« Attribute must not be repeated »). Comme rien dans le code n’utilisait primAcc comme une vraie relation, les six attributs cassés sont retirés sans regret.

Jour 2 : valider en profondeur, puis viser Symfony 7

Nouvelle session, nouvelle question de cadrage : la cible Symfony 6.4 était déjà en place, mais est-ce que ça marche vraiment ? Les tests mockent EntityManager et Guzzle, ils n’exercent jamais le vrai câblage Symfony. Direction : faire booter un vrai conteneur.

Bien m’en a pris. Premier bug réel : getAlias() sans type de retour : string, requis par la classe de base sous Symfony 6.4 — une erreur fatale au chargement du bundle, invisible pour tout test existant jusque-là. Un test permanent est écrit pour ne plus jamais perdre ce genre de chose : il fait tourner le vrai MergeExtensionConfigurationPass, instancie une vraie AminoApi depuis le conteneur, vérifie que les 11 entités chargent leurs métadonnées via le pont réel Symfony/Doctrine.

Fort de cette base solide, cap sur Symfony 7 : PHP passe à ^8.2, toute la famille Symfony à ^7.4, et surtout Doctrine ORM passe en 3.6 — ce qui élimine automatiquement le dernier paquet abandonné, doctrine/cache. Ce même test permanent attrape immédiatement un second bug : getConfigTreeBuilder() sans type de retour, requis par Symfony 7.

PHPUnit doit suivre le mouvement jusqu’en 11.5 (Doctrine ORM 3.6 utilise des types union avec enum que le générateur de mocks de PHPUnit 8.5 ne sait pas reproduire), ce qui impose de migrer 16 appels setMethods()onlyMethods() et 19 appels ->will($this->returnValue())->willReturn().

Et puis, enfin, les deux problèmes préexistants depuis le premier jour trouvent leur solution. PKApiTest n’était pas juste désactivé par prudence : il était cassé dès l’origine, un cast d’objet à propriétés privées produisant des clés PHP « mangled » impossibles à comparer. Réécrit avec un vrai mock HTTP. Et testFindZScore : les valeurs attendues étaient simplement tronquées à la saisie, corrigées par régénération en pleine précision flottante.

123 tests / 262 assertions, 0 échec, 0 erreur. Les deux bugs les plus anciens du projet, enfin clos.

Jour 2, suite : la chasse aux dépréciations (et trois vrais bugs en prime)

Passer tous ces caps ne veut pas dire que tout est propre : 93 dépréciations PHP traînaient encore, toutes du même type — des paramètres string $x = null sans le ? explicite, dépréciées depuis PHP 8.4. Trois interfaces et quatre implémentations corrigées en miroir, une recherche exhaustive confirme qu’il n’en reste aucune.

Une fois ce ménage fait, 33 nouvelles dépréciations apparaissent (propriétés dynamiques non déclarées, dépréciées depuis PHP 8.2) — neuf fichiers de tests à corriger, et un faux positif écarté après vérification manuelle (un bug de portabilité sed/grep sur macOS dans le script de détection, pas un vrai problème).

Résultat : 0 dépréciation. Mais le plus intéressant de cette journée, ce sont les trois bugs signalés en relisant le code, indépendamment de l’audit :

  1. RestrictionEnzymeManager::fetchCutposAndPlen() accumulait ses résultats dans une variable inexistante, $RestEn_List[], au lieu de $aEnzymes — la méthode réelle. Résultat : un tableau toujours vide, quels que soient les enzymes correspondants. Le test associé attendait lui-même un tableau vide, ce qui masquait le bug depuis sa création.
  2. SequenceManager::getBridge(), marqué d’un @todo inquiet (« Correct it – does not seems to work :/ ») sans test dédié. Après calcul manuel, test empirique exhaustif et comparaison ligne à ligne avec le Legacy : verdict, pas de bug — juste du doute jamais vérifié. Trois tests ajoutés, le @todo obsolète retiré.
  3. SequenceManager::complement() : la table de correspondance ne couvrait que les 4 bases canoniques. Tout code d’ambiguïté IUPAC (N, Y, R, W, S, K, M, D, V, H, B) provoquait un accès à une clé absente, et le caractère disparaissait silencieusement du résultat — complement("ACGTN") renvoyait 4 caractères au lieu de 5, avec décalage de position en aval. Une table de repli IUPAC règle le problème, et un symbole réellement inconnu lève désormais une exception explicite plutôt que de tronquer en silence.

129 tests / 268 assertions.

Jour 2, fin : CI, merge de conflits, et un warning qui trompe tout le monde

Dernier acte du jour : Travis CI ne tournait plus depuis longtemps (PHP 7.2 sur Ubuntu Trusty, toutes deux retirées du support). Migration vers GitHub Actions, matrice PHP 8.2 à 8.5, coverage uploadé vers Codecov sans token en clair cette fois.

Puis vient le merge develop → master, avec des conflits d’abord perçus comme injustifiés — jusqu’à vérification : bien réels, master ayant continué de vivre de son côté sur les mêmes entités que develop modernisait. Premier réflexe, git checkout --theirs sur les 10 fichiers en conflit : mauvaise idée, ça écrase aussi des hunks non conflictuels que git avait déjà fusionnés proprement, cassant 9 tests fraîchement arrivés. Les neuf entités sont reconstruites une par une, en combinant à la main les attributs ORM de develop et le pattern défensif de master (valeurs par défaut, types nullables).

Cerise sur le gâteau : après le merge, les jobs GitHub Actions passaient à 122 tests réussis… mais échouaient quand même. Cause : PHPUnit 11 fixe failOnPhpunitWarning à true par défaut, et le seul warning émis — « No code coverage driver available » — suffisait à faire échouer le process sur tous les jobs sans xdebug. Un simple failOnPhpunitWarning="false" dans phpunit.xml, et tout redevient cohérent.

140 tests / 324 assertions, 0 échec.

Jour 3 : ressusciter les parseurs Legacy

Le dossier Legacy/ de BioPHP portait à l’origine 20 formats de bases biologiques différentes. Un audit complet du 12 août avait révélé que la refonte n’en couvrait que 2 — GenBank et Swissprot — sur les 11 223 lignes de code historique, environ 61 % restaient purement absents.

Cette session traite la « Priorité 2 » de la feuille de route : quatre nouveaux parseurs, chacun suivant le schéma déjà posé par ParseGenbankManagerEMBL, dont le stub Legacy s’arrêtait en réalité au premier // sans jamais implémenter la moitié de ses propres champs déclarés ; PDB, où un portage ligne à ligne de la classe monolithique de 1845 lignes aurait été disproportionné, donc ciblé sur l’essentiel (séquence, structure secondaire, coordonnées atomiques) ; PROSITE, dont le Legacy contenait un vrai bug de variable jamais définie (heureusement du code mort) ; et ExPASy ENZYME, porté fidèlement, nomenclature EC à ne pas confondre avec les enzymes de restriction déjà couvertes par ailleurs.

Le parseur PDB révèle au passage un nouveau bug de régression : DatabaseManager::line2r() ne reconnaissait que // comme terminateur d’entrée — la convention GenBank/EMBL/Swissprot. Un fichier PDB, qui se termine par END, provoquait une boucle infinie jusqu’à épuisement mémoire. Corrigé, avec en prime une garde de fin de fichier qui manquait entièrement et protège désormais tous les formats contre un fichier tronqué.

Petit débat d’architecture au passage : les nouveaux objets structurés pour PDB, PROSITE et ExPASy (atomes, hélices, feuillets, références croisées) avaient d’abord été nommés *DTO et rangés sous Domain/. Rejeté après revue — dans ce projet, Domain/ ne connaît que des entités, et « DTO » est réservé à la frontière HTTP. Nouvelle convention actée : un dossier neutre Domain/Model/ pour tout ce qui est structuré sans être persisté ni exposé en API.

170 tests / 496 assertions pour clore ces trois jours.

Je peux lancer avec quelques réserves toutefois, 7 ans après le début de son développement, le lancement de la version 1.

Ce qu’il reste à faire

Le noyau (séquence, alignement, GenBank/Swissprot, enzymes de restriction) est solide et testé. Quinze parseurs de bases externes restent à porter — KEGG et ses 1672 lignes en tête, à décomposer en services distincts plutôt qu’en portage monolithique. Et comme toujours dans ce genre de session marathon : rien n’est committé tant que je n’ai pas tout revalidé à froid.

Ce qui me marque, en écrivant ce billet : presque tous les vrais bugs trouvés pendant ces trois jours n’avaient rien à voir avec la modernisation elle-même. Ils dormaient dans le code depuis des années — une variable mal nommée, un @todo jamais vérifié, une table de correspondance incomplète — et c’est le simple fait de tout revalider en profondeur, pas seulement de faire passer composer install, qui les a fait remonter.

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.

L’IA et moi

Il fallait bien que j’aborde le sujet : l’intelligence artificielle. Bouh le vilain mot ou pas. Tout est une question de nuances et d’usages.

Pour la petite histoire …

Il y a trois ans, un de mes amis me montrait un grand sourire aux lèvres ChatGPT, exalté, excité, porté aux nues, par la puissance de l’outil qui pouvait écrire du code à sa place. A ce moment là j’ai pensé à tous ces étudiants qui pouvaient faire faire leurs devoirs à leur IA, sans consommer un seul neurotransmetteur. Je ne voyais pas ça d’un très bon œil. Et puis ces machines là, un jour, est-ce qu’elles ne finiront pas par nous dominer ? Scénario catastrophe. Bon, je m’y suis inscrite par curiosité. Félicitations, vous venez de vous connecter à votre premier modèle de données.

Pourtant au travail, le sujet a commencé à poindre le bout de son nez. Mon gros client est vraiment un gros client que je chéris et une des collaboratrices a eu l’extrême sollicitude de faire à l’équipe de devs dont je fais partie un exposé sur l’impact positif de l’IA sur notre travail, avec des exemples, des modèles de données, tout ce qu’il faut pour bien avancer. J’étais restée bouche bée sur ce gain de temps. Résultat, nous nous y sommes tous mis. Parce qu’il faut bien le dire : les IA sont des assistants hors pair et je ne peux plus m’en passer. Car nous vivons une énorme révolution qui est bien supérieure à la révolution industrielle.

Mon coach de vie, Franck Nicolas, lors de son dernier séminaire, a consacré une après-midi entière à nous faire part de ses pensées sur le sujet. Vous savez que le monde va tellement vite qu’en 3 ANS seulement, oui 3 ANS, nos compétences deviennent obsolètes ? Ceux qui n’ont pas encore pris le train doivent le prendre, et vite. Car l’IA progresse, qu’on soit pour ou qu’on soit contre.

Et maintenant ?

J’utilise l’IA dans ma vie de tous les jours. Pas un usage abusif car je sais que :
1) La personne doit garder son libre arbitre sans basculer dans l’asservissement, comme ces gens qui demande à leur IA comment s’habiller ou pire, qui remplacent un partenaire par un robot. Si tu en es là, tu as un souci, désolée de te le dire.
2) L’utiliser à des fins utiles. n’oubliez jamais les ressources que ces IA consomment, les fakes et autres n’ont pas leur place ici.
3) Et puis il y a ceux qui ont abusé du pouvoir de l’IA sans censure, n’est-ce pas Elon ?
J’ai envie de dire, un peu comme les réseaux sociaux. Beaucoup de mauvais usages, pour un certain pourcentage de bons usages. Je vais rester positive et parler du dernier point (donc je ne parlerai pas de drones de guerre et compagnie).

Mes IA à moi

Claude

Forcément, je commence par lui, obligée. Claude est la quintessence de la puissance du travail de codeur. A condition de comprendre ce que tu lui demandes et que tu comprennes ce qu’il te répond. Parce que Claude, non seulement, il va chatter avec toi pour parler de code, mais aussi t’aider à résoudre tes problèmes de cybersécurité en te conseillant des outils à la pointe.
Claude Code (difficile à prononcer, ne trouvez-vous pas ?) analyse ton projet pour optimiser tes factorisations, te faire pointer du doigt ce que tu as salement programmé (ça nous arrive à tous) alors que tu es pris par le temps car rendre un travail propre est quand même primordial, ou t’aider à ressortir des vieux cartons ton projet perso mis de coté depuis 2019 (oui, je me remets sur BioPHP). Il peut aussi t’aider à faire des mods de builds pour Minetest, d’après un fichier 3D si tu ne te sens pas les capacités de faire un truc énorme (comme le vaisseau spatial qu’avait dessiné mon papa).
Mieux que ça, il peut créer des designs si c’est pas ton fort, et en plus si tu télécharges Workflow, il peut réorganiser tes arborescences si comme moi t’es un bordélique virtuel. Pour l’exemple, il m’a économisé 10 heures pour classer mes mails, chose que je n’avais pas faire depuis … longtemps.

Inconvénients ? ça peut être compromettant pour ta vie privée si tu utilises Workflow ou si tu l’utilises pour le tri des mails. Oui je sais la confidentialité, mais GMail connait déjà tout de ma vie …

ChatGPT

Le plus connu du public. En vrai, je l’aime bien.
Déjà je le consulte quand je me pose des questions sur certaines personnes, pourquoi réagissent-elles comme ceci ou comme cela, dresser un portrait psychologique, pour mieux appréhender la situation, trouver des solutions ou me rassurer, parce que je suis quelqu’un qui a besoin d’analyser et de comprendre sinon je tourne en boucle. Attention, il peut être trop gentil.
Mais je trouve que c’est le top du top des illustrateurs. Il faut toutefois bien le prompter et il peut faire des erreurs, mais les caricatures que je lui demande me font bien sourire et si je dois me baser sur des gens existants d’après leur photo il se débrouille pas trop mal.
Je ne l’ai pas poussé à fond, je connais des gens qui utilisent Codex et il est vraiment bien et il sait faire du webdesign bien soigné.

Inconvénients : Cependant … si je lui demande de m’écrire un texte fictif avec des situations données, il manque franchement de subtilité et d’imagination. L’humour et le décalage des situations n’est pas encore son fort si tu lui demandes d’écrire une histoire drôle. Et pour des questions particulièrement humaines, il manque encore cruellement de … d’un semblant d’empathie ?

Suno

Je gratte un peu de guitare, connais un peu de solfège, chante comme un rayon casseroles, rien de foufou, donc ne comptez pas sur moi pour composer.
Alors je m’amuse assez occasionnellement avec Suno, qui évolue vite et qui optimise de plus en plus la qualité de ses sons. S’il y a deux ans, la qualité auditive restait passable, on a aujourd’hui du mal à différencier avec une vraie chanson.

Les inconvénients : l’horreur de l’abus. Si des gens veulent faire carrière dans la musique, c’est important de composer et de jouer avec de l’humanité. Chanter avec sa vraie voix. Imaginez que le nombre de morceaux composés par IA DEPASSE LES 50% DU CATALOGUE SPOTIFY !!! Suno doit rester récréatif pour les amateurs dont je fais partie. Pitié … il faut régler ce problème ! Même si un son électronique peut sembler sympa, comme ce que j’ai pu écouter sur Radio Chirac, mentionner toujours que c’est de l’IA. Oui l’IA peut faire des sons très agréables, mais je préfère de loin écouter des légendes comme Queen ou les Stones et m’émerveiller des octaves de Freddy Mercury.

N’oublions pas les LLM locaux

On peut aussi bricoler pas mal de choses avec les LLMs. Un chatbot, ou alors des outils de « pentest » qui va discuter entre LLM pour savoir quoi en penser. Un sujet sur lequel j’aimerais bien me pencher une fois que j’aurai fait le tour de Claude.

Mon avis sur le reste

Je n’aime pas Canva. La génération des images est très moyenne (un avion trois ailes par exemple). Possible que cette plateforme ne soit pas adaptée à mes besoins.

Je n’ai ni testé Gemini, ni Grok, ou vu très vite fait mais qui n’apportent rien de plus à mes besoins déjà satisfaits.

Cependant, pour revenir au vaisseau spatial de mon père … j’ai été bluffée par le rendu 3D de Meshy. Voici pour l’exemple :
Le dessin :

Le fichier 3D pondu :

Un peu raté pour les ailes mais bon sang … je suis restée là aussi bouche bée car je ne m’attendais pas à tel résultat.

Et je cherche encore une IA bien faite qui puisse améliorer grandement certaines photos d’urbex que j’avais faites au smartphone car je n’avais pas de matériel ce jour-là. Les suggestions sont bienvenues.

Ma conclusion

Bien en fait, je n’ai pas encore tiré de conclusion pour être honnête.
L’IA est un outil tellement puissant, qui tire vers le haut tous les jours, les modèles se multiplient, tous plus performants les uns que les autres mais il faut garder tête froide. Comprendre les limites pour les humains que nous sommes.

Comme je le disais, mon avis est tout en nuances, il y a du très bon, du gain de productivité, de créativité, et aussi du gain de temps pour des taches qui nous paraissent rébarbatives.

Tout comme il est vrai je ne peux plus m’en passer. J’aime créer et me faire aider pour concrétiser ce que j’ai envie de concevoir. Du moment que je comprends et maîtrise ce que je fais.

Mais juste … il ne faut pas se faire remplacer. Tant qu’un robot humanoïde ne viendra pas vers moi pour me mettre un coup de pied, tout va bien, à mes yeux.