Back to symfony.com

Demo 5 of 10 · 2 min

Click any paragraph or press to highlight its code

Security voters

"Can this user edit this post?" Scattering that logic across controllers with if statements gets messy fast. In Symfony you declare the question with an attribute: #[IsGranted] protects the route and passes the post as the subject.

A voter answers the question. First it declares which decisions it knows how to make: the POST_EDIT permission on Post objects.

Then it applies your business rule: only the author can edit. The rule lives in one place. When it changes ("also allow moderators"), you edit one line and every controller, template and API endpoint follows. And it's a plain class: unit-test it without booting the framework.

<?phpnamespace App\Controller;use App\Entity\Post;use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\Routing\Attribute\Route;use Symfony\Component\Security\Http\Attribute\IsGranted;class PostController extends AbstractController{    #[Route('/posts/{id}/edit')]    #[IsGranted('POST_EDIT', subject: 'post')]    public function edit(Post $post): Response    {        // if the voter denies access, Symfony already returned        // a 403 page before running any of this code    }}
<?phpnamespace App\Security;use App\Entity\Post;use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;use Symfony\Component\Security\Core\Authorization\Voter\Vote;use Symfony\Component\Security\Core\Authorization\Voter\Voter;class PostVoter extends Voter{    protected function supports(string $attribute, mixed $subject): bool    {        return 'POST_EDIT' === $attribute && $subject instanceof Post;    }    protected function voteOnAttribute(        string $attribute,        mixed $subject,        TokenInterface $token,        ?Vote $vote = null,    ): bool {        // the entire "who can edit a post?" rule of your app,        // in a single testable place        return $subject->getAuthor() === $token->getUser();    }}