Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Writing a Custom Voter

Writing a Custom Voter

~16 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

Inline security expressions (chapter 51) quickly become UNMANAGEABLE for COMPLEX logic – a voter encapsulates the decision "is this user allowed to edit THIS project?" in its OWN, TESTABLE class.

Creating the ProjectVoter class

docker compose exec php bin/console make:voter ProjectVoter
api/src/Security/Voter/ProjectVoter.php
<?php

declare(strict_types=1);

namespace App\Security\Voter;

use App\Entity\Project;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;

final class ProjectVoter extends Voter
{
    public const EDIT = 'PROJECT_EDIT';
    public const VIEW = 'PROJECT_VIEW';

    protected function supports(string $attribute, mixed $subject): bool
    {
        return \in_array($attribute, [self::EDIT, self::VIEW], true)
            && $subject instanceof Project;
    }

    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
    {
        $user = $token->getUser();

        if (!$user instanceof User) {
            return false;
        }

        /** @var Project $project */
        $project = $subject;

        if (\in_array('ROLE_ADMIN', $user->getRoles(), true)) {
            return true;
        }

        return match ($attribute) {
            self::VIEW => true,
            self::EDIT => $project->getOwner() === $user,
            default => false,
        };
    }
}

EXACTLY the same voter scaffolding as in the Symfony course (chapter 40) – supports() decides WHETHER the voter is even RESPONSIBLE, voteOnAttribute() makes the actual YES/NO decision.

Achtung: The voter references $project->getOwner() – this field does NOT exist YET. Chapter 54 adds it. Until then, EDIT fails for ALL users except ROLE_ADMIN, which is UNPROBLEMATIC for the INTERIM development.

Registration: automatic

EXACTLY as in the Symfony course, Symfony's autowiring recognizes the voter AUTOMATICALLY via the Voter interface – NO manual registration in services.yaml needed.

Tipp: PROJECT_VIEW/PROJECT_EDIT as STRING constants (instead of raw strings) prevents typos when calling them LATER in chapter 53 – a typo in a raw string would SILENTLY ALWAYS return "not allowed" instead of throwing an error.