Skip to content

studoo-app/symfony-tp4-repository-doctrine-correction

Repository files navigation

separe

Symfony TP 4 - Utilisation des repositories Doctrine

Version Niveau

🎯 Contexte professionnel

Vous travaillez pour MediConnect, une startup qui développe une plateforme numérique de gestion de consultations médicales pour les maisons de santé pluriprofessionnelles. La plateforme doit permettre aux structures de santé de coordonner leurs consultations (médecine générale, spécialités, téléconsultations, urgences) et aux patients de les trouver facilement.

Le système doit gérer :

  • Consultations avec leurs détails et planning
  • Cabinets médicaux (centres de santé, cabinets libéraux, cliniques)
  • Praticiens (médecins, infirmiers, kinésithérapeutes, psychologues)
  • Spécialités médicales pour faciliter la recherche

Votre mission : développer le module de recherche et de manipulation des données en utilisant les Repository Pattern, DQL, Query Builder, et les techniques d'optimisation de performance.

📋 Objectifs pédagogiques

Compétences techniques visées :

  • Maîtriser le Repository Pattern et créer des repositories personnalisés
  • Construire des requêtes complexes avec DQL et Query Builder
  • Optimiser les performances en évitant les problèmes N+1
  • Implémenter des systèmes de recherche et de filtrage avancés
  • Utiliser les techniques de pagination et de cache
  • Analyser et optimiser les performances avec le Profiler Doctrine

Compétences transversales :

  • Concevoir des interfaces de recherche intuitives
  • Structurer le code selon les bonnes pratiques du Repository Pattern
  • Diagnostiquer et résoudre les problèmes de performance

🛠️ Consignes détaillées

🚀 Phase 1 : Modélisation et Entities de Base (60 minutes)

Étape 1.1 : Préparation du projet

Créez un nouveau projet Symfony et installez les dépendances :

symfony new symfony-tp4-repository-doctrine --webapp

Étape 1.2 : Création des entités principales

Créez les quatre entités de base avec la commande make:entity. Pour cette phase, nous allons nous concentrer sur Cabinet et Praticien.

Entité Cabinet :

symfony console make:entity Cabinet

Ajoutez les propriétés suivantes :

  • nom : string, 200 caractères
  • adresse : string, 255 caractères
  • ville : string, 100 caractères
  • capaciteAccueil : integer
  • typeCabinet : string, 50 caractères (centres de santé, cabinets libéraux, cliniques)
  • description : text, nullable

Entité Praticien :

symfony console make:entity Praticien

Ajoutez les propriétés suivantes :

  • nom : string, 200 caractères
  • email : string, 180 caractères
  • telephone : string, 14 caractères, nullable (format souhaite '01-02-03-04-05')
  • numeroOrdre : string, 255 caractères, nullable
  • description : text, nullable

Entité SpecialiteMedicale :

symfony console make:entity SpecialiteMedicale

Ajoutez les propriétés suivantes :

  • nom : string, 100 caractères
  • couleur : string, 7 caractères
  • icone : string, 50 caractères
  • description : text, nullable

Entité Consultation :

symfony console make:entity Consultation

Ajoutez les propriétés suivantes :

  • titre : string, 250 caractères
  • description : text
  • dateDebut : datetime
  • dateFin : datetime
  • tarif : decimal (10,2), nullable
  • creneauxDisponibles : integer
  • estPublie : boolean
  • dateCreation : datetime

Étape 1.3 : Ajout des relations

Modifiez l'entité Consultation pour ajouter les relations :

symfony console make:entity Consultation

Ajoutez les relations bidirectionnelles suivantes :

  • cabinet : relation ManyToOne vers Cabinet
  • praticien : relation ManyToOne vers Praticien
  • specialite : relation ManyToOne vers SpecialiteMedicale

Modifiez l'entité Praticien pour ajouter la relation :

symfony console make:entity Praticien

Ajoutez les relations bidirectionnelles suivantes :

  • specialite : relation ManyToOne vers SpecialiteMedicale

Générez la migration et mettez à jour la base de données :

symfony console make:migration
symfony console doctrine:migrations:migrate

Étape 1.4 : Fixtures de données

Créez des fixtures pour peupler votre base avec des données de test :

composer require --dev orm-fixtures fakerphp/faker

Créez au minimum :

  • 5 cabinets différents dans 3 villes
  • 3 praticiens de types différents
  • 6 spécialités médicales
  • 20 consultations variées (passées, présentes, futures)

Étape 1.5 : Contrôleur CabinetController avec CRUD complet

Créez le contrôleur pour gérer les cabinets :

symfony console make:controller CabinetController

Implémenter les fonctions suivantes :

<?php

namespace App\Controller;

use App\Entity\Cabinet;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Faker\Factory;

#[Route('/cabinet')]
class CabinetController extends AbstractController
{
    #[Route('/', name: 'cabinet_index')]
    public function index(EntityManagerInterface $em): Response
    {
        // Récupération de tous les cabinets avec la méthode findAll()

        // retourne la vue twig associée avec les cabinets

    }
    
    #[Route('/create', name: 'cabinet_create')]
    public function create(EntityManagerInterface $em): Response
    {
        // Utilisation de Faker pour générer des données aléatoires
        
        // Persist et flush pour sauvegarder en base
        
        // Message flash pour confirmer la création
        
        // Redirection vers l'index

    }
    
    #[Route('/{id}', name: 'cabinet_show', requirements: ['id' => '\d+'])]
    public function show(int $id, EntityManagerInterface $em): Response
    {
        // Récupération d'un cabinet par son ID avec find()

        // Gestion de l'ID n'existant pas
        
        // Retourne la vue twig associée au detail d'un cabinet

    }
    
    #[Route('/{id}/update', name: 'cabinet_update', requirements: ['id' => '\d+'])]
    public function update(int $id, EntityManagerInterface $em): Response
    {
        $faker = Factory::create('fr_FR');
        
        // Récupération du cabinet (l'entité devient automatiquement "Managed")
       
       // Gestion de l'ID n'existant pas
        
        // Modification aléatoire de certaines propriétés
        
        // PAS BESOIN de persist() pour une entité existante !
        // Le dirty checking de Doctrine détecte automatiquement les modifications, mais il faut quand les flush

        // Message flash pour confirmer la modification

        // Redirection vers la page de visualisation

    }
    
    #[Route('/{id}/delete', name: 'cabinet_delete', requirements: ['id' => '\d+'])]
    public function delete(int $id, EntityManagerInterface $em): Response
    {
	    // Récupération du cabinet

        // Gestion de l'ID n'existant pas
        
        // Extraction du nom du cabinet pour le message flash
        
        // Suppression de l'entité
        
        // Message flash pour confirmer la suppression
        
        // Redirection vers l'index
    }
}

Étape 1.6 : Mission autonome - Contrôleur PraticienController

Mission : Sur le modèle du CabinetController, créez un PraticienController complet avec :

  1. Route /praticien - Index listant tous les praticiens
  2. Route /praticien/create - Création automatique d'un praticien avec Faker
  3. Route /praticien/{id} - Affichage des détails d'un praticien
  4. Route /praticien/{id}/update - Mise à jour aléatoire (email, téléphone, description)
  5. Route /praticien/{id}/delete - Suppression d'un praticien

🔍 Phase 2 : Repositories Personnalisés et Requêtes de Base (75 minutes)

Étape 2.1 : Repository ConsultationRepository

Créez un repository personnalisé pour l'entité Consultation avec les méthodes suivantes :

class ConsultationRepository extends ServiceEntityRepository
{
    /**
     * Trouve les consultations publiées et à venir
     */
    public function findConsultationsAvenir(): array
    {
        // À implémenter avec QueryBuilder
    }
    
    /**
     * Trouve les consultations par spécialité
     */
    public function findBySpecialite(SpecialiteMedicale $specialite): array
    {
        // À implémenter
    }
    
    /**
     * Trouve les consultations dans une ville donnée
     */
    public function findByVille(string $ville): array
    {
        // À implémenter avec jointure sur Cabinet
    }
    
    /**
     * Compte le nombre de consultations publiées
     */
    public function countConsultationsPubliees(): int
    {
        // À implémenter
    }
}

Étape 2.2 : Repository CabinetRepository

Créez un repository pour l'entité Cabinet :

class CabinetRepository extends ServiceEntityRepository
{
    /**
     * Trouve les cabinets par type avec le nombre de consultations
     */
    public function findCabinetsAvecNombreConsultations(): array
    {
        // À implémenter avec LEFT JOIN et GROUP BY
    }
    
    /**
     * Trouve les cabinets les plus actifs (avec le plus de consultations)
     */
    public function findCabinetsLesPlusActifs(int $limit = 5): array
    {
        // À implémenter
    }
}

Mission autonome : Implémentez ces méthodes en utilisant le QueryBuilder. Testez-les avec des données factices créées manuellement.

🔧 Phase 3 : Requêtes DQL Avancées (90 minutes)

Étape 3.1 : Système de recherche complexe

Implémentez une méthode de recherche avancée dans ConsultationRepository :

/**
 * Recherche de consultations avec filtres multiples
 * 
 * @param array $criteres [
 *   'terme' => string|null,           // Recherche dans titre et description
 *   'specialite' => SpecialiteMedicale|null,
 *   'ville' => string|null,
 *   'dateDebut' => DateTime|null,     // À partir de cette date
 *   'dateFin' => DateTime|null,       // Jusqu'à cette date
 *   'tarifMax' => float|null,         // Tarif maximum
 *   'gratuit' => bool|null            // Seulement les consultations gratuites
 * ]
 */
public function rechercherConsultations(array $criteres = []): array
{
    $qb = $this->createQueryBuilder('c')
        ->leftJoin('c.cabinet', 'cab')
        ->leftJoin('c.specialite', 's')
        ->leftJoin('c.praticien', 'p');
    
    // Ajouter les conditions dynamiquement selon les critères
    // Utiliser addSelect() pour optimiser les jointures
    
    return $qb->getQuery()->getResult();
}

Étape 3.2 : Requêtes statistiques avec DQL

Afin d'accéder à certaines fonctions liées aux dates, il est necéssaire d'installer une extension doctrine ajoutant des fonctions au DQL de base:

https://github.com/beberlei/DoctrineExtensions

composer require beberlei/doctrineextensions

Modifier ensuite le fichier config/packages/doctrine.yaml afin d'ajouter vos fonctions:

doctrine:
	# ...
	orm:
	# ...
		dql:
            datetime_functions:
                month: DoctrineExtensions\Query\Mysql\Month
                year: DoctrineExtensions\Query\Mysql\Year

Créez des requêtes pour générer des statistiques :

/**
 * Statistiques des consultations par mois
 */
public function getStatistiquesParMois(int $annee): array
{
    // Utiliser les fonctions DATE() de DQL
    // Retourner un tableau avec mois => nombre de consultations
}

/**
 * Top 10 des praticiens les plus actifs
 */
public function getTopPraticiens(int $limit = 10): array
{
    // Jointure avec GROUP BY et ORDER BY COUNT
}

Étape 3.3 : Gestion des relations optimisées

Implémentez une méthode qui évite le problème N+1 :

/**
 * Récupère les consultations avec toutes leurs relations
 * Optimisé pour éviter le problème N+1
 */
public function findConsultationsAvecRelations(): array
{
    // Utiliser plusieurs addSelect() pour charger :
    // - cabinet, praticien, specialite en une seule requête
}

🎨 Phase 4 : Interface de Recherche et Filtrage (75 minutes)

Étape 4.1 : API de recherche JSON

Créez une route /api/recherche qui retourne du JSON pour une utilisation AJAX :

/**
 * @Route("/api/recherche", name="api_recherche", methods={"GET"})
 */
public function apiRecherche(Request $request, ConsultationRepository $repo): JsonResponse
{
    $terme = $request->query->get('q', '');
    $ville = $request->query->get('ville');
    $specialite = $request->query->get('specialite');
    
    $criteres = array_filter([
        'terme' => $terme ?: null,
        'ville' => $ville ?: null,
        'specialite' => $specialite ? $this->getSpecialiteById($specialite) : null
    ]);
    
    $consultations = $repo->rechercherConsultations($criteres);
    
    // Sérialiser en JSON avec les données essentielles
    $data = array_map(function($consultation) {
        return [
            'id' => $consultation->getId(),
            'titre' => $consultation->getTitre(),
            'date' => $consultation->getDateDebut()->format('Y-m-d H:i'),
            'cabinet' => $consultation->getCabinet()->getNom(),
            'ville' => $consultation->getCabinet()->getVille(),
            'tarif' => $consultation->getTarif()
        ];
    }, $consultations);
    
    return new JsonResponse($data);
}

📖 Ressources utiles

Documentation officielle

Ressources pour l'optimisation

About

Manipulation des données avec les repositories Doctrine

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published