Doctrine 2 i tabela linków wiele do wielu z dodatkowym polem

88

(Przepraszam za moje niespójne pytanie: próbowałem odpowiedzieć na kilka pytań podczas pisania tego posta, ale oto jest :)

Próbuję utworzyć model bazy danych z relacją wiele-do-wielu wewnątrz tabeli linków, ale która ma również wartość na łącze, w tym przypadku tabela zapasów. (to jest podstawowy przykład na więcej problemów, które mam, ale pomyślałem, że po prostu go przetestuję, zanim będę kontynuował).

Model bazy danych dla podstawowego systemu przechowywania wielu produktów, obejmującego wiele sklepów

Użyłem exportmwb do wygenerowania dwóch Entities Store i Product dla tego prostego przykładu, oba są wyświetlane poniżej.

Jednak teraz problem polega na tym, że nie mogę dowiedzieć się, jak uzyskać dostęp do wartości stock.amount (ze znakiem int, ponieważ może być ujemna) za pomocą Doctrine. Również, gdy próbuję tworzyć tabele za pomocą orm: schema-tool: create function

układ bazy danych, jak widać z HeidiSQL

Dało to tylko dwie jednostki i trzy tabele, jedną jako tabelę łączącą bez wartości i dwie tabele danych, ponieważ relacje wiele do wielu same w sobie nie są jednostkami, więc mogę mieć tylko Produkt i Sklep jako jednostkę.

Logicznie rzecz biorąc, spróbowałem zmienić mój model bazy danych, aby zapasy były oddzielną tabelą z relacjami do przechowywania i produktu. Przepisałem również nazwy pól, aby móc wykluczyć to jako źródło problemu:

zmieniony układ bazy danych

Wtedy odkryłem, że nadal nie otrzymałem jednostki Stock ... a sama baza danych nie miała pola „kwota”.

Naprawdę potrzebowałem być w stanie połączyć te sklepy i produkty razem w tabeli zapasów (między innymi) ... więc samo dodanie zapasów do samego produktu nie wchodzi w grę.

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

A kiedy tworzę bazę danych, nadal nie daje mi ona odpowiednich pól w tabeli giełdowej:

układ bazy danych, jak widać z HeidiSQL

Patrząc więc tutaj na niektóre rzeczy, odkryłem, że połączenia wiele do wielu nie są bytami i dlatego nie mogą mieć wartości. Więc spróbowałem zmienić to na osobną tabelę z relacjami do innych, ale nadal nie działało.

Co ja tu robię źle?

Henry van Megen
źródło
Ok, znalazłem kilka wzmianek stwierdzających, że nie można mieć połączeń wiele do wielu za pomocą Doctrine, z komentarzami zalecającymi zapobieganie tym związkom ... ale co jeśli naprawdę utkniesz w sytuacji takiej jak ta, którą opisałem w moje oryginalne pytanie? Mam całą bazę danych, kompatybilną z Magento, która całkowicie opiera się na relacjach wiele do wielu. Więc w zasadzie mówi się mi "Doctrine ORM nie radzi sobie z wieloma do wielu, nie używaj tego"?
Henry van Megen,
3
Dałbym ci +100, gdybym mógł za wysiłek, jaki włożyłeś, aby wyjaśnić dokładnie, nad czym się zastanawiałem w tak przyjemny sposób :-)
Torsten Römer

Odpowiedzi:

141

Skojarzenie wiele do wielu z dodatkowymi wartościami nie jest skojarzeniem wiele do wielu, ale w rzeczywistości jest nową jednostką, ponieważ ma teraz identyfikator (dwie relacje do połączonych jednostek) i wartości.

To również powód, dlaczego wiele-do-wielu stowarzyszenia są tak rzadkie: masz tendencję do przechowywania w nich dodatkowe właściwości, takie jak sorting, amountitp

To, czego prawdopodobnie potrzebujesz, to coś takiego (zrobiłem obie relacje dwukierunkowe, rozważ zrobienie przynajmniej jednej z nich jednokierunkowej):

Produkt:

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

Sklep:

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

Zbiory:

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
źródło
Ok, dodam trochę getterów i seterów, ponieważ w tej konfiguracji dostaję tylko klucze podstawowe działające bez żadnych wartości :)
Henry van Megen
Kiedy używam tej konfiguracji, a następnie próbuję wykonać zapytanie przy użyciu Stock.store_id, pojawia się błąd „Stock nie ma pola lub skojarzenia o nazwie store_id”. Powinien zostać znaleziony, ponieważ kolumna istnieje w bazie danych.
afilina
@afilina db nie ma znaczenia podczas generowania schematu - DBAL zgłasza wyjątek, ponieważ nie znajduje kolumny w metadanych DDL (w pamięci)
Ocramius
@Ocramius Miałem na myśli to, że baza danych została wygenerowana z metadanych. Jeśli był w stanie wygenerować kolumnę w pierwszej kolejności, powinien być w stanie ją znaleźć podczas zapytania. Rozwiązaniem mojego problemu było porównanie Stock.store z wybranym identyfikatorem.
afilina
W 100% potrzebuję i działa jak urok! Czy wiesz, jak zbudować formularz z zestawem pól, aby edytować kwotę na sklep i produkt?
cwhisperer
17

Doctrine dobrze radzi sobie z relacjami wiele do wielu.

Problem polega na tym, że nie potrzebujesz prostego skojarzenia ManyToMany, ponieważ stowarzyszenia nie mogą mieć „dodatkowych” danych.

Twoja środkowa tabela (stock), ponieważ zawiera więcej niż product_id i store_id, potrzebuje własnej jednostki do modelowania tych dodatkowych danych.

Więc naprawdę potrzebujesz trzech klas jednostek:

  • Produkt
  • StockLevel
  • Sklep

i dwa stowarzyszenia:

  • Produkt oneToMany StockLevel
  • Store oneToMany StockLevel
timdev
źródło
1
Dziękuję za Twoją odpowiedź ! Dodałem dodatkowe pola do mojej "stockowej" tabeli. Jednak doktryna nadal nie bierze pod uwagę tej „tabeli łączenia” i pomija ją, gdy uruchamiam php app/console doctrine:mapping:import AppBundle yml, aby zaimportować schemat z bazy danych. Chciałbym, aby wygenerował ten dodatkowy plik yaml mapowania. Czy ktokolwiek ma jakiś pomysł ? :(
Stphane
Odpowiedź nie rozwiązuje zapisywania danych do jednostki „połączenie”. To jest problem. Brak deklarowania podmiotów. Czy ktoś może wesprzeć przykład, w którym dane są przekazywane z formularza (pole CollectionType ma osadzony formularz z polem EntityType). Nie mogę przekazać danych z osadzonego formularza do głównego formularza i poprawnie zapisać kolekcję pól
zoore