'exact', 'type' => 'exact'])] #[ApiFilter(ExistsFilter::class, properties: ['parent'])] #[ApiResource( operations : [ new Get(), new GetCollection(), new Post(security: "is_granted('ROLE_ADMIN')", processor: ForumPositionProcessor::class), new Patch(security: "is_granted('ROLE_ADMIN')", processor: ForumPositionProcessor::class), new Delete(security: "is_granted('ROLE_ADMIN')") ], normalizationContext : ['groups' => ['forum:read']], denormalizationContext: ['groups' => ['forum:write']], order : ['position' => 'ASC'] )] class Forum { public const TYPE_CATEGORY = 'category'; public const TYPE_FORUM = 'forum'; #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] #[Groups(['forum:read', 'thread:read'])] private ?int $id = null; #[ORM\Column(length: 100)] #[Assert\NotBlank] #[Groups(['forum:read', 'forum:write', 'thread:read'])] private ?string $name = null; #[ORM\Column(type: 'text', nullable: true)] #[Groups(['forum:read', 'forum:write'])] private ?string $description = null; #[ORM\Column(length: 20)] #[Assert\Choice(choices: [self::TYPE_CATEGORY, self::TYPE_FORUM])] #[Groups(['forum:read', 'forum:write'])] private string $type = self::TYPE_CATEGORY; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[Assert\Expression( "this.getParent() === null or this.getParent().isCategory()", message: "Parent must be a category." )] #[Groups(['forum:read', 'forum:write'])] private ?self $parent = null; #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] #[Groups(['forum:read'])] private Collection $children; #[ORM\OneToMany(mappedBy: 'forum', targetEntity: Thread::class)] #[Groups(['forum:read'])] private Collection $threads; #[ORM\Column] #[Groups(['forum:read'])] private int $position = 0; #[ORM\Column] #[Groups(['forum:read'])] private ?\DateTimeImmutable $createdAt = null; #[ORM\Column] #[Groups(['forum:read'])] private ?\DateTimeImmutable $updatedAt = null; public function __construct() { $this->children = new ArrayCollection(); $this->threads = new ArrayCollection(); } #[ORM\PrePersist] public function onCreate(): void { $now = new \DateTimeImmutable(); $this->createdAt = $now; $this->updatedAt = $now; } #[ORM\PreUpdate] public function onUpdate(): void { $this->updatedAt = new \DateTimeImmutable(); } public function getId(): ?int { return $this->id; } public function getName(): ?string { return $this->name; } public function setName(string $name): self { $this->name = $name; return $this; } public function getDescription(): ?string { return $this->description; } public function setDescription(?string $description): self { $this->description = $description; return $this; } public function getType(): string { return $this->type; } public function setType(string $type): self { $this->type = $type; return $this; } public function getParent(): ?self { return $this->parent; } public function setParent(?self $parent): self { $this->parent = $parent; return $this; } /** * @return Collection */ public function getChildren(): Collection { return $this->children; } public function getPosition(): int { return $this->position; } public function setPosition(int $position): self { $this->position = $position; return $this; } /** * @return Collection */ public function getThreads(): Collection { return $this->threads; } public function isCategory(): bool { return $this->type === self::TYPE_CATEGORY; } public function isForum(): bool { return $this->type === self::TYPE_FORUM; } }