<?php
namespace App\Security\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;
/**
* Rola EDIT bez VIEW jest nieużywalna: okno nie pojawia się w menu, więc nie ma jak wejść w dane, które wolno edytować.
* Zamiast wypisywać tę zależność osobno dla każdego obszaru, wyliczamy ją z nazwy roli - katalog trzyma konwencję ROLE_<OBSZAR>_<CZASOWNIK>
*
* Dla roli VIEW nadanej wprost ten voter głosuje na "nie" - nie ma wtedy roli zapisu, z której
* mógłby ją wyliczyć - a przyznaje ją RoleVoter Symfony. Działa to tylko przy domyślnej
* strategii "affirmative"; przy "unanimous" role VIEW przestałyby być przyznawane.
*/
final class WriteImpliesViewVoter extends Voter
{
private const VIEW_SUFFIX = '_VIEW';
/**
* Czasowniki, które oznaczają zmianę danych i tym samym dają podgląd tego samego obszaru.
*/
private const WRITE_SUFFIXES = ['_EDIT', '_EXPORT', '_CREATOR'];
public function __construct(private readonly Security $security)
{
}
protected function supports(string $attribute, $subject): bool
{
return \str_starts_with($attribute, 'ROLE_') && \str_ends_with($attribute, self::VIEW_SUFFIX);
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
$area = \substr($attribute, 0, -\strlen(self::VIEW_SUFFIX));
foreach (self::WRITE_SUFFIXES as $suffix) {
// isGranted(), a nie role z tokenu - dzięki temu działa też przez role_hierarchy
if ($this->security->isGranted($area . $suffix)) {
return true;
}
}
return false;
}
}