*/ final class PermissionVoter extends Voter { /** * Regex de reconnaissance des codes de permission. * * Contraintes : * - Premier caractere alphabetique minuscule (pas de chiffre, pas de ROLE_). * - Au moins un point de separation (ecarte les attributs atomiques * type ROLE_ADMIN ou IS_AUTHENTICATED_FULLY). * - Segments en snake_case minuscule coherents avec les permissions * declarees par les *Module::permissions() et validees par app:sync-permissions. */ private const string PERMISSION_CODE_PATTERN = '/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/'; protected function supports(string $attribute, mixed $subject): bool { return (bool) preg_match(self::PERMISSION_CODE_PATTERN, $attribute); } protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool { $user = $token->getUser(); if (!$user instanceof User) { // Token anonyme ou user d'un autre type : on refuse explicitement. // Les voters core (AuthenticatedVoter) se chargent deja du cas // "pas authentifie du tout". return false; } if ($user->isAdmin()) { // Bypass total : decision architecturale #343 section 11. // Cette regle est dupliquee cote front dans usePermissions() // et les deux doivent bouger ensemble si elle evolue un jour. return true; } return in_array($attribute, $user->getEffectivePermissions(), true); } }