Doctrine 2 et table de liens plusieurs-à-plusieurs avec un champ supplémentaire

88

(Désolé pour ma question incohérente: j'ai essayé de répondre à quelques questions pendant que j'écrivais ce post, mais la voici :)

J'essaie de créer un modèle de base de données avec une relation plusieurs-à-plusieurs dans une table de liens, mais qui a également une valeur par lien, dans ce cas une table de gestion des stocks. (ceci est un exemple de base pour plus de problèmes que j'ai, mais j'ai pensé que je le testerais juste avant de continuer).

Modèle de base de données pour un système de stockage de base multi-magasins et multi-produits

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

Cependant, le problème maintenant est que je ne peux pas comprendre comment accéder à la valeur stock.amount (signé int, car il peut être négatif) en utilisant Doctrine. Aussi, quand j'essaye de créer les tables en utilisant la doctrine orm: schema-tool: create function

la disposition de la base de données telle qu'elle est vue depuis HeidiSQL

Cela n'a donné que deux entités et trois tables, une en tant que table de liens sans valeurs et deux tables de données, car les relations plusieurs-à-plusieurs ne sont pas des entités elles-mêmes, donc je ne peux avoir que Product et Store en tant qu'entité.

Donc, logiquement, j'ai essayé de changer mon modèle de base de données pour avoir le stock sous forme de table séparée avec des relations entre le magasin et le produit. J'ai également réécrit les noms de champ juste pour pouvoir exclure cela comme source du problème:

modification de la disposition de la base de données

Ensuite, j'ai découvert 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 produits ensemble dans une table de stock (entre autres) ... donc ajouter simplement 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 lorsque je crée la base de données, cela ne me donne toujours pas les bons champs dans la table de stock:

la disposition de la base de données telle qu'elle est vue depuis HeidiSQL

Donc, en recherchant certaines choses ici, j'ai découvert que les connexions plusieurs à plusieurs ne sont pas des entités et ne peuvent donc pas avoir de valeurs. J'ai donc essayé de le changer en une table séparée avec des relations avec les autres, mais cela ne fonctionnait toujours pas.

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

Henry van Megen
la source
Ok, j'ai trouvé quelques mentions indiquant qu'il n'est pas possible d'avoir des connexions plusieurs-à-plusieurs en utilisant Doctrine, avec des commentaires conseillant d'éviter ces relations ... mais que faire si vous êtes vraiment coincé dans une situation comme celle que j'ai décrite dans ma question initiale? J'ai une base de données entière, compatible avec Magento, qui repose entièrement sur des relations plusieurs-à-plusieurs. Donc, en gros, on me dit "Doctrine ORM ne peut pas gérer plusieurs à plusieurs, ne l'utilisez pas" ??
Henry van Megen
3
Je vous donnerais +100 si je pouvais pour l'effort que vous avez fait pour expliquer exactement ce que je me demandais d'une si belle manière :-)
Torsten Römer

Réponses:

141

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

C'est aussi la raison pour laquelle plusieurs à De nombreuses associations sont si rares: vous avez tendance à stocker les propriétés supplémentaires dans les, comme sorting, amount, etc.

Ce dont vous avez probablement besoin, c'est quelque chose comme suivant (j'ai rendu les deux relations bidirectionnelles, envisagez de rendre au moins l'une d'entre elles unidirectionnelles):

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;
}

Boutique:

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;
}
Ocramius
la source
Ok, je vais ajouter quelques getter et setters, car avec cette configuration, je ne fais que les clés primaires opérationnelles sans aucune valeur :)
Henry van Megen
Lorsque j'utilise cette configuration, puis que j'essaie d'interroger à l'aide de Stock.store_id, j'obtiens l'erreur "Stock n'a pas de champ ou d'association nommé store_id". Il doit être trouvé car la colonne existe dans la base de données.
afilina
@afilina la base de données n'a pas d'importance lors de la génération du schéma - le DBAL lève l'exception car il ne trouve pas la colonne dans les métadonnées DDL (en mémoire)
Ocramius
@Ocramius Ce que je voulais dire, c'est que la base de données a été générée à partir de métadonnées. SI il a pu générer la colonne en premier lieu, il devrait pouvoir la trouver pendant la requête. La solution à mon problème était de comparer Stock.store à l'identifiant souhaité.
afilina
100% de ce dont j'ai besoin et ça marche comme un charme! Savez-vous comment créer un formulaire avec fieldset afin de modifier le montant par magasin et par produit?
cwhisperer
17

La doctrine gère très bien les relations 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 avoir de données "supplémentaires".

Votre table du milieu (stock), puisqu'elle contient plus que product_id et 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
  • Boutique

et deux associations:

  • Produit unToMany StockLevel
  • Stocker oneToMany StockLevel
Timdev
la source
1
Merci pour votre réponse ! J'ai ajouté des champs supplémentaires à mon "stock" comme table. Cependant, la doctrine ne considère toujours pas cette "table de jointure" et l'ignore lorsque je cours php app/console doctrine:mapping:import AppBundle ymlpour importer le schéma de la base de données. Je voudrais qu'il génère ce fichier yaml de mappage supplémentaire. Est-ce que quelqu'un a une idée ? :(
Stphane
Réponse ne résolvant pas l'écriture de données dans l'entité "connexion" C'est un problème. Ne pas déclarer d'entités. Pouvez-vous s'il vous plaît quelqu'un prendre en charge l'exemple où les données sont transmises à partir du formulaire (le champ CollectionType a un formulaire incorporé avec le champ EntityType) Je ne peux pas transmettre les données du formulaire intégré au formulaire principal et enregistrer correctement la collection de champs
zoore