Essaie de répondre à ça en revue de conformité : pourquoi exactement ton application a-t-elle laissé le compte 4127 approuver cette facture en mars ? Pas si elle l'a fait, le journal d'accès règle cette partie. Pourquoi. Quelle règle s'est déclenchée, quel rôle a fait pencher la balance, ce que le voter voyait réellement à ce moment-là. Pour la plupart des applis Symfony, la réponse honnête a longtemps été un booléen et un haussement d'épaules, parce qu'un voter retourne true ou false et emporte son raisonnement dans la tombe. Cette époque touche à sa fin, et je pense qu'on devrait enterrer les bricolages avec elle.
security:
access_decision_manager:
strategy_service: App\Security\WeightedComplianceStrategyTu connais le bricolage dont je parle, tu l'as probablement écrit toi-même. Un appel $logger->info() dans chaque voter, chacun formaté un peu différemment, aucun relié de façon fiable à la décision que le gestionnaire d'accès finit par prendre. Du formatage de chaînes d'audit planté au milieu de la logique d'autorisation, autrement dit à peu près le dernier endroit où il a sa place. Symfony 7.3, sorti en mai 2025 avec la PR #59771 de Nicolas Grekas, a rendu cette béquille obsolète : VoterInterface::vote() et Voter::voteOnAttribute() ont gagné un argument Vote nullable, et un simple $vote?->addReason('why I said no') place l'explication exactement là où la décision se prend. Ces raisons remontent dans le profiler, dans les logs et sur les pages d'exception, et la classe de base Voter écrit toute seule le résultat granted, denied ou abstain dans le Vote. Ta sous-classe n'a plus qu'à s'expliquer.
Les raisons, c'est de la prose pour humains. Ce qui change ma façon de concevoir l'autorisation est arrivé en novembre 2025 avec 7.4 et 8.0 : extraData, contribué par Roman Joly, Eltharin sur GitHub, dans la PR #60085. Un vote peut désormais transporter un tableau, ou carrément un objet si tu y tiens, de données exploitables par la machine. Un score, une référence de politique, un drapeau de risque. Combine ça avec AccessDecisionStrategyInterface et la config strategy_service, qui existaient bien avant 7.4, et une stratégie custom a soudain quelque chose qui vaut la peine d'être lu dans decide(). Le vote Symfony a toujours été strictement égalitaire, chaque voter comptant pareil, ce qui colle mal dès que l'approbation d'un directeur financier devrait peser plus lourd que celle d'un stagiaire sur une grosse facture. Maintenant un voter peut attacher son poids, et une seule stratégie peut écrire une seule entrée d'audit structurée par requête, couvrant tous les votes exprimés. Ajoute un dixième voter au prochain sprint et le format d'audit ne bouge pas.
La couche template a suivi. La PR #61379 de Florent Destremau ajoute access_decision() et access_decision_for_user() à côté des fonctions is_granted() purement booléennes. Les deux retournent un objet AccessDecision exposant le nom de la stratégie, isGranted, le tableau des votes et une méthode getMessage(), et la variante for_user vérifie un compte précis plutôt que le token courant, exactement ce qu'il faut à un écran d'admin qui prévisualise les permissions de quelqu'un d'autre. Un piège mérite un surlignage en rouge : getMessage() n'assemble que les raisons des votes qui vont dans le sens du résultat final. Un voter qui a voté contre ou s'est abstenu disparaît de ce message, aussi soigneusement qu'il se soit expliqué. Pour le tableau complet, tu ouvres toujours le profiler.
Maintenant la concession, parce qu'elle est réelle. La plupart des applications sont parfaitement servies par la stratégie affirmative et un simple oui ou non, et aucune des stratégies intégrées, affirmative, consensus, unanimous ou priority, ne lit extraData. Écris des scores dans tes votes en tournant en affirmative et tu auras produit de magnifiques données mortes joliment structurées qui ne changent rien. Il y a un vrai risque de construire un petit moteur de règles que personne n'a demandé, avec des poids que personne ne saura justifier en revue de code. Et une chaîne de raison écrite pour un onglet interne du profiler peut nommer une politique métier que tu ne veux absolument pas voir rendue sur une page destinée au client. Balancer decision.message directement dans Twig sans penser au second public, c'est une nouvelle classe de bugs, fraîchement disponible.
Je reste fermement du côté de l'adoption de l'objet Vote, et l'argument n'est pas la conformité, c'est le débogage du mardi après-midi. Sept voters enregistrés, une requête renvoie 403, et avant 7.3 tu devais les bissecter à coups de dumps ou de points d'arrêt pendant qu'un collègue attendait. addReason() coûte une ligne par branche et transforme cette heure en un coup d'œil au profiler. Mes collègues qui écrivent du Go assemblent ce genre de trace de décision à la main avec des valeurs de contexte et des middlewares, et ils trouvent ça normal. Nous, on l'obtient comme primitive du framework avec un argument nullable. Je signe à chaque fois.
Un peu d'arithmétique de versions pour finir. extraData et les deux fonctions Twig exigent 7.4 ou 8.0. Symfony 7.4 est la LTS, tourne sur PHP 8.2 et plus, et reçoit des corrections de bugs jusqu'en novembre 2028 avec des correctifs de sécurité jusqu'en novembre 2029 ; 8.0 réclame PHP 8.4 et suit un cycle plus court, supporté jusqu'en juillet 2026. Si tu es garé sur 7.3, tu as déjà addReason() et tu devrais l'utiliser dès aujourd'hui. Ma règle d'adoption est simple : des raisons partout, immédiatement, parce qu'elles ne coûtent rien. extraData seulement le jour où une stratégie custom la lit vraiment.
Reste la question que je continue de retourner : où traces-tu la ligne d'exposition ? Est-ce que decision.message est strictement un artefact de back-office pour admins et auditeurs, ou est-ce que tu montrerais un jour une version filtrée à l'utilisateur final qui vient de se faire bloquer, pour qu'il sache que c'était le seuil de facture et pas un bug ? Dis-moi où ton équipe se situe, et si quelqu'un a réellement eu besoin de votes pondérés en production plutôt que d'unanimous plus une raison bien écrite.




Commentaires
Pas encore de commentaire — écris le premier.
Lance la discussion
Pas de compte ni de mot de passe — saisis simplement ton adresse e-mail et nous t’envoyons un lien de connexion à usage unique. Première visite ? Tout se met en place automatiquement.
Ton évaluation sera appliquée automatiquement après ta connexion.
Vérifie ta boîte mail
Nous avons envoyé un lien de connexion à …. Ouvre-le sur cet appareil — cet onglet te connectera automatiquement.
Rien reçu ? Vérifiez le dossier spam — et marquez le message « Non spam » pour qu'il arrive directement la prochaine fois.