Lorsque la logique d’un SaaS commence par des conditions telles que if ($tenant->plan === 'pro'), cela semble être une solution directe. Le problème apparaît dès la deuxième modification du catalogue : un plan est renommé, une capacité est vendue séparément, un client conserve d’anciennes conditions ou le support doit activer temporairement une fonctionnalité. Le nom commercial cesse alors de décrire une règle stable et se retrouve réparti entre contrôleurs, tâches planifiées, requêtes, API et interface.
La gestion des capacités dans un SaaS PHP doit traduire l’offre commerciale en décisions de domaine vérifiables. Le code ne devrait pas se demander si une entreprise est « Pro », mais si elle peut effectuer une action précise, avec quelle limite, sous quelles conditions et jusqu’à quand. Cette séparation permet de modifier les prix ou les offres groupées sans réécrire les règles opérationnelles.
Séparer plan, capacité, limite, permission et configuration

Ces concepts sont liés, mais ils ne sont pas interchangeables. Un plan est une offre commerciale groupée. Une capacité active une possibilité du produit, comme exporter des données, créer des automatisations ou utiliser une intégration. Une limite définit une quantité ou une cadence autorisée, par exemple le nombre de projets actifs, d’utilisateurs facturables ou de requêtes API par période.
Une permission répond à une autre question : quelle identité peut effectuer une action au sein d’une entreprise. Le fait qu’une organisation ait la capacité d’exporter des données ne signifie pas que n’importe quel utilisateur peut les exporter. Enfin, la configuration du client correspond aux options valides pour une entreprise donnée, comme le fournisseur d’identité choisi, une politique de conservation ou un template de notifications. Il ne convient pas d’utiliser une configuration libre pour masquer des décisions commerciales ou d’autorisation.
- Plan : composition commerciale de droits et de limites.
- Capacité : règle fonctionnelle exprimée en termes de produit.
- Limite : seuil quantifiable associé à une capacité ou à une ressource.
- Permission : autorisation accordée à un acteur pour exécuter une action.
- Configuration : paramètre de comportement disponible dans le cadre des règles déjà accordées.
Une décision peut nécessiter toutes les couches. Pour créer une automatisation, l’entreprise a besoin de la capacité correspondante, doit rester sous la limite d’automatisations actives et l’utilisateur doit disposer d’une permission d’administration. Le flux peut ensuite valider la configuration de destination.
Modéliser des règles de domaine, pas des noms de plans
Maintenez un catalogue stable de capacités avec des identifiants techniques indépendants du marketing : automation.create, data.export ou api.webhooks. Le catalogue peut inclure le type de valeur attendu : booléen, entier, ensemble d’options ou politique structurée. L’identifiant exprime un besoin du produit ; il ne doit pas intégrer le nom d’un plan ni d’une campagne.
L’attribution commerciale peut être résolue dans une couche distincte. Un plan en vigueur apporte un ensemble d’autorisations, mais il peut également exister des modules complémentaires, des migrations héritées et des exceptions explicites. Le résultat pour chaque entreprise est une résolution des droits effectifs avec leur provenance.
capacité : automation.create
valeur effective : true
provenance : module complémentaire d’automatisations
validité : jusqu’à annulation
Cette provenance est essentielle. Si une capacité est active, les équipes produit, support et facturation doivent savoir si elle provient du plan actuel, d’une clause héritée ou d’une exception avec une date d’expiration. Évitez de ne stocker qu’un champ plan dans l’entreprise et d’en déduire tout le reste à chaque point d’utilisation.
Un service de décision unique
En PHP, exposez un service de domaine, par exemple EntitlementResolver ou CapabilityGate, qui reçoit l’entreprise, la capacité et le contexte nécessaire. Il doit renvoyer une décision explicable, et non seulement un booléen : autorisé ou refusé, valeur résolue, motif, règle source et date d’évaluation. Cette date représente l’instant où le résolveur a déterminé la décision et permet d’interpréter correctement les périodes de validité, les expirations et les changements de plan ultérieurs. Les contrôleurs, commandes, listeners et workers asynchrones consultent ce service ; ils ne reconstruisent pas leurs propres requêtes d’abonnement.
Définir des limites mesurables avant de les programmer
Une limite ambiguë génère des conflits et des erreurs d’implémentation. « Jusqu’à 100 utilisateurs » exige de répondre à ce qui compte comme utilisateur : invitation en attente, utilisateur suspendu, membre supprimé durant le cycle, compte de service ? « Mille exportations » nécessite de définir la période, le fuseau horaire, les tentatives et si une exportation échouée consomme le quota.
Pour chaque limite, documentez au minimum :
- La ressource, l’événement ou la consommation comptabilisé.
- Le périmètre : entreprise, projet, utilisateur ou intégration.
- La fenêtre : total en vigueur, jour calendaire, mois de facturation ou fenêtre glissante.
- Le moment du contrôle : avant la création, à l’activation, à l’envoi ou après consolidation.
- La réaction : bloquer, autoriser avec un avertissement, mettre en file d’attente, dégrader ou exiger une approbation.
- Le traitement de la concurrence, des tentatives, des annulations et de la réversion.
Les limites d’objets actifs sont généralement validées au moyen d’une opération transactionnelle ou d’une réservation qui évite de dépasser le seuil en cas de concurrence. Les limites de consommation nécessitent un compteur à la sémantique claire et de l’idempotence au moyen d’une clé d’événement. Ne vous fiez pas uniquement à un compteur affiché dans l’interface : deux requêtes simultanées peuvent passer un contrôle préalable et dépasser le maximum.
Faites également la distinction entre avertissement et blocage. Une alerte à 80 % améliore la prévisibilité, mais ne remplace pas une protection réelle à l’endroit où la ressource est créée ou exécutée.
Appliquer les règles dans tous les chemins d’exécution
Masquer un bouton améliore l’expérience, mais ce n’est pas un contrôle d’accès. La validation doit exister dans le cas d’utilisation côté serveur qui exécute l’action. Elle couvre ainsi l’interface web, l’API publique, les intégrations et les appels internes.
Les processus asynchrones exigent une décision supplémentaire : contrôler lors de la mise en file d’attente et contrôler de nouveau lors de l’exécution lorsque le travail peut être retardé. Si une entreprise perd une capacité entre ces deux moments, la politique doit définir si le travail est annulé, s’il se termine parce qu’il a été accepté auparavant ou s’il exige une révision. Le choix dépend du type d’opération, mais il doit être cohérent et enregistré.
Les outils de support et d’administration ne devraient pas contourner silencieusement les règles. Ils peuvent fonctionner avec une autorisation administrative distincte, mais ils doivent laisser une traçabilité et indiquer s’ils génèrent une autorisation formelle, une correction de données ou une action exceptionnelle.
Gérer les changements, héritages et exceptions sans scinder le produit
Un changement de plan ne consiste pas seulement à mettre à jour une étiquette. Il peut réduire une limite en dessous de l’utilisation actuelle ou retirer une capacité qui soutient des processus actifs. Définissez des politiques par type de ressource : empêcher les nouvelles créations et conserver l’existant, désactiver explicitement les excédents, accorder une période de transition ou demander un choix à l’administrateur de l’entreprise.
Les exceptions temporaires doivent être des autorisations de première classe avec périmètre, valeur, motif, émetteur et expiration. Un champ manuel tel que is_vip est difficile à interpréter et survit souvent à sa cause d’origine. Pour les clients hérités, modélisez une attribution de migration avec des règles précises et une date de révision, au lieu de créer des branches de code permanentes.
Une exception durable est une donnée auditable interprétée par le même résolveur ; une exception dangereuse est une condition spéciale ajoutée à un flux concret.
Combiner capacités, rôles et isolation multi-entreprise
Dans une plateforme multi-entreprise, toute requête de droits, de consommation et de configuration doit être délimitée par la bonne entreprise. Ne dérivez pas le contexte uniquement à partir de valeurs envoyées par le client. Résolvez-le à partir de l’authentification, du domaine demandé ou du contexte interne validé, et propagez-le aux travaux en file d’attente et aux événements.
La décision finale est généralement une intersection : l’entreprise dispose de la capacité, la limite n’est pas épuisée et l’acteur possède la permission requise. Centraliser les capacités ne remplace pas un modèle de rôles ; cela évite que rôles et plans soient mélangés. Un rôle peut accorder qui administre les automatisations, tandis que la capacité détermine si l’entreprise peut utiliser les automatisations.
Données, audit et tests permettant d’expliquer les décisions
Conservez les attributions de droits avec leur validité et leur précédence, ainsi que les événements de consommation lorsqu’un agrégat ne suffit pas. Enregistrez les décisions importantes : entreprise, acteur ou processus, capacité, valeur évaluée, résultat, source et corrélation de la requête. Ne stockez pas de données personnelles inutiles dans ces registres et définissez une conservation conforme à vos obligations.
L’audit doit permettre de répondre à la question de savoir pourquoi une action a été refusée sans devoir lire du code historique. Il est particulièrement utile lors de changements commerciaux, d’incidents de facturation et d’opérations de support.
Les tests doivent inclure une matrice de capacités et de valeurs, les limites au seuil exact, la concurrence, les changements de plan, l’expiration des exceptions et les tentatives d’événements. Exécutez les mêmes cas via HTTP, API et workers en file d’attente lorsqu’ils partagent le même cas d’utilisation. Ajoutez des tests d’isolation afin de confirmer qu’une entreprise ne peut ni consulter ni consommer les droits d’une autre.
Plan progressif pour centraliser une plateforme PHP existante
- Inventoriez les noms de plans, les conditions, les compteurs et les exceptions manuelles dans le code et les opérations.
- Choisissez une capacité ou une limite à fort impact et définissez toute sa sémantique avant de la migrer.
- Introduisez le résolveur comme façade, initialement compatible avec les sources actuelles.
- Déplacez les points d’application vers le cas d’utilisation central, et pas seulement vers l’interface.
- Enregistrez les décisions et comparez le nouveau comportement avec l’ancien avant de retirer les anciennes branches.
- Migrez plan par plan vers des attributions explicites et éliminez les références commerciales du domaine.
Liste de vérification avant de publier un changement

- La capacité possède-t-elle un identifiant stable et une définition métier non équivoque ?
- La limite spécifie-t-elle l’unité, le périmètre, la période, la concurrence et la réaction au dépassement ?
- La règle est-elle appliquée côté serveur, dans l’API et dans les processus asynchrones ?
- Les rôles, les capacités et le contexte d’entreprise sont-ils validés séparément ?
- Les changements de plan et les exceptions ont-ils une validité, une provenance et un audit ?
- Existe-t-il des tests pour le seuil de la limite, la révocation et l’isolation multi-entreprise ?
Avec ce modèle, le catalogue commercial peut évoluer sans transformer chaque modification en recherche de conditions. La plateforme conserve des règles compréhensibles, mesurables et défendables tant pour le produit que pour l’ingénierie.



