Skip to Content
SymfonyPartie 18 — Mini-jeux (exploration guidée)Terrain 5 — Doctrine : entités & relations

Terrain 5 — Doctrine : entités & relations

🔍 Renfort : 6.1, 6.2.

1. Entité simple ⭐

🕹️ Mission : créez une entité Provider (id, name, slug unique).

✅ Solution

#[ORM\Entity] class Provider { #[ORM\Id, ORM\GeneratedValue, ORM\Column] private ?int $id = null; #[ORM\Column(length: 255)] private string $name; #[ORM\Column(length: 255, unique: true)] private string $slug; }

2. Enum en colonne ⭐⭐

🕹️ Mission : mappez BookingStatus sur une colonne.

✅ Solution

#[ORM\Column(enumType: BookingStatus::class)] private BookingStatus $status = BookingStatus::Pending;

3. ManyToOne ⭐⭐

🕹️ Mission : reliez Slot à Service (un slot appartient à un service). Quel côté porte la FK ?

✅ Solution

Côté Slot (propriétaire, porte la FK) :

#[ORM\ManyToOne(inversedBy: 'slots')] #[ORM\JoinColumn(nullable: false)] private ?Service $service = null;

4. OneToMany + collection ⭐⭐⭐

🕹️ Mission : côté Service, la collection slots avec helpers synchronisant les deux côtés.

✅ Solution

/** @var Collection<int, Slot> */ #[ORM\OneToMany(targetEntity: Slot::class, mappedBy: 'service', orphanRemoval: true)] private Collection $slots; public function __construct() { $this->slots = new ArrayCollection(); } public function addSlot(Slot $s): static { if (!$this->slots->contains($s)) { $this->slots->add($s); $s->setService($this); } return $this; }

5. Timestamps automatiques ⭐⭐⭐

🕹️ Mission : ajoutez createdAt/updatedAt auto. Quelle annotation sur la classe ne pas oublier ? 🔍 6.5.

✅ Solution

#[ORM\HasLifecycleCallbacks] sur la classe, + #[ORM\PrePersist]/#[ORM\PreUpdate] sur des méthodes qui posent les dates.

6. Générer une migration ⭐⭐

🕹️ Mission : après avoir ajouté une propriété, quelles commandes ? Quelle étape ne pas sauter ?

✅ Solution

make:migrationrelire le fichier généré → doctrine:migrations:migrate → committer. Ne pas sauter la relecture.

7. Slug via entity listener ⭐⭐⭐⭐

🕹️ Mission : générez le slug d’un Provider en prePersist via un service Slugger. Callback ou listener ? 🔍 6.5.

✅ Solution

Un entity listener (injectable), car un callback d’entité ne peut pas recevoir de service :

final class ProviderSlugListener { public function __construct(private Slugger $slugger) {} public function prePersist(Provider $p): void { if ($p->getSlug() === null) $p->setSlug($this->slugger->slugify($p->getName())); } }

8. Montant en centimes ⭐⭐

🕹️ Mission : stockez un montant sans erreur d’arrondi.

✅ Solution

Un int en centimes (private int $amountCents;) ou un decimal — jamais un float.

9. OneToOne ⭐⭐⭐

🕹️ Mission : reliez Booking et Payment (au plus un paiement par réservation).

✅ Solution

// Payment (propriétaire) #[ORM\OneToOne(inversedBy: 'payment')] #[ORM\JoinColumn(nullable: false)] private ?Booking $booking = null; // Booking (inverse) #[ORM\OneToOne(mappedBy: 'booking')] private ?Payment $payment = null;

10. Cascade prudente ⭐⭐⭐⭐

🕹️ Mission : supprimer un Service doit effacer ses Slot mais jamais les Booking. Comment ?

✅ Solution

orphanRemoval: true (ou cascade: ['remove']) sur Service::$slots ; aucune cascade remove vers Booking, et onDelete: 'RESTRICT' sur la FK des réservations (ou archivage) pour les protéger.


Terrain suivant : Doctrine : requêtes & perf.