Doctrine2: Der beste Weg, um mit vielen zu vielen mit zusätzlichen Spalten in der Referenztabelle umzugehen


282

Ich frage mich, was die beste, sauberste und einfachste Art ist, mit vielen-zu-vielen-Beziehungen in Doctrine2 zu arbeiten.

Nehmen wir an, wir haben ein Album wie Master of Puppets von Metallica mit mehreren Tracks. Bitte beachten Sie jedoch, dass ein Titel möglicherweise in mehr als einem Album enthalten ist, wie dies bei Battery by Metallica der Fall ist - drei Alben enthalten diesen Titel.

Was ich also brauche, ist eine Viele-zu-Viele-Beziehung zwischen Alben und Titeln, wobei eine dritte Tabelle mit einigen zusätzlichen Spalten verwendet wird (z. B. die Position des Titels im angegebenen Album). Tatsächlich muss ich, wie aus der Dokumentation von Doctrine hervorgeht, eine doppelte Eins-zu-Viele-Beziehung verwenden, um diese Funktionalität zu erreichen.

/** @Entity() */
class Album {
    /** @Id @Column(type="integer") */
    protected $id;

    /** @Column() */
    protected $title;

    /** @OneToMany(targetEntity="AlbumTrackReference", mappedBy="album") */
    protected $tracklist;

    public function __construct() {
        $this->tracklist = new \Doctrine\Common\Collections\ArrayCollection();
    }

    public function getTitle() {
        return $this->title;
    }

    public function getTracklist() {
        return $this->tracklist->toArray();
    }
}

/** @Entity() */
class Track {
    /** @Id @Column(type="integer") */
    protected $id;

    /** @Column() */
    protected $title;

    /** @Column(type="time") */
    protected $duration;

    /** @OneToMany(targetEntity="AlbumTrackReference", mappedBy="track") */
    protected $albumsFeaturingThisTrack; // btw: any idea how to name this relation? :)

    public function getTitle() {
        return $this->title;
    }

    public function getDuration() {
        return $this->duration;
    }
}

/** @Entity() */
class AlbumTrackReference {
    /** @Id @Column(type="integer") */
    protected $id;

    /** @ManyToOne(targetEntity="Album", inversedBy="tracklist") */
    protected $album;

    /** @ManyToOne(targetEntity="Track", inversedBy="albumsFeaturingThisTrack") */
    protected $track;

    /** @Column(type="integer") */
    protected $position;

    /** @Column(type="boolean") */
    protected $isPromoted;

    public function getPosition() {
        return $this->position;
    }

    public function isPromoted() {
        return $this->isPromoted;
    }

    public function getAlbum() {
        return $this->album;
    }

    public function getTrack() {
        return $this->track;
    }
}

Beispieldaten:

             Album
+----+--------------------------+
| id | title                    |
+----+--------------------------+
|  1 | Master of Puppets        |
|  2 | The Metallica Collection |
+----+--------------------------+

               Track
+----+----------------------+----------+
| id | title                | duration |
+----+----------------------+----------+
|  1 | Battery              | 00:05:13 |
|  2 | Nothing Else Matters | 00:06:29 |
|  3 | Damage Inc.          | 00:05:33 |
+----+----------------------+----------+

              AlbumTrackReference
+----+----------+----------+----------+------------+
| id | album_id | track_id | position | isPromoted |
+----+----------+----------+----------+------------+
|  1 |        1 |        2 |        2 |          1 |
|  2 |        1 |        3 |        1 |          0 |
|  3 |        1 |        1 |        3 |          0 |
|  4 |        2 |        2 |        1 |          0 |
+----+----------+----------+----------+------------+

Jetzt kann ich eine Liste der damit verbundenen Alben und Titel anzeigen:

$dql = '
    SELECT   a, tl, t
    FROM     Entity\Album a
    JOIN     a.tracklist tl
    JOIN     tl.track t
    ORDER BY tl.position ASC
';

$albums = $em->createQuery($dql)->getResult();

foreach ($albums as $album) {
    echo $album->getTitle() . PHP_EOL;

    foreach ($album->getTracklist() as $track) {
        echo sprintf("\t#%d - %-20s (%s) %s\n", 
            $track->getPosition(),
            $track->getTrack()->getTitle(),
            $track->getTrack()->getDuration()->format('H:i:s'),
            $track->isPromoted() ? ' - PROMOTED!' : ''
        );
    }   
}

Die Ergebnisse sind das, was ich erwarte, dh: eine Liste von Alben mit ihren Titeln in der richtigen Reihenfolge und beworbenen, die als beworben markiert sind.

The Metallica Collection
    #1 - Nothing Else Matters (00:06:29) 
Master of Puppets
    #1 - Damage Inc.          (00:05:33) 
    #2 - Nothing Else Matters (00:06:29)  - PROMOTED!
    #3 - Battery              (00:05:13) 

Also, was ist falsch?

Dieser Code zeigt, was falsch ist:

foreach ($album->getTracklist() as $track) {
    echo $track->getTrack()->getTitle();
}

Album::getTracklist()Gibt ein Array von AlbumTrackReferenceObjekten anstelle von TrackObjekten zurück. Ich kann keine Proxy-Methoden erstellen, was wäre, wenn beide, Albumund Trackhätte getTitle()Methode? Ich könnte eine zusätzliche Verarbeitung innerhalb der Album::getTracklist()Methode durchführen, aber wie geht das am einfachsten? Bin ich gezwungen, so etwas zu schreiben?

public function getTracklist() {
    $tracklist = array();

    foreach ($this->tracklist as $key => $trackReference) {
        $tracklist[$key] = $trackReference->getTrack();

        $tracklist[$key]->setPosition($trackReference->getPosition());
        $tracklist[$key]->setPromoted($trackReference->isPromoted());
    }

    return $tracklist;
}

// And some extra getters/setters in Track class

BEARBEITEN

@beberlei schlug vor, Proxy-Methoden zu verwenden:

class AlbumTrackReference {
    public function getTitle() {
        return $this->getTrack()->getTitle()
    }
}

Das wäre eine gute Idee, aber ich verwende dieses "Referenzobjekt" von beiden Seiten: $album->getTracklist()[12]->getTitle()und $track->getAlbums()[1]->getTitle(), daher getTitle()sollte die Methode je nach Aufrufkontext unterschiedliche Daten zurückgeben.

Ich müsste so etwas tun wie:

 getTracklist() {
     foreach ($this->tracklist as $trackRef) { $trackRef->setContext($this); }
 }

 // ....

 getAlbums() {
     foreach ($this->tracklist as $trackRef) { $trackRef->setContext($this); }
 }

 // ...

 AlbumTrackRef::getTitle() {
      return $this->{$this->context}->getTitle();
 }

Und das ist kein sehr sauberer Weg.


2
Wie gehst du mit der AlbumTrackReference um? Zum Beispiel $ album-> addTrack () oder $ album-> removeTrack ()?
Daniel

Ich habe nicht verstanden, dass Sie über den Kontext kommentieren. Meiner Meinung nach hängen die Daten nicht vom Kontext ab. About $album->getTracklist()[12]ist ein AlbumTrackRefObjekt, gibt also $album->getTracklist()[12]->getTitle()immer den Titel des Tracks zurück (wenn Sie die Proxy-Methode verwenden). Während $track->getAlbums()[1]es sich um ein AlbumObjekt handelt, $track->getAlbums()[1]->getTitle()wird immer der Titel des Albums zurückgegeben.
Vinícius Fagundes

Eine andere Idee ist die Verwendung von AlbumTrackReferencezwei Proxy-Methoden getTrackTitle()und getAlbumTitle.
Vinícius Fagundes

Antworten:


158

Ich habe eine ähnliche Frage in der Doctrine-Benutzer-Mailingliste geöffnet und eine wirklich einfache Antwort erhalten.

Betrachten Sie die Viele-zu-Viele-Beziehung als eine Einheit selbst, und dann stellen Sie fest, dass Sie drei Objekte haben, die zwischen ihnen mit einer Eins-zu-Viele- und einer Viele-zu-Eins-Beziehung verbunden sind.

http://groups.google.com/group/doctrine-user/browse_thread/thread/d1d87c96052e76f7/436b896e83c10868#436b896e83c10868

Sobald eine Beziehung Daten enthält, ist sie keine Beziehung mehr!


Weiß jemand, wie ich das Doctrine-Befehlszeilentool dazu bringen kann, diese neue Entität als yml-Schemadatei zu generieren? Dieser Befehl: app/console doctrine:mapping:import AppBundle ymlGenerieren Sie immer noch manyToMany-Beziehungen für die ursprünglichen zwei Tabellen und ignorieren Sie einfach die dritte Tabelle, anstatt sie als Entität zu betrachten:/
Stphane

Was ist der Unterschied zwischen foreach ($album->getTracklist() as $track) { echo $track->getTrack()->getTitle(); }@Crozin und consider the relationship as an entity? Ich denke, was er fragen möchte, ist, wie man die relationale Entität überspringt und den Titel eines Titels foreach ($album->getTracklist() as $track) { echo $track->getTitle(); }
abruft,

6
"Sobald eine Beziehung Daten enthält, ist sie keine Beziehung mehr." Das war wirklich aufschlussreich. Ich konnte einfach nicht über eine Beziehung aus der Sicht einer Entität nachdenken!
Zwiebel

Was ist, wenn die Beziehung bereits erstellt und als viele zu viele verwendet wurde? Wir haben erkannt, dass wir zusätzliche Felder in unseren vielen zu vielen benötigen, also haben wir eine andere Entität geschaffen. Das Problem ist, dass vorhandene Daten und eine vorhandene Tabelle mit demselben Namen keine Freunde sein möchten. Hat das schon mal jemand versucht?
Tylerismus

Für diejenigen, die sich fragen: Erstellen einer Entität mit der (bereits vorhandenen) Viele-zu-Viele-Verknüpfung, während ihre Tabelle funktioniert. Die Entitäten, die die Viele-zu-Viele enthalten, müssen jedoch an Eins-zu-Viele an die neue Entität angepasst werden. Auch Schnittstellen nach außen (Getter / Setter für die früheren Viele-zu-Viele) müssen höchstwahrscheinlich angepasst werden.
Jakumi

17

Von $ album-> getTrackList () erhalten Sie immer "AlbumTrackReference" -Entitäten zurück. Wie wäre es also mit dem Hinzufügen von Methoden aus dem Track und dem Proxy?

class AlbumTrackReference
{
    public function getTitle()
    {
        return $this->getTrack()->getTitle();
    }

    public function getDuration()
    {
        return $this->getTrack()->getDuration();
    }
}

Auf diese Weise wird Ihre Schleife erheblich vereinfacht, ebenso wie alle anderen Codes, die sich auf das Schleifen der Titel eines Albums beziehen, da alle Methoden nur in AlbumTrakcReference als Proxy verwendet werden:

foreach ($album->getTracklist() as $track) {
    echo sprintf("\t#%d - %-20s (%s) %s\n", 
        $track->getPosition(),
        $track->getTitle(),
        $track->getDuration()->format('H:i:s'),
        $track->isPromoted() ? ' - PROMOTED!' : ''
    );
}

Übrigens sollten Sie die AlbumTrackReference umbenennen (zum Beispiel "AlbumTrack"). Es ist eindeutig nicht nur eine Referenz, sondern enthält zusätzliche Logik. Da es wahrscheinlich auch Tracks gibt, die nicht mit einem Album verbunden sind, sondern nur über eine Promo-CD oder ähnliches erhältlich sind, ermöglicht dies auch eine sauberere Trennung.


1
Proxy-Methoden lösen das Problem nicht zu 100% (überprüfen Sie meine Bearbeitung). Btw You should rename the AlbumT(...)- guter Punkt
Crozin

3
Warum hast du nicht zwei Methoden? getAlbumTitle () und getTrackTitle () für das AlbumTrackReference-Objekt? Beide Stellvertreter für ihre jeweiligen Unterobjekte.
Beberlei

Das Ziel ist die natürlichste Objekt-API. $album->getTracklist()[1]->getTrackTitle()ist so gut / schlecht wie $album->getTracklist()[1]->getTrack()->getTitle(). Es scheint jedoch, dass ich zwei verschiedene Klassen haben müsste: eine für Album-> Titelreferenzen und eine für Titel-> Albumreferenzen - und das ist zu schwer zu implementieren. Also wahrscheinlich ist das die beste Lösung bisher ...
Crozin

13

Nichts geht über ein schönes Beispiel

Wenn Sie nach einem sauberen Codierungsbeispiel für eine Eins-zu-Viele / Viele-zu-Eins-Zuordnung zwischen den drei teilnehmenden Klassen suchen, um zusätzliche Attribute in der Beziehung zu speichern, lesen Sie diese Site:

schönes Beispiel für Eins-zu-Viele / Viele-zu-Eins-Assoziationen zwischen den 3 teilnehmenden Klassen

Denken Sie an Ihre Primärschlüssel

Denken Sie auch an Ihren Primärschlüssel. Für solche Beziehungen können Sie häufig zusammengesetzte Schlüssel verwenden. Die Lehre unterstützt dies von Haus aus. Sie können Ihre referenzierten Entitäten in IDs umwandeln. Überprüfen Sie hier die Dokumentation zu zusammengesetzten Schlüsseln


10

Ich denke, ich würde dem Vorschlag von @ beberlei folgen, Proxy-Methoden zu verwenden. Um diesen Prozess zu vereinfachen, können Sie zwei Schnittstellen definieren:

interface AlbumInterface {
    public function getAlbumTitle();
    public function getTracklist();
}

interface TrackInterface {
    public function getTrackTitle();
    public function getTrackDuration();
}

Dann können sowohl Sie Albumals auch Sie Tracksie implementieren, während die AlbumTrackReferencebeiden weiterhin wie folgt implementieren können:

class Album implements AlbumInterface {
    // implementation
}

class Track implements TrackInterface {
    // implementation
}

/** @Entity whatever */
class AlbumTrackReference implements AlbumInterface, TrackInterface
{
    public function getTrackTitle()
    {
        return $this->track->getTrackTitle();
    }

    public function getTrackDuration()
    {
        return $this->track->getTrackDuration();
    }

    public function getAlbumTitle()
    {
        return $this->album->getAlbumTitle();
    }

    public function getTrackList()
    {
        return $this->album->getTrackList();
    }
}

Auf diese Weise können Sie Ihre Logik, die direkt auf ein Trackoder ein verweist, entfernen Albumund sie einfach so ersetzen, dass sie ein TrackInterfaceoder verwendet AlbumInterface, AlbumTrackReferencein jedem Fall verwenden. Was Sie brauchen, ist, die Methoden zwischen den Schnittstellen ein wenig zu unterscheiden.

Dies wird weder die DQL- noch die Repository-Logik unterscheiden, aber Ihre Dienste ignorieren einfach die Tatsache, dass Sie ein Albumoder ein AlbumTrackReferenceoder ein Trackoder ein übergeben, AlbumTrackReferenceweil Sie alles hinter einer Schnittstelle versteckt haben :)

Hoffe das hilft!


7

Erstens stimme ich beberlei in seinen Vorschlägen größtenteils zu. Möglicherweise entwerfen Sie sich jedoch selbst in eine Falle. Ihre Domain scheint den Titel als natürlichen Schlüssel für einen Track zu betrachten, was wahrscheinlich bei 99% der Szenarien der Fall ist, auf die Sie stoßen. Was aber, wenn Batterie auf Meister der Puppen ist eine andere Version (unterschiedliche Länge, Live, akustisch, mischen sie wieder, remastered, usw.) als die Version auf der Metallica - Sammlung .

Je nachdem, wie Sie diesen Fall behandeln (oder ignorieren) möchten, können Sie entweder die von beberlei vorgeschlagene Route wählen oder einfach Ihre vorgeschlagene zusätzliche Logik in Album :: getTracklist () verwenden. Persönlich denke ich, dass die zusätzliche Logik gerechtfertigt ist, um Ihre API sauber zu halten, aber beide haben ihre Vorzüge.

Wenn Sie meinen Anwendungsfall berücksichtigen möchten, können Tracks ein selbstreferenzierendes OneToMany für andere Tracks enthalten, möglicherweise $ SimilarTracks. In diesem Fall gäbe es zwei Einheiten für die Track- Batterie , eine für die Metallica-Sammlung und eine für Master of the Puppets . Dann würde jede ähnliche Track-Entität einen Verweis aufeinander enthalten. Außerdem würde dies die aktuelle AlbumTrackReference-Klasse beseitigen und Ihr aktuelles "Problem" beseitigen. Ich stimme zu, dass es nur die Komplexität an einen anderen Punkt verschiebt, aber es ist in der Lage, einen Anwendungsfall zu handhaben, den es vorher nicht konnte.


6

Sie fragen nach dem "besten Weg", aber es gibt keinen besten Weg. Es gibt viele Möglichkeiten und einige davon haben Sie bereits entdeckt. Wie Sie das Assoziationsmanagement verwalten und / oder kapseln möchten, wenn Sie Assoziationsklassen verwenden, liegt ganz bei Ihnen und Ihrer konkreten Domäne. Ich fürchte, niemand kann Ihnen einen "besten Weg" zeigen.

Abgesehen davon könnte die Frage erheblich vereinfacht werden, indem Doktrin- und relationale Datenbanken aus der Gleichung entfernt werden. Das Wesentliche Ihrer Frage ist die Frage, wie Sie mit Assoziationsklassen in einfachem OOP umgehen sollen.


6

Ich hatte einen Konflikt mit der Join-Tabelle, die in einer Annotation einer Assoziationsklasse (mit zusätzlichen benutzerdefinierten Feldern) definiert ist, und einer Join-Tabelle, die in einer Annotation von vielen zu vielen definiert ist.

Die Zuordnungsdefinitionen in zwei Entitäten mit einer direkten Viele-zu-Viele-Beziehung schienen zur automatischen Erstellung der Verknüpfungstabelle unter Verwendung der Annotation 'joinTable' zu führen. Die Verknüpfungstabelle wurde jedoch bereits durch eine Anmerkung in der zugrunde liegenden Entitätsklasse definiert, und ich wollte, dass sie die eigenen Felddefinitionen dieser Zuordnungsentitätsklasse verwendet, um die Verknüpfungstabelle um zusätzliche benutzerdefinierte Felder zu erweitern.

Die Erklärung und Lösung ist die von FMaz008 oben angegebene. In meiner Situation war es diesem Beitrag im Forum ' Doctrine Annotation Question ' zu verdanken . Dieser Beitrag macht auf die Doctrine-Dokumentation zu ManyToMany Unidirektionalen Beziehungen aufmerksam . Lesen Sie den Hinweis zum Ansatz der Verwendung einer 'Assoziationsentitätsklasse', bei der die Zuordnung von vielen zu vielen Annotationen direkt zwischen zwei Hauptentitätsklassen durch eine Eins-zu-viele-Annotation in den Hauptentitätsklassen und zwei 'Viele-zu-Viele "ersetzt wird -one 'Annotationen in der assoziativen Entitätsklasse. In diesem Forumbeitrag finden Sie ein Beispiel für Assoziationsmodelle mit zusätzlichen Feldern :

public class Person {

  /** @OneToMany(targetEntity="AssignedItems", mappedBy="person") */
  private $assignedItems;

}

public class Items {

    /** @OneToMany(targetEntity="AssignedItems", mappedBy="item") */
    private $assignedPeople;
}

public class AssignedItems {

    /** @ManyToOne(targetEntity="Person")
    * @JoinColumn(name="person_id", referencedColumnName="id")
    */
private $person;

    /** @ManyToOne(targetEntity="Item")
    * @JoinColumn(name="item_id", referencedColumnName="id")
    */
private $item;

}

3

Dieses wirklich nützliche Beispiel. Es fehlt in der Dokumentationslehre 2.

Vielen Dank.

Für die Proxys können folgende Funktionen ausgeführt werden:

class AlbumTrack extends AlbumTrackAbstract {
   ... proxy method.
   function getTitle() {} 
}

class TrackAlbum extends AlbumTrackAbstract {
   ... proxy method.
   function getTitle() {}
}

class AlbumTrackAbstract {
   private $id;
   ....
}

und

/** @OneToMany(targetEntity="TrackAlbum", mappedBy="album") */
protected $tracklist;

/** @OneToMany(targetEntity="AlbumTrack", mappedBy="track") */
protected $albumsFeaturingThisTrack;

3

Sie beziehen sich auf Metadaten, Daten über Daten. Ich hatte das gleiche Problem für das Projekt, an dem ich gerade arbeite, und musste einige Zeit damit verbringen, es herauszufinden. Es sind zu viele Informationen, um sie hier zu veröffentlichen, aber unten finden Sie zwei Links, die Sie möglicherweise nützlich finden. Sie verweisen auf das Symfony-Framework, basieren jedoch auf dem Doctrine ORM.

http://melikedev.com/2010/04/06/symfony-saving-metadata-during-form-save-sort-ids/

http://melikedev.com/2009/12/09/symfony-w-doctrine-saving-many-to-many-mm-relationships/

Viel Glück und schöne Metallica-Referenzen!


3

Die Lösung liegt in der Dokumentation von Doctrine. In den FAQ können Sie Folgendes sehen:

http://docs.doctrine-project.org/en/2.1/reference/faq.html#how-can-i-add-columns-to-a-many-to-many-table

Und das Tutorial ist hier:

http://docs.doctrine-project.org/en/2.1/tutorials/composite-primary-keys.html

Sie tun also nichts mehr, manyToManysondern müssen eine zusätzliche Entität erstellen und diese manyToOneIhren beiden Entitäten zuweisen.

ADD für @ f00bar Kommentar:

Es ist einfach, Sie müssen nur so etwas tun:

Article  1--N  ArticleTag  N--1  Tag

Sie erstellen also eine Entität ArticleTag

ArticleTag:
  type: entity
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  manyToOne:
    article:
      targetEntity: Article
      inversedBy: articleTags
  fields: 
    # your extra fields here
  manyToOne:
    tag:
      targetEntity: Tag
      inversedBy: articleTags

Ich hoffe, es hilft



Genau das habe ich gesucht, danke! Leider gibt es kein yml-Beispiel für den dritten Anwendungsfall! :(Könnte jemand ein Beispiel des dritten Anwendungsfalls im yml-Format teilen? Ich würde wirklich appriace:#
Stphane

Ich habe der Antwort Ihren Fall hinzugefügt;)
Mirza Selimovic

Es ist falsch. Die Entität muss nicht mit id (id) AUTO sein. Das ist falsch, ich versuche das richtige Beispiel zu erstellen
Gatunox

Ich werde eine neue Antwort posten, um zu erhalten, wenn sie richtig formatiert ist
Gatunox

3

Unidirektional. Fügen Sie einfach inversedBy: (Name der Fremdspalte) hinzu, um es bidirektional zu machen.

# config/yaml/ProductStore.dcm.yml
ProductStore:
  type: entity
  id:
    product:
      associationKey: true
    store:
      associationKey: true
  fields:
    status:
      type: integer(1)
    createdAt:
      type: datetime
    updatedAt:
      type: datetime
  manyToOne:
    product:
      targetEntity: Product
      joinColumn:
        name: product_id
        referencedColumnName: id
    store:
      targetEntity: Store
      joinColumn:
        name: store_id
        referencedColumnName: id

Ich hoffe, es hilft. Wir sehen uns.


2

Möglicherweise können Sie mit Class Table Inheritance das erreichen, was Sie wollen, indem Sie AlbumTrackReference in AlbumTrack ändern:

class AlbumTrack extends Track { /* ... */ }

Und getTrackList()würde AlbumTrackObjekte enthalten , die Sie dann verwenden können, wie Sie möchten:

foreach($album->getTrackList() as $albumTrack)
{
    echo sprintf("\t#%d - %-20s (%s) %s\n", 
        $albumTrack->getPosition(),
        $albumTrack->getTitle(),
        $albumTrack->getDuration()->format('H:i:s'),
        $albumTrack->isPromoted() ? ' - PROMOTED!' : ''
    );
}

Sie müssen dies gründlich prüfen, um sicherzustellen, dass Sie nicht unter der Leistung leiden.

Ihre aktuelle Einrichtung ist einfach, effizient und leicht zu verstehen, auch wenn einige der Semantiken nicht ganz richtig zu Ihnen passen.


0

Während alle Albumtitel innerhalb der Albumklasse erstellt werden, generieren Sie eine weitere Abfrage für einen weiteren Datensatz. Das liegt an der Proxy-Methode. Es gibt ein weiteres Beispiel für meinen Code (siehe letzten Beitrag im Thema): http://groups.google.com/group/doctrine-user/browse_thread/thread/d1d87c96052e76f7/436b896e83c10868#436b896e83c10868

Gibt es eine andere Methode, um das zu beheben? Ist ein einzelner Join nicht eine bessere Lösung?


1
Während dies theoretisch die Frage beantworten kann, wäre es vorzuziehen , die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen.
Spontifixus

0

Hier ist die Lösung, wie in der Doctrine2-Dokumentation beschrieben

<?php
use Doctrine\Common\Collections\ArrayCollection;

/** @Entity */
class Order
{
    /** @Id @Column(type="integer") @GeneratedValue */
    private $id;

    /** @ManyToOne(targetEntity="Customer") */
    private $customer;
    /** @OneToMany(targetEntity="OrderItem", mappedBy="order") */
    private $items;

    /** @Column(type="boolean") */
    private $payed = false;
    /** @Column(type="boolean") */
    private $shipped = false;
    /** @Column(type="datetime") */
    private $created;

    public function __construct(Customer $customer)
    {
        $this->customer = $customer;
        $this->items = new ArrayCollection();
        $this->created = new \DateTime("now");
    }
}

/** @Entity */
class Product
{
    /** @Id @Column(type="integer") @GeneratedValue */
    private $id;

    /** @Column(type="string") */
    private $name;

    /** @Column(type="decimal") */
    private $currentPrice;

    public function getCurrentPrice()
    {
        return $this->currentPrice;
    }
}

/** @Entity */
class OrderItem
{
    /** @Id @ManyToOne(targetEntity="Order") */
    private $order;

    /** @Id @ManyToOne(targetEntity="Product") */
    private $product;

    /** @Column(type="integer") */
    private $amount = 1;

    /** @Column(type="decimal") */
    private $offeredPrice;

    public function __construct(Order $order, Product $product, $amount = 1)
    {
        $this->order = $order;
        $this->product = $product;
        $this->offeredPrice = $product->getCurrentPrice();
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.