Tabla de enlaces de Doctrine 2 y Many-to-Many con un campo adicional

88

(Perdón por mi pregunta incoherente: intenté responder algunas preguntas mientras escribía esta publicación, pero aquí está :)

Estoy tratando de crear un modelo de base de datos con una relación de muchos a muchos dentro de una tabla de enlaces, pero que también tiene un valor por enlace, en este caso una tabla de mantenimiento de existencias. (este es un ejemplo básico para más problemas que estoy teniendo, pero pensé en probarlo con esto antes de continuar).

Modelo de base de datos para un sistema básico de mantenimiento de almacenamiento de varias tiendas y productos

He usado exportmwb para generar las dos Entities Store y Product para este ejemplo simple, ambos se muestran a continuación.

Sin embargo, el problema ahora es que no puedo averiguar cómo acceder al valor stock.amount (firmado int, ya que puede ser negativo) usando Doctrine. Además, cuando trato de crear las tablas usando el orm de doctrine: schema-tool: create function

el diseño de la base de datos como se ve en HeidiSQL

Esto produjo solo dos Entidades y tres tablas, una como una tabla de enlaces sin valores y dos tablas de datos, ya que las relaciones de muchos a muchos no son entidades en sí mismas, por lo que solo puedo tener Producto y Tienda como una entidad.

Entonces, lógicamente, intenté cambiar el modelo de mi base de datos para tener stock como una tabla separada con relaciones con la tienda y el producto. También reescribí los nombres de campo solo para poder excluir eso como una fuente del problema:

diseño de base de datos cambiado

Entonces lo que encontré fue que todavía no obtuve una entidad de Stock ... y la base de datos en sí no tenía un campo de 'cantidad'.

Realmente necesitaba poder unir estas tiendas y productos en una tabla de existencias (entre otras cosas) ... así que simplemente agregar las existencias en el producto en sí no es una opción.

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

Y cuando creo la base de datos, todavía no me da los campos correctos en la tabla de valores:

el diseño de la base de datos como se ve en HeidiSQL

Entonces, al buscar algunas cosas aquí, descubrí que las conexiones de muchos a muchos no son entidades y, por lo tanto, no pueden tener valores. Así que intenté cambiarlo a una tabla separada con relaciones con los demás, pero aún así no funcionó.

¿Qué estoy haciendo mal aquí?

Henry van Megen
fuente
Ok, encontré un par de menciones que dicen que no es posible tener conexiones de muchos a muchos usando Doctrine, con comentarios que aconsejan prevenir estas relaciones ... pero ¿qué pasa si realmente estás atrapado en una situación como la que describí en mi pregunta original? Tengo una base de datos completa, compatible con Magento, que se basa completamente en relaciones de muchos a muchos. Entonces, básicamente, me dicen "¿Doctrine ORM no puede manejar muchos a muchos, no lo use"?
Henry van Megen
3
Te daría +100 si pudiera por el esfuerzo que has hecho para explicar exactamente lo que me estaba preguntando de una manera tan agradable :-)
Torsten Römer

Respuestas:

141

Una asociación Many-To-Many con valores adicionales no es Many-To-Many, sino que es una entidad nueva, ya que ahora tiene un identificador (las dos relaciones con las entidades conectadas) y valores.

Esa es también la razón por varios a muchas asociaciones son tan raras: se tiende a almacenar las propiedades adicionales en ellas, tales como sorting, amount, etc.

Lo que probablemente necesite es algo como lo siguiente (hice ambas relaciones bidireccionales, considere hacer al menos una de ellas unidireccional):

Producto:

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

Tienda:

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

Valores:

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
fuente
Ok, agregaré algunos getter y setters, porque con esta configuración, solo consigo las claves primarias en funcionamiento sin ningún valor :)
Henry van Megen
Cuando utilizo esta configuración y luego intento realizar una consulta usando Stock.store_id, aparece el error "Stock no tiene ningún campo o asociación llamado store_id". Debería encontrarse porque la columna existe en la base de datos.
afilina
@afilina, la base de datos no importa mientras se genera el esquema: el DBAL lanza la excepción porque no encuentra la columna en los metadatos DDL (en la memoria)
Ocramius
@Ocramius Lo que quise decir es que la base de datos se generó a partir de metadatos. Si pudo generar la columna en primer lugar, entonces debería poder encontrarla durante la consulta. La solución a mi problema fue comparar Stock.store con la identificación deseada.
afilina
¡100% de lo que necesito y funciona como un encanto! ¿Sabes cómo construir un formulario con fieldset para editar la cantidad por tienda y producto?
cwhisperer
17

Doctrine maneja muy bien las relaciones de muchos a muchos.

El problema que tiene es que no necesita una simple asociación ManyToMany, porque las asociaciones no pueden tener datos "extra".

Su tabla intermedia (stock), ya que contiene más que product_id y store_id, necesita su propia entidad para modelar esos datos adicionales.

Entonces realmente quieres tres clases de entidad:

  • Producto
  • Nivel de existencias
  • Tienda

y dos asociaciones:

  • Producto oneToMany StockLevel
  • Almacenar oneToMany StockLevel
timdev
fuente
1
Gracias por su respuesta ! Agregué campos adicionales a mi tabla similar a "acciones". Sin embargo, la doctrina todavía no considera esta "tabla de unión" y la omito cuando ejecuto php app/console doctrine:mapping:import AppBundle ymlpara importar el esquema de la base de datos. Me gustaría generar este archivo yaml de mapeo adicional. Alguien tiene alguna idea ? :(
Stphane
La respuesta no resuelve la escritura de datos en la entidad "conexión". Esto es un problema. No declarar entidades. ¿Puede alguien apoyar el ejemplo donde los datos se pasan desde el formulario (el campo CollectionType tiene un formulario incrustado con el campo EntityType). No puedo pasar datos del formulario incrustado al formulario principal y guardar la colección de campos correctamente
zoore