• Aucun résultat trouvé

Relation Many-To-Many avec attributs Présentation

besoins d'une application.

Pour illustrer ce manque, rien de tel qu'un exemple : considérons l'entité Produit d'un site e-commerce ainsi que l'entité Commande. Une commande contient plusieurs produits, et bien entendu un même produit peut être dans différentes

commandes. On a donc bien une relation Many-To-Many. Voyez-vous le manque ? Lorsqu'un utilisateur ajoute un produit à une commande, où met-on la quantité de ce produit qu'il veut ? Si je veux 3 exemplaires de Harry Potter, où mettre cette quantité ? Dans l'entité Commande ? Non cela n'a pas de sens. Dans l'entité Produit ? Non, cela n'a pas de sens non plus. Cette quantité est un attribut de la relation qui existe entre Produit et Commande, et non un attribut de Produit ou de Commande. Il n'y a pas de moyen simple de gérer les attributs d'une relation avec Doctrine. Pour cela, il faut esquiver en créant simplement une entité intermédiaire qui va représenter la relation, appelons-la CommandeProduit. Et c'est dans cette entité que l'on mettra les attributs de relation, comme notre quantité. Ensuite, il faut bien entendu mettre en relation cette entité intermédiaire avec les deux autres entités d'origine, Commande et Produit. Pour cela, il faut logiquement faire : Commande One-To-Many CommandeProduit Many-To-One Produit. En effet, une commande (One) peut avoir plusieurs relations avec des produits (Many), plusieurs CommandeProduit, donc ! La relation est symétrique pour les produits.

Attention, dans le titre de cette section, j'ai parlé de la relation Many-To-Many avec attributs, mais il s'agit bien en fait de deux relations Many-To-One des plus normales, soyons d'accord. On ne va donc rien apprendre dans ce prochain paragraphe, car on sait déjà faire une Many-To-One, mais c'est une astuce qu'il faut bien connaître et savoir utiliser, donc prenons le temps de bien la comprendre.

J'ai pris l'exemple de produits et de commandes, car c'est plus intuitif pour comprendre l'enjeu et l'utilité de cette relation. Cependant, pour rester dans le cadre de notre blog, on va faire une relation entre des Article et des Compétence, et l'attribut de la relation sera le niveau. L'idée est de pouvoir afficher sur chaque article la liste des compétences utilisées

(Symfony2, Doctrine2, Formulaire, etc.) avec le niveau dans chaque compétence (Débutant, Avisé et Expert). On a alors l'analogie suivante :

Article <=> Commande

ArticleCompetence <=> Commande_Produit Competence <=> Produit

Et donc : Article One-To-Many ArticleCompetence Many-To-One Competence. Pour cela, créez d'abord cette entité Compétence, avec au moins un attribut nom. Voici la mienne :

Code : PHP

<?php

// src/Sdz/BlogBundle/Entity/Competence.php

namespace Sdz\BlogBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/** * Sdz\BlogBundle\Entity\Competence * * @ORM\Table() * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\CompetenceRepository") */ class Competence { /**

* @var integer $id * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /**

* @var string $nom *

*/ private $nom; /** * Get id * * @return integer */

public function getId() { return $this->id; } /** * Set nom *

* @param string $nom * @return Competence */

public function setNom($nom) {

$this->nom = $nom; } /** * Get nom * * @return string */

public function getNom() {

return $this->nom; }

}

Définition de la relation dans les entités

Annotation

Tout d'abord, on va créer notre entité de relation (notre ArticleCompetence) comme ceci : Code : PHP

<?php

// src/Sdz/BlogBundle/Entity/ArticleCompetence.php

namespace Sdz\BlogBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/** * @ORM\Entity */ class ArticleCompetence { /** * @ORM\Id * @ORM\ManyToOne(targetEntity="Sdz\BlogBundle\Entity\Article") */ private $article; /** * @ORM\Id

* @ORM\ManyToOne(targetEntity="Sdz\BlogBundle\Entity\Competence") */ private $competence; /** * @ORM\Column() */

private $niveau; // Ici j'ai un attribut de relation « niveau »

// … vous pouvez ajouter d'autres attributs bien entendu

}

Comme les côtés Many des deux relations Many-To-One sont dans ArticleCompetence, cette entité est l'entité propriétaire des deux relations.

Et ces @ORM\Id ? Pourquoi y en a-t-il deux et qu'est-ce qu'ils viennent faire ici ?

Très bonne question. Comme toute entité, notre ArticleCompetence se doit d'avoir un identifiant. C'est obligatoire pour que Doctrine puisse la gérer par la suite. Depuis le début, nous avons ajouté un attribut id qui était en auto-incrément, on ne s'en occupait pas trop donc. Ici c'est différent, comme une ArticleCompetence correspond à un unique couple

Article/Competence (pour chaque couple Article/Competence, il n'y a qu'une seule ArticleCompetence), on peut se servir de ces deux attributs pour former l'identifiant de cette entité.

Pour cela, il suffit de définir @ORM\Id sur les deux colonnes, et Doctrine saura mettre une clé primaire sur ces deux colonnes, puis les gérer comme n'importe quel autre identifiant. Encore une fois, merci Doctrine !

Mais, avec une relation unidirectionnelle, on ne pourra pas faire $article->getArticleCompetence() pour récupérer les ArticleCompetence et donc les compétences ? Ni l'inverse depuis $competence ?

En effet, et c'est pourquoi la prochaine section de ce chapitre traite des relations bidirectionnelles ! En attendant, pour notre relation One-To-Many-To-One, continuons simplement sur une relation unidirectionnelle.

Sachez quand même que vous pouvez éviter une relation bidirectionnelle ici en utilisant simplement la méthode findByCommande($commande->getId()) (pour récupérer les produits d'une commande) ou

findByProduit($produit->getId()) (pour l'inverse) du repository CommandeProduitRepository.

L'intérêt de la bidirectionnelle ici est lorsque vous voulez afficher une liste des commandes avec leurs produits. Dans la boucle sur les commandes, vous n'allez pas faire appel à une méthode du repository qui va générer une requête par boucle, il faudra passer par un $commande->getCommandeProduits(). Nous le verrons plus loin dans ce chapitre.

N'oubliez pas de mettre à jour votre base de données en exécutant la commande doctrine:schema:update .