diff --git a/security/voters.rst b/security/voters.rst index 0aefd3c2aa8..a44083d86bc 100644 --- a/security/voters.rst +++ b/security/voters.rst @@ -35,15 +35,44 @@ The Voter Interface ------------------- A custom voter needs to implement -:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface` +:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\CacheableVoterInterface` or extend :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\Voter`, which makes creating a voter even easier:: use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; - abstract class Voter implements VoterInterface + abstract class Voter implements VoterInterface, CacheableVoterInterface { + public function vote(TokenInterface $token, mixed $subject, array $attributes): int + { + // abstain vote by default in case none of the attributes are supported + $vote = self::ACCESS_ABSTAIN; + + foreach ($attributes as $attribute) { + try { + if (!$this->supports($attribute, $subject)) { + continue; + } + } catch (\TypeError $e) { + if (str_contains($e->getMessage(), 'supports(): Argument #1')) { + continue; + } + + throw $e; + } + + // as soon as at least one attribute is supported, default is to deny access + $vote = self::ACCESS_DENIED; + + if ($this->voteOnAttribute($attribute, $subject, $token)) { + // grant access as soon as at least one attribute returns a positive response + return self::ACCESS_GRANTED; + } + } + + return $vote; + } abstract protected function supports(string $attribute, mixed $subject): bool; abstract protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool; }