85 votes

Doctrine 2 et table de liaison Many-to-many avec un champ supplémentaire

(Désolé pour ma question incohérente : J'ai essayé de répondre à certaines questions au fur et à mesure que j'écrivais ce billet, mais voilà :)

J'essaie de créer un modèle de base de données avec une relation many-to-many dans une table de liens, mais qui a également une valeur par lien, dans ce cas une table de stockage. (Il s'agit d'un exemple de base pour d'autres problèmes que j'ai, mais j'ai pensé que je devais juste le tester avec ceci avant de continuer).

Database model for a basic multi-store, multi-product store-keeping system

J'ai utilisé exportmwb pour générer les deux entités Store et Product pour cet exemple simple, les deux sont affichés ci-dessous.

Cependant, le problème actuel est que je n'arrive pas à trouver comment accéder à la valeur stock.amount (int signé, car il peut être négatif) en utilisant Doctrine. De plus, lorsque j'essaie de créer les tables en utilisant la fonction orm:schema-tool:create de doctrine

the database layout as it is seen from HeidiSQL

Cela n'a donné que deux entités et trois tables, dont une table de liens sans valeurs et deux tables de données, étant donné que les relations plusieurs à plusieurs ne sont pas des entités elles-mêmes et que je ne peux avoir que le produit et le magasin comme entité.

En toute logique, j'ai donc essayé de modifier mon modèle de base de données pour que le stock soit une table distincte avec des relations avec le magasin et le produit. J'ai également réécrit les noms des champs pour pouvoir exclure cette source de problème :

changed database layout

Ensuite, j'ai constaté que je n'avais toujours pas d'entité Stock... et que la base de données elle-même n'avait pas de champ "montant".

J'avais vraiment besoin de pouvoir lier ces magasins et ces produits dans une table de stock (entre autres choses)... donc ajouter le stock sur le produit lui-même n'est pas une option.

root@hdev:/var/www/test/library# php doctrine.php orm:info
Found 2 mapped entities:
[OK]   Entity\Product
[OK]   Entity\Store

Et quand je crée la base de données, je n'obtiens toujours pas les bons champs dans la table des stocks :

the database layout as it is seen from HeidiSQL

En cherchant un peu ici, j'ai découvert que les connexions many-to-many ne sont pas des entités et ne peuvent donc pas avoir de valeurs. J'ai donc essayé de la changer en une table séparée avec des relations avec les autres, mais cela ne fonctionne toujours pas.

Qu'est-ce que je fais de mal ici ?

138voto

Ocramius Points 10275

Une association Many-To-Many avec des valeurs supplémentaires n'est pas un Many-To-Many, mais bien une nouvelle entité, puisqu'elle possède maintenant un identifiant (les deux relations avec les entités connectées) et des valeurs.

C'est également la raison pour laquelle les associations Many-To-Many sont si rares : vous avez tendance à y stocker des propriétés supplémentaires, telles que sorting , amount etc.

Ce dont vous avez probablement besoin est quelque chose comme ce qui suit (j'ai rendu les deux relations bidirectionnelles, pensez à rendre au moins l'une d'entre elles unidirectionnelle) :

Produit :

namespace Entity;

use Doctrine\ORM\Mapping as ORM;

/** @ORM\Table(name="product") @ORM\Entity() */
class Product
{
    /** @ORM\Id() @ORM\Column(type="integer") */
    protected $id;

    /** ORM\Column(name="product_name", type="string", length=50, nullable=false) */
    protected $name;

    /** @ORM\OneToMany(targetEntity="Entity\Stock", mappedBy="product") */
    protected $stockProducts;
}

Magasin :

namespace Entity;

use Doctrine\ORM\Mapping as ORM;

/** @ORM\Table(name="store") @ORM\Entity() */
class Store
{
    /** @ORM\Id() @ORM\Column(type="integer") */
    protected $id;

    /** ORM\Column(name="store_name", type="string", length=50, nullable=false) */
    protected $name;

    /** @ORM\OneToMany(targetEntity="Entity\Stock", mappedBy="store") */
    protected $stockProducts;
}

Stock :

namespace Entity;

use Doctrine\ORM\Mapping as ORM;

/** @ORM\Table(name="stock") @ORM\Entity() */
class Stock
{
    /** ORM\Column(type="integer") */
    protected $amount;

    /** 
     * @ORM\Id()
     * @ORM\ManyToOne(targetEntity="Entity\Store", inversedBy="stockProducts") 
     * @ORM\JoinColumn(name="store_id", referencedColumnName="id", nullable=false) 
     */
    protected $store;

    /** 
     * @ORM\Id()
     * @ORM\ManyToOne(targetEntity="Entity\Product", inversedBy="stockProducts") 
     * @ORM\JoinColumn(name="product_id", referencedColumnName="id", nullable=false) 
     */
    protected $product;
}

17voto

timdev Points 25910

Doctrine gère très bien les relations de plusieurs à plusieurs.

Le problème que vous rencontrez est que vous n'avez pas besoin d'une simple association ManyToMany, car les associations ne peuvent pas contenir de données "supplémentaires".

La table du milieu (stock), puisqu'elle contient plus que le product_id et le store_id, a besoin de sa propre entité pour modéliser ces données supplémentaires.

Donc vous voulez vraiment trois classes d'entités :

  • Produit
  • StockLevel
  • Magasin

et deux associations :

  • Produit oneToMany StockLevel
  • Store oneToMany StockLevel

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X