ManyToMany avec Doctrine : côté propriétaire et côté inverse

Une relation ManyToMany avec Doctrine relie deux entités Symfony dans les deux sens, à travers une table de jointure qui stocke une ligne par couple.

// Plat : le côté propriétaire, celui que Doctrine enregistre (table plat_ingredient).
#[ORM\ManyToMany(targetEntity: Ingredient::class, inversedBy: 'plats')]
#[ORM\OrderBy(['nom' => 'ASC'])]
private Collection $ingredients;

// Ingredient : le côté inverse, qui sert à naviguer.
#[ORM\ManyToMany(targetEntity: Plat::class, mappedBy: 'ingredients')]
private Collection $plats;

public function addPlat(Plat $plat): static
{
    if (!$this->plats->contains($plat)) {
        $this->plats->add($plat);
        $plat->addIngredient($this); // sinon, rien n'arrive en base
    }

    return $this;
}

Un plat contient plusieurs ingrédients, un ingrédient entre dans plusieurs plats : aucune des deux tables ne peut porter la clé étrangère, d'où la table de jointure, créée par la migration. Le côté qui déclare inversedBy est propriétaire, celui qui déclare mappedBy est inverse, et #[ORM\OrderBy] trie la collection au chargement. Le piège : Doctrine n'écrit en base que ce que contient le côté propriétaire. Une méthode addPlat() qui ne remplit que la collection inverse donne des objets PHP en apparence à jour, un flush() sans erreur, et une table de jointure vide.

Les exercices font relier les plats de la taverne à leurs ingrédients, triés par ordre alphabétique et affichés sur la fiche de chaque plat, puis retrouver pourquoi, après le rangement du cellier par Pipin, plus un seul ingrédient n'apparaît.

2 exercices font travailler cette notion, en écrivant du code dans le navigateur.

Dans les parcours

Notions voisines

DébogageMigrationsRequêtes N+1Pagination