src/Entity/WhileProject/MissionsCategories.php line 34

Open in your IDE?
  1. <?php
  2. namespace App\Entity\WhileProject;
  3. use Doctrine\Common\Collections\ArrayCollection;
  4. use Doctrine\Common\Collections\Collection;
  5. use Doctrine\ORM\Mapping as ORM;
  6. /**
  7. * Catégories de missions WhileProject.
  8. *
  9. * Modèle "1 row = 1 langue" :
  10. * - `locale` (fr|en|...) identifie la langue de la traduction
  11. * - `code` est l'identifiant stable cross-langue
  12. * (ex: "dev-web" existe en FR ET en EN, avec un name différent)
  13. * - `slug` est l'URL-friendly, peut différer par locale
  14. *
  15. * Hiérarchie : self-FK via `parent` / `children`. Une sous-catégorie DOIT
  16. * avoir la même locale que son parent (validé en service à l'import).
  17. *
  18. * @ORM\Entity(repositoryClass="App\Repository\WhileProject\MissionsCategoriesRepository")
  19. * @ORM\Table(name="whileproject_missions_categories",
  20. * uniqueConstraints={
  21. * @ORM\UniqueConstraint(name="uk_missions_categories_locale_slug", columns={"locale","slug"}),
  22. * @ORM\UniqueConstraint(name="uk_missions_categories_locale_code", columns={"locale","code"})
  23. * },
  24. * indexes={
  25. * @ORM\Index(name="idx_missions_categories_parent", columns={"parent_id"}),
  26. * @ORM\Index(name="idx_missions_categories_locale", columns={"locale"}),
  27. * @ORM\Index(name="idx_missions_categories_enabled", columns={"enabled"})
  28. * }
  29. * )
  30. */
  31. class MissionsCategories
  32. {
  33. /**
  34. * @ORM\Id()
  35. * @ORM\GeneratedValue()
  36. * @ORM\Column(type="integer")
  37. */
  38. private $id;
  39. /**
  40. * Langue de cette traduction. Ex: "fr", "en", "de", "es".
  41. *
  42. * @ORM\Column(type="string", length=5)
  43. */
  44. private $locale = 'fr';
  45. /**
  46. * Identifiant stable cross-langue (ex: "dev-web").
  47. * Permet de relier la version FR et EN d'une même catégorie.
  48. *
  49. * @ORM\Column(type="string", length=80)
  50. */
  51. private $code;
  52. /**
  53. * Slug URL-friendly (peut être différent du code, et différent par langue).
  54. *
  55. * @ORM\Column(type="string", length=140)
  56. */
  57. private $slug;
  58. /**
  59. * @ORM\Column(type="string", length=120)
  60. */
  61. private $name;
  62. /**
  63. * @ORM\Column(type="text", nullable=true)
  64. */
  65. private $description;
  66. /**
  67. * @ORM\Column(type="string", length=80, nullable=true)
  68. */
  69. private $icon;
  70. /**
  71. * @ORM\Column(type="string", length=20, nullable=true)
  72. */
  73. private $color;
  74. /**
  75. * @ORM\Column(type="string", length=180, nullable=true)
  76. */
  77. private $seoTitle;
  78. /**
  79. * @ORM\Column(type="string", length=250, nullable=true)
  80. */
  81. private $seoDescription;
  82. /**
  83. * @ORM\Column(type="integer")
  84. */
  85. private $position = 0;
  86. /**
  87. * @ORM\Column(type="boolean")
  88. */
  89. private $enabled = true;
  90. /**
  91. * Catégorie parente (NULL = racine).
  92. *
  93. * @ORM\ManyToOne(targetEntity="App\Entity\WhileProject\MissionsCategories", inversedBy="children")
  94. * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
  95. */
  96. private $parent;
  97. /**
  98. * Sous-catégories.
  99. *
  100. * @ORM\OneToMany(targetEntity="App\Entity\WhileProject\MissionsCategories", mappedBy="parent")
  101. * @ORM\OrderBy({"position"="ASC", "name"="ASC"})
  102. */
  103. private $children;
  104. /**
  105. * @ORM\Column(type="datetime")
  106. */
  107. private $createdAt;
  108. /**
  109. * @ORM\Column(type="datetime")
  110. */
  111. private $updatedAt;
  112. public function __construct()
  113. {
  114. $this->children = new ArrayCollection();
  115. $this->createdAt = new \DateTime('now');
  116. $this->updatedAt = new \DateTime('now');
  117. }
  118. // ----- Getters / setters --------------------------------------------------
  119. public function getId(): ?int { return $this->id; }
  120. public function getLocale(): string { return $this->locale; }
  121. public function setLocale(string $v): self { $this->locale = strtolower($v); return $this; }
  122. public function getCode(): ?string { return $this->code; }
  123. public function setCode(string $v): self { $this->code = $v; return $this; }
  124. public function getSlug(): ?string { return $this->slug; }
  125. public function setSlug(string $v): self { $this->slug = $v; return $this; }
  126. public function getName(): ?string { return $this->name; }
  127. public function setName(string $v): self { $this->name = $v; return $this; }
  128. public function getDescription(): ?string { return $this->description; }
  129. public function setDescription(?string $v): self { $this->description = $v; return $this; }
  130. public function getIcon(): ?string { return $this->icon; }
  131. public function setIcon(?string $v): self { $this->icon = $v; return $this; }
  132. public function getColor(): ?string { return $this->color; }
  133. public function setColor(?string $v): self { $this->color = $v; return $this; }
  134. public function getSeoTitle(): ?string { return $this->seoTitle; }
  135. public function setSeoTitle(?string $v): self { $this->seoTitle = $v; return $this; }
  136. public function getSeoDescription(): ?string { return $this->seoDescription; }
  137. public function setSeoDescription(?string $v): self { $this->seoDescription = $v; return $this; }
  138. public function getPosition(): int { return $this->position; }
  139. public function setPosition(int $v): self { $this->position = $v; return $this; }
  140. public function isEnabled(): bool { return $this->enabled; }
  141. public function setEnabled(bool $v): self { $this->enabled = $v; return $this; }
  142. public function getParent(): ?self { return $this->parent; }
  143. public function setParent(?self $v): self
  144. {
  145. // Sécurité : un parent doit être dans la même locale
  146. if ($v !== null && $v->getLocale() !== $this->locale) {
  147. throw new \InvalidArgumentException(sprintf(
  148. 'Parent locale (%s) doit correspondre à child locale (%s)',
  149. $v->getLocale(),
  150. $this->locale
  151. ));
  152. }
  153. // Sécurité : pas d'auto-référence
  154. if ($v !== null && $v === $this) {
  155. throw new \InvalidArgumentException('Une catégorie ne peut pas être son propre parent');
  156. }
  157. $this->parent = $v;
  158. return $this;
  159. }
  160. /** @return Collection<int, self> */
  161. public function getChildren(): Collection { return $this->children; }
  162. public function addChild(self $child): self
  163. {
  164. if (!$this->children->contains($child)) {
  165. $this->children->add($child);
  166. $child->setParent($this);
  167. }
  168. return $this;
  169. }
  170. public function removeChild(self $child): self
  171. {
  172. if ($this->children->removeElement($child)) {
  173. if ($child->getParent() === $this) {
  174. $child->setParent(null);
  175. }
  176. }
  177. return $this;
  178. }
  179. public function getCreatedAt(): ?\DateTimeInterface { return $this->createdAt; }
  180. public function setCreatedAt(\DateTimeInterface $v): self { $this->createdAt = $v; return $this; }
  181. public function getUpdatedAt(): ?\DateTimeInterface { return $this->updatedAt; }
  182. public function setUpdatedAt(\DateTimeInterface $v): self { $this->updatedAt = $v; return $this; }
  183. // ----- Helpers ------------------------------------------------------------
  184. public function isRoot(): bool { return $this->parent === null; }
  185. public function hasChildren(): bool { return !$this->children->isEmpty(); }
  186. /** Profondeur dans l'arbre (0 = racine). */
  187. public function getDepth(): int
  188. {
  189. $depth = 0;
  190. $current = $this->parent;
  191. while ($current !== null) {
  192. $depth++;
  193. $current = $current->getParent();
  194. if ($depth > 10) break; // garde-fou anti-boucle
  195. }
  196. return $depth;
  197. }
  198. }