['permission:read']], security: "is_granted('ROLE_USER')", ), new Get( normalizationContext: ['groups' => ['permission:read']], security: "is_granted('ROLE_USER')", ), ], )] #[ApiFilter(SearchFilter::class, properties: ['module' => 'exact'])] #[ApiFilter(BooleanFilter::class, properties: ['orphan'])] #[ORM\Entity(repositoryClass: DoctrinePermissionRepository::class)] #[ORM\Table(name: 'permission')] #[Auditable] #[ORM\UniqueConstraint(name: 'uniq_permission_code', columns: ['code'])] #[ORM\Index(name: 'idx_permission_module', columns: ['module'])] #[ORM\Index(name: 'idx_permission_orphan', columns: ['orphan'])] class Permission { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] #[Groups(['permission:read'])] private ?int $id = null; #[ORM\Column(length: 255)] #[Groups(['permission:read'])] private string $code; #[ORM\Column(length: 255)] #[Groups(['permission:read'])] private string $label; #[ORM\Column(length: 100)] #[Groups(['permission:read'])] private string $module; #[ORM\Column(options: ['default' => false])] #[Groups(['permission:read'])] private bool $orphan = false; /** * Invariants : une permission doit avoir un code non vide respectant la * convention "module.resource[.sub].action" (donc contenir au moins un * point), un libelle non vide et un module proprietaire non vide. Ces * garde-fous evitent la persistence de lignes incoherentes si un appelant * (fixture, commande de synchro, import) oublie un champ ou passe une * chaine vide. */ public function __construct(string $code, string $label, string $module) { if ('' === $code) { throw new InvalidArgumentException('Le code de permission ne peut pas etre vide.'); } if (!str_contains($code, '.')) { throw new InvalidArgumentException(sprintf('Le code de permission "%s" ne respecte pas la convention "module.resource[.sub].action".', $code)); } if ('' === $label) { throw new InvalidArgumentException('Le libelle de permission ne peut pas etre vide.'); } if ('' === $module) { throw new InvalidArgumentException('Le module proprietaire de la permission ne peut pas etre vide.'); } $this->code = $code; $this->label = $label; $this->module = $module; } public function getId(): ?int { return $this->id; } public function getCode(): string { return $this->code; } public function getLabel(): string { return $this->label; } public function getModule(): string { return $this->module; } public function isOrphan(): bool { return $this->orphan; } /** * Marque la permission comme orpheline : son code n'est plus declare par * aucun module. Elle reste en base pour preserver les assignations et * permettre une reactivation ulterieure, mais doit etre ignoree par les * verifications d'autorisation. */ public function markOrphan(): static { $this->orphan = true; return $this; } /** * Reactive une permission precedemment orpheline : son code reapparait * dans le code source d'un module. Equivaut a updateMetadata() suivi d'un * clearing du flag orphan ; on delegue a updateMetadata() pour ne pas * dupliquer la logique d'affectation des metadonnees. */ public function revive(string $label, string $module): static { $this->updateMetadata($label, $module); $this->orphan = false; return $this; } /** * Met a jour les metadonnees d'une permission active sans toucher a son * statut d'orphelin. Utilise par la commande de synchronisation lorsque * seul le libelle ou le module proprietaire a change cote code. */ public function updateMetadata(string $label, string $module): static { $this->label = $label; $this->module = $module; return $this; } }