Terrain 6 — Doctrine : requêtes & perf
1. Méthode de repository ⭐⭐
🕹️ Mission : countPendingForUser(User $user): int avec le QueryBuilder.
✅ Solution
return (int) $this->createQueryBuilder('b')
->select('COUNT(b.id)')
->where('b.user = :u')->setParameter('u', $user)
->andWhere('b.status = :s')->setParameter('s', BookingStatus::Pending)
->getQuery()->getSingleScalarResult();2. Corriger une injection SQL ⭐⭐
🕹️ Mission : sécurisez ->where("b.email = '$email'").
✅ Solution
->where('b.email = :email')->setParameter('email', $email)3. Diagnostiquer un N+1 ⭐⭐⭐
🕹️ Mission : une liste affiche booking.slot.service.name par ligne → 101 requêtes. Que regardez-vous et que corrigez-vous ?
✅ Solution
Le profiler (onglet Doctrine, nombre de requêtes). Correction : fetch join —
->addSelect('s', 'svc')->join('b.slot', 's')->join('s.service', 'svc')4. Fetch join complet ⭐⭐⭐
🕹️ Mission : chargez les réservations avec leur slot et service en une requête.
✅ Solution
$this->createQueryBuilder('b')
->addSelect('s', 'svc')->join('b.slot', 's')->join('s.service', 'svc')
->getQuery()->getResult();5. Piège du join sans addSelect ⭐⭐⭐
🕹️ Mission : pourquoi ->join('b.slot', 's') seul ne corrige pas toujours le N+1 ?
✅ Solution
join filtre mais n’hydrate pas la relation ; il faut addSelect('s') pour charger réellement les slots.
6. Projection DTO ⭐⭐⭐⭐
🕹️ Mission : listez id, email, status en objets légers (pas d’entités).
✅ Solution
$this->getEntityManager()->createQuery(
'SELECT NEW App\Dto\BookingListItem(b.id, b.customerEmail, b.status) FROM App\Entity\Booking b'
)->getResult();7. Pagination ⭐⭐⭐
🕹️ Mission : paginez avec offset/limit + total correct.
✅ Solution
$q = $this->createQueryBuilder('b')->setFirstResult(($p-1)*$per)->setMaxResults($per)->getQuery();
return new \Doctrine\ORM\Tools\Pagination\Paginator($q); // count() = total8. Ajouter un index ⭐⭐
🕹️ Mission : indexez la colonne status fréquemment filtrée.
✅ Solution
#[ORM\Index(name: 'idx_booking_status', columns: ['status'])]
class Booking {}(Puis une migration.)
9. Traiter 500k lignes ⭐⭐⭐⭐
🕹️ Mission : parcourez 500 000 entités sans saturer la mémoire.
✅ Solution
foreach ($q->toIterable() as $i => $e) {
/* traiter */
if ($i % 500 === 0) { $em->flush(); $em->clear(); }
}
$em->flush();10. COUNT sécurisé ⭐
🕹️ Mission : comptez les réservations Pending.
✅ Solution
$repo->count(['status' => BookingStatus::Pending]);Terrain suivant : Twig & composants.