Un refus qui ne dit rien est un refus que vous déboguerez à nouveau dans six mois. Le composant rend un objet, pas un booléen, et cette différence porte à elle seule toute son histoire de diagnostic.
$decision = $accessControlManager->decide($accessRequest);
$decision->isGranted(); // the verdict alone, for callers that only branch on it
$decision->decision; // the DecisionVote, where an abstention is still an abstention
$decision->reason; // the strategy's summary, then the reasons that concur with it
$decision->votes; // a list of CastVote: a voter, and what it answered
isGranted est toujours là
La plupart des appelants font un test et passent à la suite, donc isGranted() répond ce qu’ils attendent, y compris pour une abstention : non, exactement comme un refus répond non. C’est ce que tout point d’entrée en fait déjà.
L’objet plus riche coûte à ces appelants un appel de méthode et rien de plus. Il est disponible pour le moment où un booléen cesse de suffire, moment qui arrive plus tôt en autorisation que partout ailleurs dans une application.
CastVote : qui a dit quoi
Un CastVote apparie un votant avec son résultat. Cet appariement vit sur la décision plutôt que sur le résultat, et la raison mérite une phrase : un votant construit un résultat tout seul et n’a pas à se nommer lui-même, et un gestionnaire de politique en construit un sans aucun votant. Le gestionnaire est le seul endroit qui connaisse les deux.
C’est ce qui permet de relire une décision après coup et de savoir encore qui a refusé, ce que la raison seule ne dit pas quand deux votants refusent pour le même motif. C’est aussi ce que liste le panneau du profileur, et ce sur quoi s’appuie assertAccessWasDeniedBy() dans un test.
La raison, et à qui elle s’adresse
La raison est un diagnostic. Elle est composée par l’algorithme de combinaison, qui énonce d’abord son propre résumé puis les raisons qui vont dans son sens, si bien que la relire vous dit à la fois quelle règle s’est appliquée et quels votants l’ont portée.
Elle est délibérément retirée de la réponse. Un refus qui atteint HTTP est un 403 nu, et la raison va au profileur, au journal et aux tests. Dire à un visiteur quelle règle l’a refusé, c’est lui dire la forme des règles, et le visiteur n’est pas toujours un navigateur.
Quand un visiteur doit lire quelque chose, ce texte est un autre champ : le message d’une politique d’accès, écrit là où la politique est déclarée, pour le public qui le verra.
Là où elle devient visible
- Dans un gabarit, où
access_control_decision('EDIT', post)remet l’objet entier à Twig, de sorte qu’une page peut expliquer pourquoi un bouton n’est pas là. - Dans le panneau du profileur, qui liste chaque question de la requête avec son verdict, sa raison, son algorithme et ses votes.
- Dans les tests, où les assertions portent sur l’attribut, le verdict et le votant plutôt que sur un code de statut.
- Dans votre propre piste d’audit, bâtie sur les événements que le gestionnaire émet.
Les quatre lisent le même objet. Il n’existe pas de variante de la décision réservée au débogage, et rien n’a besoin d’être activé pour que la raison existe : ce qui change d’un environnement à l’autre, c’est qui a le droit de la regarder.