# Doc: Symfony Voters (Symfony 7.x)

> Relevancia Wari: DT-013 (autorización sin Voter en GrupoController)

## Cuándo usar un Voter

- Lógica de permisos que se reutiliza en más de un controlador.
- Reglas de autorización ligadas a un objeto de dominio (Grupo, Evento...).
- Para permisos simples no reutilizados basta con `denyAccessUnlessGranted()` directo.

## Estructura mínima

```php
class GrupoVoter extends Voter
{
    const VIEW   = 'view';
    const MANAGE = 'manage';

    protected function supports(string $attribute, mixed $subject): bool
    {
        return in_array($attribute, [self::VIEW, self::MANAGE], true)
            && $subject instanceof Grupo;
    }

    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
    {
        $user = $token->getUser();
        if (!$user instanceof Usuario) {
            return false;
        }

        return match ($attribute) {
            self::VIEW   => $this->canView($subject, $user),
            self::MANAGE => $this->canManage($subject, $user),
            default      => throw new \LogicException('Unreachable'),
        };
    }
}
```

## Registro

Con `autoconfigure: true` (por defecto en Symfony) se registra solo como `security.voter`. No hay que hacer nada manual.

## Invocar desde controlador

```php
// Opción 1 — atributo (recomendado, más declarativo)
#[IsGranted('manage', 'grupo')]
public function edit(Grupo $grupo): Response { ... }

// Opción 2 — método
$this->denyAccessUnlessGranted(GrupoVoter::MANAGE, $grupo);
```

## Verificar roles dentro del Voter

```php
// ❌ NO usar Security::isGranted() dentro del voter
// ✅ Inyectar AccessDecisionManagerInterface
public function __construct(
    private AccessDecisionManagerInterface $accessDecisionManager
) {}

if ($this->accessDecisionManager->decide($token, ['ROLE_ADMIN'])) {
    return true;
}
```

## Performance

Implementar `supportsAttribute()` y `supportsType()` para evitar que Symfony invoque el voter en peticiones donde no aplica.

```php
public function supportsAttribute(string $attribute): bool
{
    return in_array($attribute, [self::VIEW, self::MANAGE], true);
}

public function supportsType(string $subjectType): bool
{
    return is_a($subjectType, Grupo::class, true);
}
```

## Errores comunes

| Error | Corrección |
|---|---|
| Usar `Security::isGranted()` dentro del voter | Usar `AccessDecisionManagerInterface` |
| No verificar que `$token->getUser()` sea la entidad correcta | Siempre comprobar el tipo |
| Duplicar chequeos ad-hoc en controladores | Centralizar todo en el Voter |
| Lógica de permisos en plantillas Twig | Solo para mostrar/ocultar UI, nunca como barrera real |
