Une demande commerciale devient dangereuse lorsqu’elle cesse d’être une décision produit explicite et commence à se matérialiser sous la forme d’un if ($tenantId === ...). Au début, elle résout une urgence. Avec le temps, cette condition apparaît dans les contrôleurs, les templates, les processus de file d’attente, les exportations et les API. Le résultat n’est pas une configuration : ce sont des variantes implicites du produit, difficiles à tester, à expliquer et à retirer.
La configuration par client dans un SaaS PHP doit permettre des différences délibérées et gouvernées, et non préserver chaque exception historique. La question utile n’est pas « pouvons-nous le faire pour ce client ? », mais « cette variation représente-t-elle une dimension stable du produit dont d’autres clients pourraient avoir besoin, avec des règles et un support durables ? ».
Le signal d’alerte : une exception de code permanente

Il existe une différence entre adapter une expérience et maintenir une branche cachée du produit. Il convient d’intervenir avant qu’une demande particulière ne crée l’un de ces signaux :
- L’identifiant du tenant, du domaine ou du client apparaît dans la logique métier.
- Une même règle est répliquée dans l’interface, l’API et le worker asynchrone.
- L’équipe ne peut pas indiquer quels clients ont une exception ni qui l’a approuvée.
- Un changement de plan modifie des comportements fonctionnels sans définition centrale.
- Retirer une adaptation exige de rechercher des conditions dans plusieurs dépôts ou services.
Une exception peut être légitime pendant une phase de découverte ou une migration, mais elle doit avoir un responsable, une date de révision et une issue : la convertir en capacité produit, l’isoler comme intégration spécifique ou la refuser. La laisser sans classification transforme la dette technique en promesse commerciale non documentée.
Ne confondez pas configuration, permissions, capacités et développement spécifique
Ces mécanismes répondent à des questions différentes. Les mélanger produit des conceptions opaques et des règles contradictoires.
- Configuration : définit comment une fonctionnalité déjà existante se comporte pour un tenant. Par exemple, le format d’une numérotation, la langue par défaut ou le fait qu’un flux requière une approbation supplémentaire.
- Permissions : déterminent ce qu’une identité peut faire au sein d’un tenant. Un utilisateur peut avoir la permission d’approuver des paiements même si l’approbation est configurée comme obligatoire.
- Capacités : indiquent si le tenant a accès à une fonctionnalité ou à une limite opérationnelle. Elles peuvent dépendre d’un contrat, d’un plan ou d’une activation contrôlée, mais ne devraient pas contenir toute la logique du domaine.
- Développement spécifique : couvre un comportement qui ne s’inscrit pas dans une dimension réutilisable du produit, comme une intégration avec un système propre au client ou une transformation contractuelle particulière.
Une règle pratique aide à décider : si cela change qui exécute une action, utilisez les permissions ; si cela change si une fonctionnalité existe ou est disponible, utilisez les capacités ; si cela change comment fonctionne une fonctionnalité disponible, utilisez la configuration. Si cela change le modèle métier de manière exclusive, ne le présentez pas comme un indicateur.
Ce qui doit être configurable et ce qui doit rester dans le noyau
Une option mérite d’entrer dans le catalogue de configuration lorsqu’elle possède une sémantique claire, un ensemble fini de valeurs, des validations connues et une attente raisonnable de réutilisation. Elle nécessite également une expérience de support compréhensible : quelqu’un doit pouvoir expliquer l’effet de sa modification sans inspecter le code.
Les paramètres de présentation, les politiques de notification, les seuils, les séquences d’approbation, les préférences régionales et les choix entre des flux déjà pris en charge sont généralement de bons candidats. En revanche, les invariants de sécurité, l’intégrité des données, le calcul financier de base et les règles dont la modification exigerait de réinterpréter des entités ou contrats existants doivent rester dans le noyau.
Ne transformez pas des données arbitraires en configuration au seul motif de la flexibilité. Un champ JSON sans schéma peut cacher des dépendances impossibles à découvrir. Lorsqu’une option modifie une règle critique, définissez les types, les valeurs autorisées, les conditions d’utilisation et les conséquences sur les données antérieures.
Construisez un modèle de configuration gouverné
Une clé isolée ne suffit pas. Chaque définition du catalogue doit inclure des métadonnées permettant d’exploiter le produit en toute sécurité :
- Clé et description fonctionnelle : des noms stables, orientés domaine et non vers des détails d’implémentation.
- Responsable : équipe ou personne responsable qui décide de son évolution et de son retrait.
- Périmètre : global, tenant, unité organisationnelle, projet ou utilisateur. Évitez d’autoriser tous les périmètres par défaut.
- Valeur par défaut : comportement explicite lorsqu’aucune surcharge n’existe.
- Type et validation : booléen, énumération, nombre avec plage ou structure validée par schéma.
- Dépendances : prérequis concernant d’autres options, capacités ou l’état de migration.
- Sensibilité : classification des données et règles d’accès pour la lecture et la modification.
- Cycle de vie : date d’introduction, de révision, de dépréciation et de retrait prévu lorsque cela s’applique.
En PHP, centralisez la résolution dans un service de domaine, par exemple TenantSettings, et fournissez des objets typés au lieu d’arrays sans contrat. L’application peut combiner la valeur globale, la valeur du tenant et une valeur plus spécifique au moyen d’une précédence documentée. L’absence de valeur doit toujours être résolue vers la valeur par défaut, et non vers une interprétation différente chez chaque consommateur.
$policy = $tenantSettings->approvalPolicy($tenantId);
if ($policy->requiresSecondApproval()) {
$workflow->requestSecondApproval($order);
}
Le stockage peut être relationnel ou documentaire, mais le catalogue et la validation ne devraient pas dépendre de la forme de persistance. Conservez également un historique immuable des changements : ancienne et nouvelle valeur, acteur, moment, motif et canal de modification. L’historique ne remplace pas un journal d’audit des actions métier, mais permet de reconstruire quelle configuration était en vigueur.
Évaluez la décision à la bonne frontière
Le problème des conditions dispersées ne se résout pas en les déplaçant toutes dans un contrôleur. Une configuration qui affecte une règle métier doit être évaluée dans le service ou la politique de domaine qui applique cette règle. Le contrôleur traduit la requête ; le template présente le résultat ; aucun ne devrait décider seul d’une politique de tenant.
Pour les comportements complexes, utilisez des stratégies ou des politiques enregistrées, plutôt que des chaînes de booléens. Une politique de facturation peut sélectionner une implémentation parmi des modes pris en charge après avoir validé que le tenant possède la capacité requise. Ainsi, l’interface, l’API et la file d’attente invoquent la même décision.
Les templates peuvent recevoir une vue déjà préparée, y compris des indicateurs de capacité pour afficher ou masquer des actions. Masquer un bouton n’est pas une autorisation. L’API doit appliquer les permissions, la capacité et la configuration côté serveur, même si l’interface n’expose pas l’opération.
Capacités et limites sans rigidifier les plans
Un plan commercial peut accorder des capacités, mais ne doit pas devenir une collection de if ($plan === '...'). Modélisez une capacité stable, telle que advanced_approvals ou api_access, et déterminez quels tenants la possèdent à l’aide d’une source contractuelle ou administrative. Ensuite, la logique fonctionnelle consulte la capacité, non le nom du plan.
Les limites nécessitent une définition encore plus précise : ce qui est compté, dans quelle fenêtre temporelle, à quel moment le blocage s’applique et comment se comportent les tentatives et les processus en file d’attente. Une limite doit être observable et cohérente dans tous les points d’entrée. Si une intégration crée des ressources en dehors de l’interface principale, elle ne peut pas contourner le même contrôle.
Modifiez les réglages de manière sûre et réversible
Modifier une option peut avoir des effets immédiats sur les travaux en cours, les enregistrements existants ou les intégrations. Avant d’enregistrer, validez le type, les permissions administratives, les dépendances et la compatibilité avec l’état actuel. Lorsque l’impact est significatif, proposez une prévisualisation du changement : quel flux sera activé, quelles contraintes il ne respecte pas et quelles opérations futures seront affectées.
L’activation progressive est différente du fait de rendre une option visible dans toute l’interface. Vous pouvez activer une capacité pour un ensemble contrôlé de tenants et observer son comportement avant de l’exposer de manière générale. Définissez également une réversion : quelle valeur rétablit l’état antérieur, si des migrations de données sont associées et ce qui se produit avec les opérations lancées sous la nouvelle configuration.
Un changement réversible dans l’interface peut ne pas être réversible dans les données. Traitez ces deux dimensions séparément avant d’activer une nouvelle politique.
Maintenez la cohérence dans les files d’attente, les API et les intégrations
Les processus asynchrones introduisent une décision supplémentaire : résoudre la configuration lors de l’exécution du travail ou conserver un instantané lors de sa création. Pour les actions qui doivent respecter la politique en vigueur, résolvez-la à l’exécution et incluez le tenant dans le contexte du travail. Pour les documents, calculs ou communications qui doivent reproduire la décision d’origine, enregistrez une version ou un instantané explicite avec la commande.
Ne mélangez pas ces deux options sans le déclarer. Une tentative peut changer de résultat si elle consulte une configuration mise à jour. Définissez l’idempotence, la version de configuration et le comportement attendu lors des tentatives. Les intégrations externes nécessitent des contrats équivalents : validation préalable, traitement des erreurs, limites et traçabilité par tenant, sans envoyer de secrets ou de données personnelles dans le journal de diagnostic.
Audit et support : expliquez le comportement observé

Le support doit pouvoir répondre pourquoi un client voit un flux, et non seulement quelle valeur possède une clé. Enregistrez une trace de décision avec l’identifiant du tenant, la version de définition, la source de la valeur effective — par défaut ou surcharge —, les capacités pertinentes et le résultat de l’évaluation. Limitez l’accès à ces informations et masquez les valeurs sensibles.
Complétez cette traçabilité par des métriques d’utilisation par option, des erreurs de validation, des changements échoués et des options non utilisées. Une configuration sans utilisation peut être obsolète ; une configuration utilisée par un seul tenant pendant longtemps mérite une revue produit. L’objectif n’est pas d’éliminer toute différence, mais de faire en sorte que chaque différence soit explicite, vérifiable, observable et retirée lorsqu’elle cesse d’apporter de la valeur.



