Doctrine 2 e tabela de links muitos para muitos com um campo extra

88

(Desculpem minha pergunta incoerente: tentei responder algumas perguntas enquanto escrevia este post, mas aqui está :)

Estou tentando criar um modelo de banco de dados com um relacionamento muitos para muitos dentro de uma tabela de links, mas que também tem um valor por link, neste caso uma tabela de manutenção de estoque. (este é um exemplo básico para mais problemas que estou tendo, mas pensei em testá-lo antes de continuar).

Modelo de banco de dados para um sistema básico de armazenamento de vários produtos e várias lojas

Usei exportmwb para gerar as duas Entidades Store e Product para este exemplo simples, ambas são exibidas abaixo.

No entanto, o problema agora é que não consigo descobrir como acessar o valor stock.amount (int assinado, pois pode ser negativo) usando o Doctrine. Além disso, quando tento criar as tabelas usando orm: schema-tool: create function da doutrina

o layout do banco de dados como é visto no HeidiSQL

Isso resultou em apenas duas Entidades e três tabelas, uma como uma tabela de link sem valores e duas tabelas de dados, já que relacionamentos muitos para muitos não são entidades em si, então só posso ter Produto e Loja como uma entidade.

Então, logicamente, tentei mudar meu modelo de banco de dados para ter estoque como uma tabela separada com relacionamentos para armazenamento e produto. Também reescrevi os nomes dos campos apenas para poder excluí-los da origem do problema:

layout de banco de dados alterado

Então o que eu descobri foi que ainda não recebi uma entidade Stock ... e o próprio banco de dados não tinha um campo 'quantidade'.

Eu realmente precisava ser capaz de juntar essas lojas e produtos em uma tabela de estoque (entre outras coisas) ... então, apenas adicionar o estoque ao produto em si não é uma opção.

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

E quando eu crio o banco de dados, ele ainda não me fornece os campos certos na tabela de estoque:

o layout do banco de dados como é visto no HeidiSQL

Então, procurando algumas coisas aqui, descobri que conexões muitos para muitos não são entidades e, portanto, não podem ter valores. Tentei mudá-la para uma tabela separada com relacionamentos com as outras, mas ainda não funcionou.

O que eu estou fazendo errado aqui?

Henry van Megen
fonte
Ok, encontrei algumas menções afirmando que não é possível ter conexões muitos-para-muitos usando o Doctrine, com comentários aconselhando para evitar esses relacionamentos .. mas e se você realmente estiver preso a uma situação como a que descrevi minha pergunta original? Eu tenho um banco de dados completo, compatível com Magento, que depende completamente de relacionamentos muitos para muitos. Então, basicamente, estou me dizendo "O Doctrine ORM não pode lidar com muitos para muitos, não use" ??
Henry van Megen
3
Eu lhe daria +100 se eu pudesse pelo esforço que você fez para explicar exatamente o que eu estava pensando de uma maneira tão boa :-)
Torsten Römer

Respostas:

143

Uma associação Muitos-para-muitos com valores adicionais não é um Muitos-para-muitos, mas sim uma nova entidade, pois agora tem um identificador (as duas relações com as entidades conectadas) e valores.

Essa também é a razão pela qual as associações Muitos para Muitos são tão raras: você tende a armazenar propriedades adicionais nelas, como sorting,amount , etc.

O que você provavelmente precisa é algo como seguir (eu tornei ambas as relações bidirecionais, considere tornar pelo menos uma delas unidirecional):

Produtos:

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

Loja:

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

Estoque:

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
fonte
Ok, vou adicionar alguns getter e setters, porque com essa configuração, só coloco as chaves primárias em funcionamento sem nenhum valor :)
Henry van Megen
Quando eu uso esta configuração e tento consultar usando Stock.store_id, obtenho o erro "Stock não tem campo ou associação chamada store_id". Deve ser encontrado porque a coluna existe no banco de dados.
afilina
@afilina o db não importa ao gerar o esquema - o DBAL lança a exceção porque não encontra a coluna nos metadados DDL (na memória)
Ocramius
@Ocramius O que eu quis dizer é que o banco de dados foi gerado a partir de metadados. Se ele conseguiu gerar a coluna em primeiro lugar, deve ser capaz de encontrá-la durante a consulta. A solução para meu problema foi comparar Stock.store com o id desejado.
afilina
100% do que preciso e funciona perfeitamente! Você sabe montar um formulário com fieldset para editar o valor por loja e produto?
cwhisperer
17

O Doctrine lida muito bem com relacionamentos muitos para muitos.

O problema que você está tendo é que você não precisa de uma associação ManyToMany simples, porque as associações não podem ter dados "extras".

Sua tabela intermediária (estoque), uma vez que contém mais do que product_id e store_id, precisa de sua própria entidade para modelar esses dados extras.

Então você realmente quer três classes de entidade:

  • produtos
  • Nível de estoque
  • Loja

e duas associações:

  • Product oneToMany StockLevel
  • Store oneToMany StockLevel
timdev
fonte
1
Obrigado pela sua resposta ! Eu adicionei campos extras à minha tabela de "ações". Porém, a doutrina ainda não considera essa "tabela de junção" e a pulo quando eu executo php app/console doctrine:mapping:import AppBundle ymlpara importar o esquema do banco de dados. Eu gostaria que ele gerasse esse arquivo yaml de mapeamento extra. Alguém tem alguma ideia? :(
Stphane
A resposta não resolve a gravação de dados na entidade de "conexão". Isto é um problema. Não declarando entidades. Alguém pode dar suporte a um exemplo onde os dados são passados ​​do Form (o campo CollectionType tem um formulário incorporado com o campo EntityType). Não consigo passar dados do formulário incorporado para o formulário principal e salvar a coleção de campos corretamente
zoore