Relation OneToMany et ManyToOne des entités Doctrine

Logo DoctrineSuite du tutoriel sur la création des relations entre entités pour leur persistance en base de données avec Doctrine.

Pour les précédent tutoriels :

Dans cette partie nous traiterons de OneToMany et sa réciproque ManyToOne

Shchéma merise d'une relation OneToMany ManyToOneUne région possède zéro ou plusieurs départements.
Un département appartient à une et une seul région.

Voici le SQL correspondant :

TABLE `Region` (
  `idRegion` bigint(20) NOT NULL AUTO_INCREMENT,
  `nom` varchar(60) DEFAULT NULL,
  PRIMARY KEY (`idRegion`));

TABLE `Departement` (
  `idDepartement` INT NOT NULL ,
  `nom` VARCHAR(60) NULL ,
  `Region_idRegion` INT NOT NULL ,
  PRIMARY KEY (`idDepartement`, `Region_idRegion`) ,
  INDEX `fk_Departement_Region` (`Region_idRegion` ASC) ,
  CONSTRAINT `fk_Departement_Region`
    FOREIGN KEY (`Region_idRegion` )
    REFERENCES `Region` (`idRegion` ) ) ;

Voici les entités correspondante :

namespace WaldoRelationBundleEntity;

use DoctrineORMMapping as ORM;
use DoctrineCommonCollectionsArrayCollection;

/**
 * @ORMTable(name="Region")
 * @ORMEntity
 */
class Region
{
    /**
     * @var decimal $identifiantRegion
     *
     * @ORMColumn(name="idRegion", type="bigint", nullable=false)
     * @ORMId
     * @ORMGeneratedValue(strategy="IDENTITY")
     */
    private $identifiantRegion;

    /**
     * @var text $nom
     *
     * @ORMColumn(name="nom", type="string", length=60, nullable=true)
     */
    private $nom;

     /**
     * @var ArrayCollection $departements
     *
     * @ORMOneToMany(targetEntity="Departement", mappedBy="region", cascade={"persist", "remove", "merge"})
     */
    private $departements;

    /**
     * @return decimal $identifiantRegion
     */
    public function getIdentifiantRegion()
    {
        return $this->identifiantRegion;
    }

    /**
     * @param text $nom
     */
    public function setNom($nom)
    {
        $this->nom = $nom;
    }

    /**
     * @return text $nom
     */
    public function getNom()
    {
        return $this->nom;
    }

    /**
     * @param BDepartement $departements
     */
    public function addDepartement(Departement $departement) {
        $departement->setRegion($this);

        // Si l'objet fait déjà partie de la collection on ne l'ajoute pas
        if (!$this->departements->contains($departement)) {
            $this->departements->add($departement);
        }
    }

    /**
     * @return ArrayCollection $departements
     */
    public function getDepartements() {
        return $this->departements;
    }

    public function __construct() {
        $this->departements = new ArrayCollection();
    }
}
namespace WaldoRelationBundleEntity;

use DoctrineORMMapping as ORM;

/**
 * @ORMTable(name="Departement")
 * @ORMEntity
 */
class Departement
{
    /**
     * @var decimal $identifiantDepartement
     *
     * @ORMColumn(name="idDepartement", type="bigint", nullable=false)
     * @ORMId
     * @ORMGeneratedValue(strategy="IDENTITY")
     */
    private $identifiantDepartement;

    /**
     * @var text $nom
     *
     * @ORMColumn(name="nom", type="string", length=60, nullable=true)
     */
    private $nom;

    /**
     * @var Region $region
     *
     * @ORMManyToOne(targetEntity="Region", inversedBy="departements", cascade={"persist", "merge"})
     * @ORMJoinColumns({
     *  @ORMJoinColumn(name="Region_idRegion", referencedColumnName="idRegion")
     * })
     */
    private $region;

    /**
     * @return decimal $identifiantDepartement
     */
    public function getIdentifiantDepartement()
    {
        return $this->identifiantDepartement;
    }

    /**
     * @param text $nom
     */
    public function setNom($nom)
    {
        $this->nom = $nom;
    }

    /**
     * @return text $nom
     */
    public function getNom()
    {
        return $this->nom;
    }

    /**
     * @param BRegion $region
     */
    public function setRegion(Region $region)
    {
        $this->region = $region;
    }

    /**
     * @return BRegion $region
     */
    public function getRegion()
    {
        return $this->region;
    }
}

Avec ce code on pourra faire ce genre de chose :

$coteDOr = new Departement();
$coteDOr->setNom("Côte d'Or");

$ain = new Departement();
$ain->setNom("Ain");

$region = new Region();
$region->setNom("Bourgogne");

$region->addDepartement($coteDOr);
$region->addDepartement($ain);
//ou, mais dans ce cas il faut faire attention à l'ordre de persistance
$coteDOr->setRegion($region);
$ain->setRegion($region);

Explication :

/**
* Extrait de la classe Region
* @var Collection $departements
*
* @ORMOneToMany(targetEntity="Departement", mappedBy="region", cascade={"persist", "remove", "merge"})
*/
private $departements;

L’annotation OneToMany définie que la propriété $departements peut contenir un ou plusieurs objets, $departements est de type ArrayCollection.
L’attribut targetEntity détermine de quelle classe dépendent les objets que $departements peut contenir.
L’attribut mappedBy détermine le nom de la propriété présente dans la classe visée (Departement) qui sert de lien.
cascade={"persist", "remove", "merge"}) : permet de définir le comportement lors de la persistance de l’objet Client.

/**
* Extrait de la classe Departement
* @var Region $region
*
* @ORMManyToOne(targetEntity="Region", cascade={"persist", "remove", "merge"})
* @ORMJoinColumns({
*  @ORMJoinColumn(name="Region_idRegion", referencedColumnName="idRegion")
* })
*/
private $region;

L’annotation ManyToOne définie que la propriété $region est liée uniquement à un objet de type Region.
L’attribut targetEntity définit la classe visée.
cascade={"persist", "remove", "merge"}) : permet de définir le comportement lors de la persistance de l’objet Client.
L’annotation JoinColumns va permettre de définir quels champs de la table sont utilisés pour lier les deux entités.
L’annotation JoinColumn permet de déterminer quels champs de la base de données permettent la relation.

L’attribut name correspond au champ de la table Departement qui permet la relation.
L’attribut referencedColumnName est le nom du champ de la table visée qui permet la relation.

Attention les valeur de name et referencedColumnName sont les noms des champs qui vont apparaître dans le SQL, alors que l’attribut targetEntity ou mappedBy prend le nom de l’entité (la classe) et non le nom de la table.

Publié dans Doctrine, PHP, Symfony2
14 commentaires pour “Relation OneToMany et ManyToOne des entités Doctrine
  1. sebastien dit :

    J’adore votre article :)

  2. PH dit :

    Merci pour les explications ça m’aide un petit peu.
    Mais comment faire pour définir directement id_Region pour Departement ?
    Par exemple :

    $dep = new Departement();
    $dep->setName('Charente Maritime');
    $dep->setRegion('6');
    $em->persist($dep);
    

    En fait j’ai ce problème avec les Status pour des Articles avec une relation OneToMany pour les Status. Actuellement je suis obligé de récupérer en bdd pour set la foreign key. Exemple :

    $statusRep = $this->getDoctrine()->getEntityManager()->getRepository('MonBundle:Status');
    // Je récupère le statut 1 qui correspond à un état
    $published = $statusRep->find('1'); 
    $article->setStatus($published);
    

    Sauf que j’ai besoin de le définir directement … Bref, je vais continuer à chercher

  3. Christophe dit :

    +1 par rapport au commentaire précédent.

    J’ai le même problème que PH, sauf que je ne sais pas du tout comment le résoudre (débutant inside).

    pourquoi l’ID de la région ne s’enregistre pas dans le département ?

    Merci pour toute aide.

    • waldo2188 dit :

      Le problème que tu soulève, je le comprend bien, je suis aussi par là.
      @PH et toi pensé de manière SQL, et non objet.
      Avec Doctrine vous gérez des Objets et non plus de simple lignes dans une BDD.
      Ce code est donc non correcte avec l’utilisation de Doctrine:

      $dep = new Departement();
      $dep->setName(‘Charente Maritime’);
      $dep->setRegion(’6′);
      $em->persist($dep);
      

      Je te conseil plutôt de faire de cette manière :

      $dep = new Departement();
      $dep->setName(‘Charente Maritime’);
      $dep->setRegion($em->getRepository('WaldoRelationBundle:Region')->findOneById(6));
      $em->persist($dep);
      
  4. scamp dit :

    Bonjour à vous, j’ai besoin de votre aide.
    J’ai suivi votre tuto qui est tout simplement super.

    Seulement j’ai un problème et je bloque depuis un moment dessus.
    En fait, lors de l’ajout en BDD, doctrine me recrée une nouvelle entrée Région et le departement pointe maintenant sur l’id de cette nouvelle entrée.

    J’aurais voulu qu’il pointe sur l’id region sans créer de doublons….

    HELP please save my day :)

  5. tanguy dit :

    salut j’ai suivi ton tutoriel, et je fais usage d’un formulaire imbriguer pour inserer les departement et meme temps que les regions. tout fonctionne bien sauf au niveau de
    idRegion qui reste toujours a une valeur par defaut NULL.

    pourrais tu me donner un coup de main

    merci d’avance

    • Waldo dit :

      Humm,
      difficile de répondre comme cela. Cela dépend de tellement de chose. Si tu as du code sur github ou autre, je veux bien y jeter un oeuil.

  6. Saidi Nizar dit :

    avec quelle version de symfony2 ?

  7. Saidi Nizar dit :

    Bonjour
    avec quelle version de symfony2 ?
    Merci

    • Waldo dit :

      J’utilise ce système depuis la version 2.3.
      Avec la version 2.7 de Symfony, cela fonctionne toujours.

      • Saidi Nizar dit :

        tu peux me donner la requête pour donner la liste des des département par région
        ( select * FROM Departement where Region_idRegion = $id ) ?

        Merci .

        • Waldo dit :

          Quelque chose dans le genre :

          $qb = $this->createQueryBuilder("Departement");
          $qb->leftJoin("Departement.region", "Region")
             ->where(
                 $qb->expr()->eq("Region.nom", ":nomRegion")
             )->setParameter("nomRegion", "Alsace")
          
  8. Djilou dit :

    Bonjour j’ai lu votre article il est très claire mais je n’est pas pu résoudre mon problème, est ce que c’est possible d’utiliser OneToOne d’un coté (entité A) et OneToMany d’un autre coté (entité B)

    -entité B: contient un tableau d’entités A amis
    /**
    * @var ArrayCollection $amis
    *
    * @ORMOneToMany(targetEntity= »A », mappedBy= »B », cascade={« persist », « remove », « merge »})
    */
    private $amis;

    -entité A doit être en relation avec l’entité b pour récupérer la liste des amis
    /**
    * @ORM\OneToOne(targetEntity= »B »,mappedBy= »A », cascade={« persist », »remove »})
    *
    * @ORM\JoinColumn(nullable=true)
    */
    private $amis;

    Merci

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

*