- Permission : guards constructeur (code/label/module non vides, code avec point) - Permission::revive() reutilise updateMetadata() pour eviter la duplication - Suppression de SystemRolesTest (tautologique, ne capture aucun comportement) - Role::permissions : commentaire explicite sur la raison du fetch EAGER - Alignement des types de retour sur static (style User.php) - Nouveau test Role::addPermission avec permissions distinctes Ticket #343 - Task 1 polish (revue qualite). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
154 lines
4.3 KiB
PHP
154 lines
4.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Module\Core\Domain\Entity;
|
|
|
|
use App\Module\Core\Domain\Exception\SystemRoleDeletionException;
|
|
use Doctrine\Common\Collections\ArrayCollection;
|
|
use Doctrine\Common\Collections\Collection;
|
|
use Doctrine\DBAL\Types\Types;
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
|
|
/**
|
|
* Role RBAC : groupe nomme de permissions assignable a un utilisateur.
|
|
*
|
|
* Un role peut etre "systeme" (cree et protege par la plateforme) ou
|
|
* "personnalise" (cree par un administrateur). Seuls les roles personnalises
|
|
* peuvent etre supprimes.
|
|
*/
|
|
// TODO: brancher repositoryClass au ticket 343 partie 2 (Task 2).
|
|
#[ORM\Entity]
|
|
#[ORM\Table(name: '`role`')]
|
|
#[ORM\UniqueConstraint(name: 'uniq_role_code', columns: ['code'])]
|
|
#[ORM\Index(name: 'idx_role_is_system', columns: ['is_system'])]
|
|
class Role
|
|
{
|
|
#[ORM\Id]
|
|
#[ORM\GeneratedValue]
|
|
#[ORM\Column]
|
|
private ?int $id = null;
|
|
|
|
#[ORM\Column(length: 100)]
|
|
private string $code;
|
|
|
|
#[ORM\Column(length: 255)]
|
|
private string $label;
|
|
|
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
|
private ?string $description = null;
|
|
|
|
#[ORM\Column(name: 'is_system', options: ['default' => false])]
|
|
private bool $isSystem = false;
|
|
|
|
/** @var Collection<int, Permission> */
|
|
// Choix deliberé de fetch: 'EAGER' (durcissement, pas oubli de perf) :
|
|
// - Evite un lazy-load silencieux pendant un refresh de token JWT ou une
|
|
// serialisation hors contexte EntityManager (voir ticket #343, section
|
|
// 11 risque #1) ou la collection serait inaccessible et provoquerait
|
|
// une erreur opaque.
|
|
// - Compromis accepte : surcout SQL volontaire, acceptable a l'echelle
|
|
// d'un CRM/ERP PME ou un role porte quelques dizaines de permissions.
|
|
// - Si la volumetrie augmente significativement : revoir vers une
|
|
// projection cachee (ticket a ouvrir a ce moment-la).
|
|
#[ORM\ManyToMany(targetEntity: Permission::class, fetch: 'EAGER')]
|
|
#[ORM\JoinTable(name: 'role_permission')]
|
|
private Collection $permissions;
|
|
|
|
public function __construct(string $code, string $label, bool $isSystem = false, ?string $description = null)
|
|
{
|
|
$this->code = $code;
|
|
$this->label = $label;
|
|
$this->isSystem = $isSystem;
|
|
$this->description = $description;
|
|
$this->permissions = new ArrayCollection();
|
|
}
|
|
|
|
public function getId(): ?int
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function getCode(): string
|
|
{
|
|
return $this->code;
|
|
}
|
|
|
|
public function getLabel(): string
|
|
{
|
|
return $this->label;
|
|
}
|
|
|
|
public function getDescription(): ?string
|
|
{
|
|
return $this->description;
|
|
}
|
|
|
|
public function isSystem(): bool
|
|
{
|
|
return $this->isSystem;
|
|
}
|
|
|
|
/** @return Collection<int, Permission> */
|
|
public function getPermissions(): Collection
|
|
{
|
|
return $this->permissions;
|
|
}
|
|
|
|
/**
|
|
* Met a jour le libelle affichable du role. Le code reste immuable pour
|
|
* garantir la stabilite des references cote fixtures et migrations.
|
|
*/
|
|
public function setLabel(string $label): static
|
|
{
|
|
$this->label = $label;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Met a jour la description libre du role (champ documentaire).
|
|
*/
|
|
public function setDescription(?string $description): static
|
|
{
|
|
$this->description = $description;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Ajoute une permission au role. Idempotent : ajouter deux fois la meme
|
|
* permission n'entraine pas de doublon dans la collection.
|
|
*/
|
|
public function addPermission(Permission $permission): static
|
|
{
|
|
if (!$this->permissions->contains($permission)) {
|
|
$this->permissions->add($permission);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Retire une permission du role. Idempotent : retirer une permission
|
|
* absente est un no-op silencieux.
|
|
*/
|
|
public function removePermission(Permission $permission): static
|
|
{
|
|
$this->permissions->removeElement($permission);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Garde domaine : refuse la suppression d'un role marque comme systeme.
|
|
* La traduction HTTP (403) est faite au niveau application / API Platform.
|
|
*/
|
|
public function ensureDeletable(): void
|
|
{
|
|
if ($this->isSystem) {
|
|
throw SystemRoleDeletionException::forRole($this);
|
|
}
|
|
}
|
|
}
|