Module Framework - partie 4 - année 2021-22 Yoann DIEUDONNÉ & Stéphane DEVISMES
Module Framework - partie 4 - année 2021-22 Yoann DIEUDONNÉ & Stéphane DEVISMES - Université de Picardie Doctrine : association d’entité et formulaire sur entité Association … Relation : • « ordinateur(s) installé(s) dans la salle ». ci-contre la relation entre tables dans une base de données : Les relations sont « classiques » en base de données : comment se traduisent-elles entre entités de Doctrine ? ◦En association entre entités : D’un coté, des références (pointeurs) sur des objets, de l’autre, des clés étrangères (foreign key) et des jointures. ◦Cardinalité des associations : ▪ManyToOne exemple : la salle dans laquelle est installée l'ordinateur ▪OneToMany exemple : les ordinateurs installés dans la salle donc bidirectionnelle de la ManyToOne ▪ManyToMany exemple : les logiciels installés sur des ordinateurs un logiciel peut être installé sur plusieurs ordinateurs un ordinateur peut avoir plusieurs logiciels ▪OneToOne exemple: une personne et son numéro de sécurité sociale …. non étudié ◦Sens de l'association: ▪unidirectionnelle : une seule ! • ManyToOne • ManyToMany • OneToOne ▪Bidirectionnelle : l’association et son inverse • une ManyToOne et la OneToMany inverse • deux ManyToMany • deux OneToOne • Le coté « owner » des associations bidirectionnelles : problème compliqué …...à suivre many to one unidirectionnel : • Ajoutons une association à l’entité Ordinateur : ◦lancez la commande make:entity : symfony console make:entity Class name of the entity to create or update (e.g. OrangePuppy): > Ordinateur New property name (press <return> to stop adding fields): > salle Field type (enter ? to see all types) [string]: > relation What class should this entity be related to?: > Salle What type of relationship is this? Relation type? [ManyToOne, OneToMany, ManyToMany, OneToOne]: > ManyToOne Is the Ordinateur.salle property allowed to be null (nullable)? (yes/no) [yes]: > yes Do you want to add a new property to Salle so that you can access/update Ordinateur objects from it - e.g. $salle->getOrdinateurs()? (yes/no) [yes]: > no updated: src/Entity/Ordinateur.php Add another property? Enter the property name (or press <return> to stop adding fields): > Success! ◦Nous définissons une association unidirectionnelle "salle" de l'entité Ordinateur vers l'entité Salle: • rmq : le nom de l'association «salle» pourrait être différent de celui de l'Entité correspondante ▪chaque ordinateur est associé à une salle (au plus) ou pas ▪plusieurs ordinateurs pourront être associés à la même salle ◦Voici ce qui a été ajouté dans le fichier de l'entité Ordinateur : // ne pas programmer !! ... /** * @ORM\ManyToOne(targetEntity="App\Entity\Salle") */ private $salle; ... public function setSalle(\SalleTpBundle\Entity\Salle $salle = null) { $this->salle = $salle; return $this; } public function getSalle() { return $this->salle; } } ◦Mettez à jour la base de données symfony console make:migration Success! symfony console doctrine:migrations:migrate -> ALTER TABLE ordinateur ADD salle_id INT DEFAULT NULL -> ALTER TABLE ordinateur ADD CONSTRAINT FK_8712E8DBDC304035 FOREIGN KEY (salle_id) REFERENCES salle (id) -> CREATE INDEX IDX_8712E8DBDC304035 ON ordinateur (salle_id) ▪Ceci ajoute un champ "salle_id" dans la table ordinateur qui est une clé étrangère vers l"id" de la table salle • Ajout d'entités reliées ◦Associons un ordinateur à une salle ▪modifier EssaiController : use App\Entity\Ordinateur; use App\Entity\Salle; ... public function test25() { $em = $this->getDoctrine()->getManager(); $salle = $em->getRepository(Salle::class)->findOneBy(array('batiment'=>'D', 'etage'=>7, 'numero'=>71)); $ordi = new Ordinateur; $ordi->setNumero(702); $ordi->setIp('192.168.7.02'); $ordi->setSalle($salle); $em->persist($ordi); $em->flush(); dump($ordi); return new Response('<html><body></body></html>'); } ◦tester http://localhost:8000/essai/test25 App\Entity\Ordinateur^ {#676 -id: 3 -ip: "192.168.7.02" -numero: 702 -salle: App\Entity\Salle^ {#678 -id: 9 -batiment: "D" -etage: 7 -numero: 71 } } ▪L’association s’est bien faite. • Premier Problème … de persistance ◦modifier EssaiController : public function test26() { $em = $this->getDoctrine()->getManager(); $salle = new Salle; $salle->setBatiment('B'); $salle->setEtage(0); $salle->setNumero(0); $ordi = new Ordinateur; $ordi->setNumero(701); $ordi->setIp('192.168.7.01'); $ordi->setSalle($salle); $em->persist($ordi); $em->flush(); dump($ordi); return new Response('<html><body></body></html>'); } ◦tester http://localhost:8000/essai/test26 ▪effectivement, la salle n’est pas "persistée" donc persistante dans le test25, il n'y a pas eu d'erreur car "find()" entraîne la persistance de la salle obtenue. ◦Correction : Persistons aussi l'entité salle public function test26() { ... $em->persist($ordi); $em->persist($salle); $em->flush(); ... ◦Executons http://localhost:8000/essai/test26 ▪ça marche ! ▪C'est au programmeur de mettre en place pour les entités les actions qui devront être répercutées au niveau de la base de données ! Bref, la persistance. • Les cascades : ◦Modifiez le fichier Entity/Ordinateur.php /** * @ORM\ManyToOne(targetEntity="App\Entity\Salle", cascade={"persist"}) */ private $salle; ◦ la persistance de l'entité Ordinateur est étendue en cascade à son entité associée ▪Écrivons un nouveau test du code qui avait généré une erreur de persistance : … avec d'autres numéros d'ordinateur public function test27() { $em = $this->getDoctrine()->getManager(); $salle = new Salle; $salle->setBatiment('B'); $salle->setEtage(0); $salle->setNumero(1); $ordi = new Ordinateur; $ordi->setNumero(703); $ordi->setIp('192.168.7.03'); $ordi->setSalle($salle); $em->persist($ordi); $em->flush(); dump($ordi); return new Response('<html><body></body></html>'); } ▪Exécutons http://localhost:8000/essai/test27 : la cascade de persistance marche ! ◦(++) Il existe d’autres cascades : remove, …. merge, detach, refresh • Lire des données d’entités associées ▪Écrivons un nouveau test28 : public function test28() { $ordi = $this->getDoctrine()->getManager() ->getRepository(Ordinateur::class) ->findOneByNumero(703); dump($ordi); $batiment = $ordi->getSalle()->getBatiment(); dump($batiment); dump($ordi); return new Response('<html><body></body></html>'); } ◦Ce qui donne bien le résultat escompté ◦Comment s’est fait la lecture des données ? ▪Regardons les requêtes SqL dans le Profiler : • il y a eu 2 requêtes successives • Cela met en évidence le « lazy loading » chargement fainéant ◦le chargement de l'entité associée est confié à un « proxy » qui le fera uniquement quand ce sera nécessaire ◦Heureusement, par défaut, quand une entité est lue depuis la base, toutes les entités qui lui sont associées ne sont pas lues automatiquement, sinon les performances seraient dégradées. Pensez à sauvegarder ! → chap4page14.zip many to one bidirectionnel : Créons une association OneToMany nommée ordinateurs de Salle vers Ordinateur : ◦c'est l'inverse de la ManyToOne "salle" ◦c'est une collection/liste/tableau de pointeur vers les ordinateurs associés ◦Nous obtenons une association bidirectionnelle entre les entités Ordinateur et Salle • Ajoutez l'association de Salle vers Ordinateur : ..... /** * @ORM\OneToMany(targetEntity="App\Entity\Ordinateur", mappedBy="salle", cascade={"persist"}) */ private $ordinateurs; ◦et modifiez l'entité Ordinateur.php ainsi : ..... /** * @ORM\ManyToOne(targetEntity="App\Entity\Salle", inversedBy="ordinateurs", cascade={"persist"}) */ private $salle; ▪les paramètres mappedBy et inversedBy désignent l'association inverse/réciproque : Ils ne s'appellent pas, tous les deux, inversedBy car l'une joue un rôle plus important … à suivre • Régénérez les entités : symfony console make:entity --regenerate ◦L'entité Salle.php est modifiée ainsi : ..... public function __construct() { $this->ordinateurs = new ArrayCollection(); } public function getOrdinateurs(): Collection { return $this->ordinateurs; } public function addOrdinateur(Ordinateur $ordinateur): self { if (!$this->ordinateurs->contains($ordinateur)) { $this->ordinateurs[] = $ordinateur; $ordinateur->setSalle($this); } return $this; } public function removeOrdinateur(Ordinateur $ordinateur): self { if ($this->ordinateurs->contains($ordinateur)) { $this->ordinateurs->removeElement($ordinateur); if ($ordinateur->getSalle() === $this) { $ordinateur->setSalle(null); } } return $this; } ▪la propriété "ordinateurs" est un tableau de références d'objets donc ses getters/setters sont adaptées à une propriété indexée ◦Mettez à jour la base de données : symfony console make:migration [WARNING] No database changes were detected. • l'association OneToMany n'ajoute rien en base de donnée : • Le coté « owner » des associations bidirectionnelles ! ◦Pas de problème pour associer un ordinateur à une salle avec la méthode setSalle( ) de l’association manyToOne « salle » ◦essayons l’inverse : ▪associer une salle à un ordinateur avec la fonction addOrdinateur( ) de l’association oneToMany « ordinateurs » ▪Voici une nouvelle action test : public function test29() { $em = $this->getDoctrine()->getManager(); $ordi = new Ordinateur; $ordi->setNumero(803); $ordi->setIp('192.168.8.03'); $salle = new Salle ; $salle->setBatiment('D'); $salle->setEtage(8); $salle->setNumero(03); $salle->addOrdinateur($ordi); $em->persist($ordi); $em->flush(); $ordi = $salle = null; $ordi = $this->getDoctrine()->getManager() ->getRepository(Ordinateur::class)->findOneByNumero(803); dump($ordi); return new Response('<html><body></body></html>'); } ▪Exécutons http://localhost:8000/essai/test29 Ça marche ! …... car la fonction addOrdinateur fait appel à setSalle (sa réciproque) ▪Expérience : commentez cette ligne : public function addOrdinateur(Ordinateur $ordinateur): self { if (!$this->ordinateurs->contains($ordinateur)) { $this->ordinateurs[] = $ordinateur; // $ordinateur->setSalle($this); } return $this; } • Essayez l'action test30 public function test30() { $em = $this->getDoctrine()->getManager(); $ordi = new Ordinateur; $ordi->setNumero(804); $ordi->setIp('192.168.8.04'); $em->persist($ordi); $salle = new Salle ; $salle->setBatiment('D'); $salle->setEtage(8); $salle->setNumero(8); $salle->addOrdinateur($ordi); $em->persist($salle); $em->flush(); dump($ordi); return new Response('<html><body></body></html>'); } • Exécutons test30 L'association ne s'est pas faite ! • Fin de l'expérience : Enlevez le commentaire sur la ligne setSalle dans la fonction addOrdinateur ▪Seule l'association coté « owner » est effectuée réellement en base de donnée • Doctrine « n’écoute » (ne détecte) que les modifications du coté « owner » (directeur) ◦Comment décider du coté « owner » d'une association unidirectionnelle ? • le coté propriétaire est désigné par l’attribut mappedBy. Plus précisément la valeur de l’attribut mappedBy désigne le nom qu’a la relation (ici salle) dans l’entité propriétaire (ici Ordinateur). • ManyToOne est obligatoirement le coté « owner » : c’est le coté naturel en SQL qui contient la foreign key • ManyToMany : peu importe ! Pensez à sauvegarder ! → chap4page8.zip • Pas de problème de lecture des données de la base via le coté non « owner » : en clair, par l'appel à la méthode getOrdinateurs() ◦notre code test : public function test32() { $em = $this->getDoctrine()->getManager(); $ordi = new Ordinateur; $ordi->setNumero(805); $ordi->setIp('192.168.8.05'); $em->persist($ordi); $salle = new Salle ; $salle->setBatiment('D'); $salle->setEtage(8); $salle->setNumero(85); $salle->addOrdinateur($ordi); $em->persist($salle); $ordi2 = new Ordinateur; $ordi2->setNumero(806); $ordi2->setIp('192.168.8.06'); $em->persist($ordi2); $salle->addOrdinateur($ordi2); $em->flush(); $id = $salle->getId(); $em->clear(); $salleTrouve = $em->getRepository(Salle::class)->find($id); $result = ""; foreach($salleTrouve ->getOrdinateurs() as $ordi) $result .= $ordi->getIp().' '; $ordinateurs = $salleTrouve->getOrdinateurs(); uploads/Litterature/ chapitre-4 4 .pdf
Documents similaires
-
20
-
0
-
0
Licence et utilisation
Gratuit pour un usage personnel Attribution requise- Détails
- Publié le Dec 22, 2021
- Catégorie Literature / Litté...
- Langue French
- Taille du fichier 0.9108MB