假设我们有一张专辑像Master of Puppets by 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()
将始终返回专辑的标题。 - Vinícius FagundesAlbumTrackReference
上使用两个代理方法,getTrackTitle()
和getAlbumTitle
。 - Vinícius Fagundes