我想知道在Doctrine2中处理多对多关系的最佳,最简洁和最简单的方法是什么。
因此,我需要的是专辑和曲目之间的多对多关系,使用带有附加列的第三张表(例如曲目在指定专辑中的位置)。实际上,正如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()方法中进行一些额外的处理,但是最简单的方法是什么?我是否被迫写类似的东西?
Album::getTracklist()
AlbumTrackReference
Track
Album
getTitle()
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()方法应基于调用的上下文返回不同的数据。
$album->getTracklist()[12]->getTitle()
$track->getAlbums()[1]->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(); }
那不是一个很干净的方法。
我已经在“ Doctrine用户”邮件列表中打开了一个类似的问题,并且得到了一个非常简单的答案。
将多对多关系视为一个实体本身,然后您意识到您有3个对象,它们之间以一对多和多对一关系链接。
关系一旦有了数据,就不再是关系!