Dans une application PHP modulaire, une frontière ne se limite pas à une séparation de dossiers, de classes ou d’espaces de noms. Elle détermine aussi quelles données un module peut accepter, quelles opérations il peut exécuter et à qui incombe la vérification de chaque condition. Si ces décisions restent implicites, les modules risquent de se faire trop confiance, de dupliquer les validations ou d’exposer des données qu’ils ne devraient pas contrôler.
Concevoir des frontières de confiance entre modules PHP consiste à expliciter ces responsabilités et à faire en sorte qu’elles restent vérifiables à mesure que le système évolue. Il ne s’agit pas de traiter chaque appel interne comme un trafic hostile ni de répéter toutes les règles à chaque couche. Il s’agit de déterminer où l’autorité change, de définir des contrats clairs et de valider chaque opération dans le module compétent pour l’exécuter.
Pourquoi une frontière entre modules est aussi une frontière de confiance

Un module peut consommer les données d’un autre au moyen d’une interface interne, d’un événement, d’une API ou d’une requête partagée. Dans tous les cas, il convient de se demander quelles garanties fournit l’émetteur et lesquelles le récepteur doit vérifier. L’emplacement du code ne suffit pas à répondre : une méthode PHP appelée directement peut recevoir des données issues d’une requête externe, d’une tâche en arrière-plan ou d’un autre module qui évolue séparément.
Distinguez la validation du format, les invariants métier et l’autorisation. La validation du format vérifie qu’un identifiant ou une date possède une représentation acceptable. Un invariant garantit, par exemple, qu’une opération ne comporte pas de quantité négative. L’autorisation détermine si l’acteur peut effectuer cette opération sur cette ressource. Ces contrôles sont liés, mais ne sont pas interchangeables.
La frontière de confiance est particulièrement importante lorsque l’acteur, la propriété des données ou le canal d’entrée change. Par exemple, un module de facturation peut recevoir une commande d’un autre module, mais ne devrait pas en déduire que la personne à l’origine de la requête a le droit de modifier n’importe quelle facture. L’identité et le contexte doivent être transmis de manière explicite et vérifiable.
Inventorier les données et les opérations qui franchissent chaque frontière
Commencez par cartographier les interactions pertinentes, plutôt que de repenser toute l’application. Pour chaque frontière, documentez qui appelle, qui est responsable de l’opération, quelles données sont échangées et si l’appel est synchrone ou asynchrone. Incluez les chemins moins visibles : commandes de console, processus planifiés, consommateurs de files d’attente et tâches d’administration.
- Données : identifiez les champs transférés, leur origine et s’ils contiennent des informations personnelles, sensibles ou relevant d’un autre domaine.
- Opérations : consignez les changements demandés et le module qui doit les appliquer.
- Contexte : déterminez quel acteur, quelle organisation, quel tenant ou quel objectif doit accompagner la requête.
- Résultats : précisez quelles informations le module peut renvoyer et quelles erreurs il peut signaler.
Cet inventaire aide à repérer les signaux de risque : un module interroge des tables qui ne lui appartiennent pas pour contourner une interface ; il reçoit un identifiant de ressource sans vérifier à qui elle appartient ; ou il accepte un objet de domaine mutable créé par un autre module. Il met aussi en évidence les duplications : plusieurs consommateurs interprètent chacun à leur manière une règle qui relève de l’autorité d’un seul domaine.
Définir des contrats explicites : format, invariants et compatibilité
Un contrat décrit ce dont le récepteur a besoin pour exécuter une opération et les garanties qu’il fournit en réponse. En PHP, il peut prendre la forme de DTO immuables, d’interfaces, de types bien définis ou de schémas de messages, selon l’architecture. La forme concrète importe moins que la clarté : évitez de transmettre des entités complètes lorsque le consommateur n’a besoin que de quelques valeurs.
Le contrat doit préciser les champs obligatoires, les limites et leur signification. Un customerId, par exemple, n’indique pas à lui seul s’il identifie un client global, un client appartenant à une organisation ou un client visible par un utilisateur donné. Si le contexte conditionne l’opération, incluez-le ou définissez la manière dont le module responsable l’obtient.
Convenez également d’erreurs distinctes. Une entrée mal formée n’est pas la même chose qu’une opération non autorisée, une ressource inexistante ou un conflit avec l’état actuel. Les détails communiqués à l’appelant doivent suffire à gérer le résultat, sans révéler de données qui faciliteraient l’énumération de ressources ou la découverte d’informations concernant des tiers.
Les contrats doivent évoluer en préservant délibérément la compatibilité. L’ajout d’un champ facultatif peut être moins perturbant que la modification de la signification d’un champ existant, mais il nécessite tout de même de vérifier les producteurs et les consommateurs. Pour les messages asynchrones, tenez compte du fait qu’une ancienne version peut encore circuler après le déploiement du nouveau code. Ne confondez pas le déploiement du code avec l’activation d’un nouveau comportement : utilisez une transition contrôlée lorsque les producteurs et les consommateurs ne changent pas en même temps.
Vérifier l’autorisation et la propriété là où l’opération est exécutée
La validation syntaxique peut avoir lieu à proximité du point d’entrée, mais l’autorisation doit être vérifiée dans le contexte qui connaît la ressource et régit l’action. Le module propriétaire d’une facture, par exemple, dispose des informations nécessaires pour décider si elle peut être émise ou modifiée. Il ne devrait pas déléguer cette décision à un module appelant qui ne connaît que l’identité de l’utilisateur.
Une vérification utile associe l’acteur, l’action, la ressource et le contexte : qui demande quelle opération, sur quelle ressource et au sein de quelle organisation ou de quel tenant. Ne supposez pas qu’un identifiant reçu équivaut à une autorisation. Vérifiez la propriété à la frontière qui exécute l’opération, même si la requête provient d’un code interne considéré comme fiable. Cela évite qu’un autre chemin ou une évolution ultérieure de l’architecture contourne une autorisation appliquée uniquement dans l’interface web.
Évitez toutefois de répliquer l’intégralité de la politique dans tous les modules. Centralisez la décision dans le module compétent et exposez une opération ou un service doté d’un contrat clair. Les modules consommateurs peuvent effectuer des vérifications de commodité pour améliorer l’expérience utilisateur, mais ils ne doivent pas devenir l’unique barrière de sécurité.
Éviter que les entités partagées créent un accès privilégié
Partager des entités ORM entre modules peut sembler pratique, mais cela peut créer des dépendances cachées. Si plusieurs modules chargent et modifient la même entité, chacun peut contourner les opérations censées protéger ses invariants. En outre, les changements de schéma ou de comportement affectent des consommateurs qui ne contrôlent pas le modèle.
Préférez transférer les données nécessaires au moyen de commandes, de requêtes ou de DTO. Le module propriétaire reste responsable de la persistance et de la validation de ses invariants ; le consommateur n’a pas besoin d’un accès général à ses dépôts ou à ses tables. Si une lecture est nécessaire, concevez une requête qui renvoie le minimum de données et respecte les autorisations et le périmètre définis.
Une base de données partagée n’impose pas de partager l’autorité. Même avant de séparer physiquement les données, il est possible de décider qu’un seul module écrit dans certaines tables et que les autres utilisent des interfaces documentées. Cette règle réduit le coût d’une séparation ultérieure et facilite la détection des accès hors contrat.
Vérifier que l’isolation perdure à mesure que le système évolue
Les tests doivent couvrir à la fois le chemin valide et les tentatives de franchissement d’une frontière sans autorisation. Les tests unitaires vérifient les règles du module propriétaire ; les tests d’intégration vérifient les contrats, la persistance et la configuration de l’autorisation entre les composants. Incluez des cas où un acteur valide tente d’agir sur une ressource d’un autre tenant, une ressource inexistante et un contexte incomplet.
- Vérifiez qu’un identifiant valide ne donne pas, à lui seul, accès à la ressource.
- Vérifiez que les erreurs d’autorisation ne renvoient aucune donnée sur la ressource protégée.
- Testez que les consommateurs ne peuvent pas modifier les invariants au moyen d’entités partagées ou d’un accès direct aux dépôts.
- Exécutez des tests de contrat lorsque les producteurs et les consommateurs sont déployés indépendamment.
Les tests doivent refléter la politique réelle, et non se contenter de vérifier qu’une exception est levée. Assurez-vous que la modification n’est pas persistée, qu’aucun événement indu n’est publié et que la répétition de l’opération ne produit pas d’effets partiels. Pour les flux comportant plusieurs étapes, définissez ce qui se passe en cas d’échec d’une étape et comment l’état est réconcilié.
Réexaminer les frontières lors de l’extraction d’un service ou d’un changement de responsabilité

L’extraction vers un service distinct rend visibles des hypothèses auparavant masquées en mémoire : identité, autorisations, latence, nouvelles tentatives, doublons et disponibilité. Le contrat doit transporter suffisamment de contexte et prendre en compte les défaillances réseau. Ne transformez pas un appel distant en copie conforme d’un appel local sans revoir les délais d’attente, l’idempotence et le comportement en cas d’erreur.
Réexaminez également les frontières lorsque la responsabilité d’une donnée change, qu’un tenant est ajouté, qu’un nouveau canal apparaît ou que de nouveaux consommateurs sont introduits. Demandez-vous qui peut écrire, qui peut lire, quel module décide de l’autorisation et quelles garanties sont maintenues pendant la migration. Consignez ces réponses avec la documentation du contrat et les tests correspondants.
Une frontière de confiance utile ne dépend pas du fait que tous les modules restent dans le même processus ni du fait que l’équipe se souvienne de conventions informelles. Elle se reconnaît à une autorité clairement attribuée, à des données transférées dont le périmètre est limité, à des autorisations vérifiées là où l’opération est exécutée et à des tests qui détectent les accès non autorisés. L’isolation est ainsi protégée sans transformer chaque module en réplique des règles des autres.



