jobsphp

Ma première contribution Symfony : le récit de Marc

Open Source & Outils. Ma première contribution Symfony : le récit de Marc

Une première contribution Symfony ne commence pas par un git commit. Elle commence par une recherche.

Ma première contribution Symfony: le récit de Marc

Dans les dépôts matures, la majorité des propositions inutiles échouent avant même la revue: le bogue est déjà signalé, un correctif existe, la branche visée est mauvaise ou le changement ne reproduit rien.

Le cas de Marc sert ici de fil technique, pas de biographie vérifiable. Aucun dépôt, aucune demande de fusion publique et aucun identifiant ne permettent d’attribuer une contribution précise à une personne portant ce prénom. En revanche, son parcours — repérer un défaut, isoler son impact, écrire un test, choisir une branche, soumettre une demande de fusion — décrit exactement ce qu’exige une contribution open source PHP sérieuse.

Symfony n’est pas un terrain de démonstration. C’est une base logicielle avec des contraintes de compatibilité, un historique de versions maintenues, une chaîne d’intégration automatisée et une revue qui doit pouvoir lire le changement sans reconstituer l’intention de l’auteur.

Une contribution utile réduit une incertitude: elle prouve un défaut, corrige un comportement et ne dégrade pas le reste.

Au-delà du code: les multiples visages de la contribution

Le réflexe classique consiste à réduire la contribution à un correctif dans src/. C’est une vision étroite. Un projet comme Symfony absorbe aussi des contributions qui ne modifient pas une ligne de PHP applicatif:

  • signalement de bogue reproductible, avec version concernée, résultat obtenu et résultat attendu;
  • tri de tickets déjà ouverts, notamment pour identifier les doublons ou les sujets devenus obsolètes;
  • revue de demandes de fusion, en particulier sur la couverture de tests, la compatibilité ascendante ou le périmètre réel du changement;
  • correction de documentation technique;
  • traduction de messages intégrés au framework.

Ce sont des contributions de maintenance. Elles diminuent le bruit dans le dépôt. Elles améliorent la capacité de l’équipe cœur à traiter les changements qui comptent.

Dans le cas d’un bug report, la valeur ne vient pas de la longueur du texte. Elle vient de la réduction du cas. Un rapport qui contient cent lignes de configuration, trois bundles externes et une base de données est coûteux à analyser. Un rapport qui produit le défaut dans un test ciblé devient exploitable.

Marc ne cherche donc pas d’abord une ligne à modifier. Il cherche un comportement observable. C’est la différence entre « j’ai trouvé quelque chose d’étrange » et « cette entrée produit X alors que le contrat du composant impose Y ».

Cette distinction a une conséquence directe sur la qualité d’une demande de fusion: le test n’est pas un appendice ajouté à la fin. Il est la spécification minimale du défaut.

Préparer le terrain: la recherche évite les contributions mortes

Le premier travail dans un processus de contribution GitHub PHP est documentaire. Symfony demande explicitement de rechercher les tickets et les demandes de fusion existants avant de commencer. Cette phase est peu spectaculaire. Elle évite pourtant les pires pertes de temps: refaire un correctif rejeté la veille, proposer une API déjà discutée ou rouvrir une décision d’architecture clôturée.

Pour un défaut donné, la séquence est mécanique:

1. Chercher le symptôme, pas seulement son interprétation. Les termes fonctionnels, le nom de l’exception, le composant concerné et le message d’erreur donnent souvent des résultats différents.

2. Lire les demandes de fusion ouvertes et fermées. Une proposition refusée contient fréquemment la contrainte qui manque au nouveau contributeur: compatibilité, contrat public, coût de maintenance ou conflit avec une évolution en cours.

3. Identifier le composant exact. Symfony est découpé en composants avec des responsabilités strictes. Corriger un effet dans un consommateur alors que la cause est dans une abstraction inférieure produit généralement une rustine.

4. Reproduire sur une version maintenue. Un défaut présent uniquement sur une branche non maintenue n’appelle pas le même traitement qu’un bug affectant une LTS.

5. Réduire le cas au minimum. Une reproduction courte permet de déterminer si le problème vient réellement du noyau Symfony, d’une configuration locale ou d’une extension tierce.

La recherche préalable ne sert pas à demander l’autorisation de contribuer. Elle sert à calibrer le changement. Un projet communautaire PHP maintenu à grande échelle ne peut pas accepter des demandes de fusion dont le périmètre reste ambigu.

Le point critique est la différence entre comportement surprenant et comportement erroné. Le premier peut relever d’une documentation insuffisante. Le second doit être démontrable contre un contrat, un test existant, une promesse de compatibilité ou une régression clairement localisée.

Le cycle de vie d'une Pull Request: de la branche cible aux tests

La branche cible: détail mineur en apparence, cause fréquente de rejet

Le choix de la branche conditionne tout le cycle de vie de la contribution. Dans Symfony, un correctif de bug doit viser la plus ancienne branche maintenue affectée par le défaut. Une fonctionnalité nouvelle doit viser la branche de développement définie par le projet.

Cette règle existe pour éviter deux problèmes coûteux:

  • corriger seulement la dernière version alors que les utilisateurs de la branche maintenue restent exposés;
  • injecter une nouvelle capacité dans une branche stable, ce qui compromet la promesse de compatibilité.

Le raisonnement n’est donc pas « quelle version j’utilise? », mais « quelle est la première branche encore maintenue où ce comportement est incorrect? ».

Nature du changementBranche cible logiqueConséquence attendue
Correctif d’un bogue présent sur plusieurs versions maintenuesLa plus ancienne branche maintenue affectéeLe correctif peut ensuite être reporté vers les branches plus récentes
Régression introduite récemmentLa branche où la régression apparaîtLe périmètre reste limité à la version concernée
Nouvelle fonctionnalitéBranche de développement indiquée par SymfonyAucune perturbation de la ligne stable
Dépréciation ou rupture de compatibilitéBranche prévue pour ce type d’évolutionDocumentation de migration obligatoire
Correction de documentationBranche correspondant à la documentation affectéeCohérence entre code, version et exemple

Symfony 6.4 reste une LTS avec une fenêtre de maintenance distincte des branches stables courantes. Au moment où les versions doivent être évaluées, les dates de fin de correction de bogues et de sécurité comptent davantage que l’étiquette « ancienne » ou « récente ». Une branche LTS n’est pas un musée: elle reste une cible légitime tant qu’elle est maintenue et affectée.

Une autre contrainte est la version de PHP. Les branches Symfony n’ont pas les mêmes planchers d’exécution. Écrire un correctif avec une syntaxe ou une API disponible uniquement dans un runtime plus récent peut invalider la compatibilité de la branche ciblée. Le typage strict ne dispense pas de vérifier la matrice de support; il rend simplement l’incompatibilité plus visible.

Le bon correctif sur la mauvaise branche est un mauvais correctif pour le projet.

Le test est le correctif avant le correctif

Une demande de fusion Symfony qui modifie du code sans test unitaire manque sa fonction principale: démontrer que le comportement change exactement là où il doit changer.

Le test doit échouer avant le patch et réussir après. C’est le critère. Un test ajouté après coup, qui serait déjà vert sur la branche de départ, ne prouve pas la correction du bug. Il ajoute éventuellement de la couverture; il ne verrouille pas une régression.

Marc doit donc construire un cas de test qui isole le défaut. En pratique, cela impose plusieurs décisions techniques:

  • choisir la classe de test située au plus près du comportement modifié;
  • utiliser les conventions de nommage déjà présentes dans le composant;
  • ne pas dépendre du réseau, de l’horloge système ou d’un ordre d’exécution fragile;
  • contrôler les fixtures et les valeurs aléatoires;
  • éviter de traverser dix couches du framework si une unité suffit à reproduire le défaut;
  • tester le contrat observable, pas l’implémentation privée.

Le test ciblé est la première exécution utile. Symfony illustre ce travail avec une commande du type php./phpunit src/Symfony/.... L’objectif n’est pas d’exécuter immédiatement toute la suite. L’objectif est de réduire le cycle édition–exécution–diagnostic.

Un échec de test peut venir de quatre zones différentes:

1. Le scénario est faux. La reproduction ne correspond pas au bug réel.

2. L’attente est fausse. Le comportement observé est peut-être celui que le composant garantit déjà.

3. Le patch est incomplet. Une condition limite, une normalisation ou un chemin d’erreur reste non traité.

4. L’environnement est incohérent. Dépendance, extension PHP, version de runtime ou fixture ne correspondent pas au dépôt.

Ce diagnostic est plus utile qu’un test global lancé trop tôt. La suite complète intervient ensuite pour vérifier que la modification n’a pas contaminé une autre zone par couplage implicite.

Avec Symfony 7.4, certains tests peuvent aussi nécessiter la régénération de fichiers attendus via TEST_GENERATE_FIXTURES=1. C’est un mécanisme à manipuler avec précision. Régénérer une fixture sans comprendre pourquoi elle change transforme une sortie de test en donnée opaque. Une fixture modifiée doit correspondre à une évolution légitime du contrat, pas masquer une divergence.

Une demande de fusion lisible est une demande de fusion révisable

La revue ne lit pas seulement le diff. Elle évalue le coût futur du changement. Un contributeur qui veut devenir contributeur open source sur un projet PHP majeur doit optimiser ce coût.

Symfony demande des commits atomiques, séparés logiquement, avec des messages explicites. Cette exigence n’a rien d’esthétique. Elle permet d’examiner, de tester et éventuellement d’annuler une partie de l’historique sans dissoudre l’intention dans un commit fourre-tout.

Un historique propre peut séparer:

  • l’ajout du test reproduisant le défaut;
  • le correctif fonctionnel;
  • une mise à jour documentaire nécessaire;
  • un ajustement de fichier de migration ou de journal des changements.

À l’inverse, combiner une correction de bug, un renommage massif, un reformatage du composant et une optimisation hypothétique de l’allocation mémoire est une mauvaise stratégie. La revue doit alors distinguer ce qui est requis de ce qui est opportuniste. Elle devient lente. Le risque de régression augmente. Le diff est plus difficile à reporter entre branches.

Symfony demande aussi d’éviter les corrections de style sans rapport avec la modification proposée. C’est une discipline de maintenance. Les changements de formatage gonflent le diff, gênent git blame, créent des conflits et réduisent la visibilité du vrai patch.

La licence impose également une frontière nette: le code soumis doit être proposé sous licence MIT. Sans cette compatibilité, il ne faut pas envoyer la demande de fusion. Le projet ne peut pas absorber un morceau de code dont le statut juridique reste incompatible avec sa distribution.

Le titre et la description ne sont pas du remplissage

Une demande de fusion doit répondre rapidement à trois questions:

  • Quel comportement était incorrect?
  • Quel changement corrige ce comportement?
  • Quelle preuve automatisée garantit que le défaut ne revient pas?

La description doit aussi signaler les limites. Si le patch ne traite qu’un cas précis, il faut l’écrire. Si une compatibilité particulière a été vérifiée, il faut la nommer. Si un changement modifie une API publique, l’omettre revient à déplacer le coût de découverte sur le relecteur.

Le niveau attendu n’est pas littéraire. Il est opérationnel. Un mainteneur doit pouvoir reprendre le contexte après plusieurs jours et comprendre le mécanisme sans extraire mentalement l’information de quinze commentaires.

Standards et automatisation: respecter les exigences de l'équipe cœur

À l’ouverture d’une demande de fusion, Symfony déclenche des vérifications automatisées. Le style est contrôlé par PHP CS Fixer. Les tests sont exécutés sur les versions de PHP et systèmes d’exploitation pris en charge. L’analyse statique peut s’appuyer sur PHPStan et Psalm.

Cette chaîne ne remplace pas la revue. Elle supprime les échecs mécaniques avant que le temps humain ne soit consommé.

Les standards de code Symfony s’appuient notamment sur PSR-1, PSR-2, PSR-4 et PSR-12. La présence de normes ne signifie pas qu’il suffit de lancer un formateur. Le formateur traite la présentation. Il ne valide ni l’architecture, ni la stabilité d’une API, ni le coût algorithmique, ni les effets de bord.

PHP CS Fixer peut appliquer automatiquement les corrections de style avec une commande telle que php php-cs-fixer.phar fix. Il faut cependant relire le diff. Une automatisation qui produit une modification inattendue n’est pas neutre; elle doit être comprise ou retirée.

Le même principe vaut pour l’analyse statique. Un passage vert de PHPStan ou Psalm ne garantit pas l’absence de bug fonctionnel. En revanche, il détecte des incohérences de type, des chemins impossibles, des appels potentiellement invalides et des contrats mal alignés. Sur une base PHP avec typage croissant, ces outils réduisent la surface des erreurs qui ne devraient jamais atteindre la revue.

La séquence efficace est simple:

1. lancer le test ciblé jusqu’à obtenir une reproduction stable;

2. appliquer le correctif minimal;

3. relancer le test ciblé;

4. exécuter le formateur;

5. lancer les outils d’analyse et la suite pertinente;

6. relire le diff final comme si chaque ligne devait être défendue séparément.

Cette dernière étape est souvent négligée. Elle détecte les imports inutiles, les modifications de fixtures involontaires, les fichiers générés et les changements de style hors périmètre. Le dépôt ne juge pas l’intention. Il intègre des octets.

Un pipeline qui passe en vert ne prouve pas que le changement est juste. Il prouve que la machine n’a rien trouvé d’évident. La nuance reste humaine.

Maintenir la compatibilité: gérer les cycles de vie des versions LTS et stables

La maintenance d’un projet PHP communautaire n’est pas seulement une question de correction immédiate. Symfony porte une contrainte forte: ne pas casser les consommateurs sans mécanisme de transition.

Une méthode publique, une signature, une exception, une option de configuration, un comportement de sérialisation ou un ordre de priorité peuvent constituer un contrat, même si la modification semble localisée. Changer un détail interne est simple. Modifier une hypothèse utilisée par des milliers d’applications ne l’est pas.

Lorsqu’un changement introduit une dépréciation ou casse la compatibilité ascendante, Symfony demande la mise à jour des fichiers CHANGELOG et UPGRADE concernés. Ce n’est pas une formalité éditoriale. Ces fichiers constituent l’interface de migration pour les équipes qui suivent le framework de version en version.

Une nouvelle fonctionnalité peut également nécessiter une demande de fusion documentaire associée. Le code seul ne suffit pas si l’API n’est pas découvrable, si son comportement limite n’est pas défini ou si son usage correct dépend d’une convention implicite.

Le bon niveau de prudence consiste à traiter les cas suivants comme des changements de contrat jusqu’à preuve du contraire:

  • modification d’un type de retour ou d’un type de paramètre;
  • changement de valeur par défaut;
  • ajout d’une exception dans un chemin auparavant silencieux;
  • changement de priorité d’un service ou d’un écouteur;
  • modification de l’ordre d’itération;
  • remplacement d’une structure tolérante par une validation stricte;
  • déplacement d’une responsabilité entre composants.

Le typage strict améliore la lisibilité des contrats. Il peut aussi transformer une tolérance historique en erreur d’exécution. C’est précisément pour cela que la compatibilité ne se déduit pas uniquement de la signature: elle se déduit de l’ensemble des hypothèses implicites qu’une API a laissé s’installer dans l’écosystème.

LTS, stables et fin de vie: arbitrer sans confusion

Une branche LTS n’a pas le même rythme qu’une branche stable. Les corrections de bogues y sont plus strictement encadrées, les ruptures y sont proscrites, et la fenêtre de support dicte la profondeur des interventions acceptées. Une branche stable récente accepte davantage d’évolutions, à condition qu’elles ne perturbent pas le cycle de release en cours.

L’erreur classique du contributeur débutant consiste à raisonner uniquement depuis sa propre version. Une équipe qui exploite encore une LTS 6.4 ne tirera aucun bénéfice d’un patch appliqué sur 7.x si le défaut existe aussi sur 6.4 et n’est pas corrigé sur cette branche. Le bon réflexe est de vérifier, pour le défaut observé, quelles branches maintenues sont réellement affectées, puis de proposer le correctif sur la plus ancienne d’entre elles. Les branches plus récentes seront mises à jour par l’équipe via les mécanismes de merge prévus à cet effet.

Les annonces de fin de support imposent un autre arbitrage. Une branche qui n’est plus maintenue n’est plus une cible recevable pour une contribution upstream. Le défaut éventuel doit alors être traité localement, documenté comme limitation connue, ou reporté sur la première branche maintenue où il se reproduit. C’est une discipline qui s’apprend surtout en lisant attentivement la politique de support officielle avant d’investir du temps sur un ticket.

Marc, en fin de parcours, n’a pas seulement ajouté quelques lignes à Symfony. Il a appris à traiter un défaut comme un objet technique complet: reproduction, contrat, test, branche, compatibilité, message, licence. Cette discipline est la même que celle qu’il appliquera, plus tard, aux contributions sur des bibliothèques métier, sur des outils internes ou sur le code de production de son équipe. Le framework n’est qu’un terrain d’entraînement exigeant; les réflexes acquis s’exportent. Une première contribution réussie ne vaut pas par sa taille. Elle vaut parce qu’elle prouve qu’un défaut a été transformé en un changement révisable, traçable, et durable.

Questions fréquentes

Pourquoi est-il nécessaire de chercher avant de proposer une contribution ?
La recherche préalable permet d'éviter de perdre du temps sur des correctifs déjà refusés, des API déjà discutées ou des décisions d'architecture déjà clôturées.
Comment choisir la bonne branche pour soumettre un correctif ?
Il faut viser la plus ancienne branche maintenue affectée par le défaut, afin de garantir que tous les utilisateurs des versions supportées bénéficient de la correction.
Pourquoi un test unitaire est-il obligatoire pour une demande de fusion ?
Le test sert de preuve automatisée que le comportement a été corrigé là où il devait l'être, tout en empêchant toute régression future.
Quelles sont les contributions possibles en dehors de la modification de code PHP ?
Il est possible de contribuer en signalant des bogues reproductibles, en triant les tickets, en révisant les demandes de fusion, en corrigeant la documentation technique ou en traduisant des messages.
Que faire si une correction de style est nécessaire dans le code ?
Il est recommandé d'éviter les corrections de style sans rapport avec la modification fonctionnelle, car elles gonflent le diff et gênent la lecture de l'historique git.