ฉันสงสัยว่าอะไรดีที่สุดสะอาดและเป็นวิธีที่ง่ายที่สุดในการทำงานกับความสัมพันธ์แบบหลายต่อหลายคนใน Doctrine2
สมมติว่าเรามีอัลบั้มอย่างMaster of Puppetsโดย Metallica ที่มีหลายแทร็ก แต่โปรดทราบความจริงที่ว่าหนึ่งแทร็กอาจปรากฏในมากกว่าหนึ่งอัลบั้มเช่นBattery by Metallicaทำ - อัลบั้มที่สามมีเนื้อเรื่องนี้
ดังนั้นสิ่งที่ฉันต้องการคือความสัมพันธ์แบบหลายต่อหลายกลุ่มระหว่างอัลบั้มและแทร็กโดยใช้ตารางที่สามกับคอลัมน์เพิ่มเติมบางอย่าง (เช่นตำแหน่งของแทร็กในอัลบั้มที่ระบุ) ที่จริงฉันต้องใช้ตามเอกสารของ Doctrine แนะนำความสัมพันธ์แบบหนึ่งต่อหลายสองครั้งเพื่อให้บรรลุการทำงานนั้น
/** @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;
    }
}ข้อมูลตัวอย่าง:
             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 |
+----+----------+----------+----------+------------+ตอนนี้ฉันสามารถแสดงรายการอัลบั้มและเพลงที่เกี่ยวข้อง:
$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!' : ''
        );
    }   
}ผลลัพธ์คือสิ่งที่ฉันคาดหวังเช่น: รายการอัลบั้มที่มีแทร็กของพวกเขาตามลำดับที่เหมาะสม
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) แล้วมีอะไรผิดปกติ?
รหัสนี้แสดงให้เห็นถึงสิ่งที่ผิด:
foreach ($album->getTracklist() as $track) {
    echo $track->getTrack()->getTitle();
}Album::getTracklist()ส่งคืนอาร์เรย์ของAlbumTrackReferenceวัตถุแทนTrackวัตถุ ฉันไม่สามารถสร้างเมธอดพร็อกซีทำให้เกิดอะไรขึ้นถ้าทั้งคู่AlbumและTrackจะมีgetTitle()เมธอดหรือไม่ ฉันสามารถทำการประมวลผลเพิ่มเติมภายในAlbum::getTracklist()วิธีการได้ แต่วิธีที่ง่ายที่สุดในการทำเช่นนั้นคืออะไร ฉันถูกบังคับอย่าเขียนอะไรอย่างนั้นหรือ
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แก้ไข
@beberlei แนะนำให้ใช้วิธีพร็อกซี:
class AlbumTrackReference {
    public function getTitle() {
        return $this->getTrack()->getTitle()
    }
}นั่นเป็นความคิดที่ดี แต่ฉันใช้ "การอ้างอิงวัตถุ" จากทั้งสองด้าน: $album->getTracklist()[12]->getTitle()และ$track->getAlbums()[1]->getTitle()ดังนั้นgetTitle()วิธีการควรกลับข้อมูลที่แตกต่างกันตามบริบทของการภาวนา
ฉันจะต้องทำสิ่งที่ชอบ:
 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();
 }และนั่นไม่ใช่วิธีที่สะอาดมาก
$album->getTracklist()[12]คือ   AlbumTrackRefวัตถุดังนั้น$album->getTracklist()[12]->getTitle()จะส่งคืนชื่อของแทร็กเสมอ (หากคุณใช้วิธีพร็อกซี) ในขณะที่$track->getAlbums()[1]เป็นAlbumวัตถุดังนั้น$track->getAlbums()[1]->getTitle()จะกลับชื่อของอัลบั้มเสมอ
                AlbumTrackReferenceสองวิธีพร็อกซี่และgetTrackTitle() getAlbumTitle