Passer au contenu
DedicatedPHP Contact

Faire évoluer des contrats PHP partagés sans bloquer les livraisons

Guide pour appliquer la rétrocompatibilité en PHP lors de la modification de composants partagés, de la migration des consommateurs et du retrait contrôlé d’API.

Diagramme éditorial de contrats PHP partagés avec adaptateurs, consommateurs et phases de migration

Une modification apparemment mineure dans une bibliothèque PHP partagée peut bloquer des livraisons indépendantes. Renommer un paramètre, modifier une valeur par défaut ou remplacer une exception peut casser un consommateur qui n’est pas déployé aujourd’hui, qui vit dans un autre dépôt ou qui invoque le composant indirectement. La défaillance peut apparaître à l’exécution, dans une tâche asynchrone ou lors de la désérialisation de données générées avant la modification.

La rétrocompatibilité en PHP ne consiste pas à conserver toute interface historique. C’est une discipline qui permet aux producteurs et aux consommateurs d’évoluer à des rythmes différents, avec une fenêtre de migration explicite et un retrait vérifiable. L’objectif est d’éviter à la fois les déploiements coordonnés forcés et l’accumulation permanente d’API obsolètes.

Identifier ce qui fait partie du contrat interne

Identifier ce qui fait partie du contrat interne — guía visual de DedicatedPHP

Un contrat interne est tout comportement dont dépend un autre module, même s’il n’est pas publié comme API externe. Les dépendances Composer et les interfaces PHP en constituent une partie visible, mais n’en épuisent pas la portée. Avant de modifier du code partagé, examinez au moins les éléments suivants :

  • Signatures publiques : noms de méthodes, paramètres, ordre, types, nullabilité, valeurs par défaut et type de retour.
  • Sémantique : ce que signifie chaque argument, quels champs sont obligatoires et quel résultat est attendu dans une condition donnée.
  • Erreurs : exceptions levées, codes d’erreur, messages traités par les clients et résultats nuls ou vides.
  • Données : clés de tableaux, structures JSON, messages de file, événements de domaine, fichiers sérialisés et données persistées.
  • Effets de bord : émission d’événements, écriture en base de données, invalidation de cache, appels HTTP et ordre d’exécution.
  • Comportement opérationnel : tentatives, idempotence, délais d’expiration et traitement des défaillances transitoires.

Par exemple, ajouter un champ à une réponse JSON est généralement additif, mais cesse de l’être si un consommateur valide une liste fermée de propriétés. De même, une exception plus spécifique peut être techniquement correcte, mais incompatible si le consommateur intercepte l’exception précédente afin d’activer une récupération.

Classifier le changement avant d’écrire l’implémentation

La classification évite qu’une décision de conception ne devienne un incident de production. Il est préférable de la documenter dans la proposition de changement, avec les consommateurs connus et la stratégie de sortie.

Changements additifs

Ils intègrent une nouvelle capacité sans modifier le chemin existant : une nouvelle méthode, un paramètre optionnel avec une sémantique neutre, un événement supplémentaire ou une nouvelle version d’un message. Ils sont à privilégier lorsque les consommateurs sont déployés séparément. La nouvelle voie doit pouvoir coexister avec la précédente et le comportement antérieur doit être conservé de manière vérifiable.

Changements compatibles avec adaptation

Ils permettent de conserver le résultat antérieur au moyen d’une couche de traduction. Par exemple, une ancienne interface peut déléguer à un nouveau service, en convertissant les arguments et les résultats. L’adaptation a du sens si elle est localisée, possède une date de retrait et ne masque pas une différence métier que le consommateur doit décider consciemment.

Changements incompatibles ou incertains

Supprimer une méthode, renforcer un type, modifier la signification d’un état ou changer un format persisté est généralement incompatible. Tout changement sans inventaire fiable des consommateurs doit également être traité comme incertain. Dans les deux cas, publier une nouvelle version du package ne suffit pas : une transition, une migration planifiée ou une version de contrat séparée est nécessaire.

Construire un inventaire vérifiable des consommateurs

Ne fondez pas la décision uniquement sur des recherches textuelles. Un composant peut atteindre un autre par l’intermédiaire d’un conteneur de dépendances, d’une configuration, de la réflexion, d’événements, de files ou d’une intégration HTTP. L’inventaire doit combiner des preuves statiques et une exécution représentative.

  1. Examinez les dépendances déclarées dans Composer, les contraintes de versions et les dépôts qui installent le package.
  2. Recherchez les utilisations directes de classes, interfaces, méthodes, événements, clés de configuration et formats de message.
  3. Inspectez les fabriques, les définitions du conteneur, les listeners, les commandes, les cron, les workers et les adaptateurs d’infrastructure.
  4. Identifiez les parcours critiques : encaissement, authentification, commandes, synchronisation, notifications et processus de récupération.
  5. Enregistrez pour chaque consommateur le responsable, la version utilisée, la voie de migration et la preuve qu’il a terminé le changement.

La publication d’une bibliothèque et le déploiement d’une application sont des actions distinctes. Publier une version compatible permet à chaque consommateur de se mettre à jour lorsqu’il est prêt ; déployer simultanément tous les consommateurs transforme une évolution ordinaire en dépendance organisationnelle fragile.

Appliquer l’évolution additive et les adaptateurs à la bonne frontière

Lorsqu’une nouvelle exigence modifie le modèle, introduisez d’abord une nouvelle capacité et conservez temporairement l’ancienne. Une interface héritée peut déléguer à la nouvelle implémentation, à condition que la conversion soit non ambiguë. Les consommateurs peuvent ainsi migrer sans devoir coordonner une fenêtre unique.

interface LegacyPriceCalculator
{
    public function calculate(int $amount): int;
}

final class LegacyPriceCalculatorAdapter implements LegacyPriceCalculator
{
    public function __construct(private PriceCalculator $calculator) {}

    public function calculate(int $amount): int
    {
        return $this->calculator->calculate(new Money($amount, 'EUR'))->amount();
    }
}

L’adaptateur appartient généralement à la frontière entre les contrats, et non au cœur du domaine. Le domaine doit exprimer le modèle actuel ; la traduction d’anciens arguments, de valeurs sentinelles ou de formats historiques doit rester dans une couche dédiée. Si le domaine conserve des conditions pour chaque génération de clients, la complexité historique se propage à toute modification future.

Ne forcez pas un adaptateur en cas de perte d’information ou de nouvelle décision métier. Si l’ancien contrat ne contient pas les données nécessaires au nouveau comportement, conservez les deux contrats pendant la transition ou demandez explicitement au consommateur les informations supplémentaires.

Transformer la dépréciation en retrait géré

Une API marquée comme obsolète sans alternative, échéance ni responsable n’est pas une dépréciation : c’est de la dette sans suivi. Un retrait utile doit inclure un signal dans le code, des instructions de migration, une condition de suppression et l’observation de l’utilisation lorsque cela est possible.

  • Marquez la méthode ou la classe héritée avec une documentation claire et, le cas échéant, émettez un avertissement contrôlé avec trigger_error(..., E_USER_DEPRECATED).
  • Indiquez l’alternative exacte, y compris les différences de sémantique, d’erreurs et de valeurs par défaut.
  • Définissez une condition de sortie vérifiable : tous les dépôts inventoriés migrés, absence d’appels observés ou fin de support d’une version précise.
  • Attribuez un responsable qui examine l’avancement et retire la couche lorsque la condition est remplie.

Évitez d’émettre des avertissements indiscriminés dans des parcours à fort volume sans stratégie d’agrégation : le bruit peut masquer des signaux pertinents et augmenter le coût opérationnel. L’observabilité doit répondre à une question précise : quels consommateurs utilisent encore le contrat antérieur et dans quel parcours.

Tester la transition et exécuter la séquence de livraison

Les tests unitaires du composant ne démontrent pas à eux seuls que les consommateurs continuent de fonctionner. Ajoutez des tests de contrat pour les entrées, les sorties et les erreurs dont chaque consommateur a besoin. Conservez des cas de régression pour l’ancienne interface tant qu’elle est prise en charge et testez explicitement les valeurs absentes, les charges sérialisées antérieures et les exceptions attendues.

La séquence sûre suit généralement cet ordre :

  1. Publier le nouveau contrat ou l’implémentation additive en conservant le chemin antérieur.
  2. Mettre à jour et déployer les consommateurs de manière indépendante, en utilisant des tests d’intégration lorsque le risque le justifie.
  3. Observer les erreurs, les avertissements de dépréciation et l’utilisation de l’interface héritée.
  4. Confirmer l’inventaire de migration et résoudre les consommateurs indirects détectés.
  5. Retirer l’adaptateur ou l’ancien contrat dans une livraison séparée, avec des tests qui confirment son absence.

Liste de contrôle pour approuver le changement

Liste de contrôle pour approuver le changement — guía visual de DedicatedPHP
  • Le contrat affecté est-il défini au-delà de la signature PHP ?
  • Le changement est-il classifié comme additif, adaptable, incompatible ou incertain ?
  • Existe-t-il un inventaire des consommateurs, y compris les événements, les données et les parcours indirects ?
  • La solution évite-t-elle d’exiger des déploiements simultanés ?
  • L’adaptateur, s’il existe, est-il hors du domaine et son retrait est-il prévu ?
  • Le comportement antérieur, la nouvelle capacité et les erreurs attendues ont-ils été testés ?
  • La dépréciation indique-t-elle une alternative, une condition de retrait et un responsable ?
  • Existe-t-il un signal permettant de détecter les dépendances cachées avant de supprimer l’API ?

La bonne décision n’est ni de maintenir la compatibilité indéfiniment ni d’imposer une coordination totale. Elle consiste à concevoir une transition avec des limites : préserver ce qui est nécessaire, migrer avec des preuves et supprimer la compatibilité historique lorsqu’elle n’apporte plus de sécurité.

Vous souhaitez appliquer ces idées à votre projet ?Parlons de votre plateforme PHP.
Afficher les services associés