Doctrine2'de çoktan çoğa ilişkilerle çalışmanın en iyi, en temiz ve en basit yolunun ne olduğunu merak ediyorum.
Diyelim ki Metallica'dan Master of Puppets gibi birkaç parçalı bir albümümüz var . Ancak, bir parçanın, Metallica'nın Pil ile olduğu gibi bir albümden daha fazlasında görünebileceğini unutmayın. - üç albüm bu parçayı içeriyor.
Yani ihtiyacım olan, bazı ek sütunları (belirtilen albümdeki parçanın konumu gibi) üçüncü tabloyu kullanarak, albümler ve parçalar arasında çoktan çoğa ilişki. Aslında, Doctrine'in belgelerinde belirtildiği gibi, bu işlevselliği elde etmek için çift bire çok ilişki kullanmak zorundayım.
/** @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;
}
}
Örnek veri:
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 |
+----+----------+----------+----------+------------+
Şimdi bunlarla ilişkili albümlerin ve parçaların bir listesini görüntüleyebilirim:
$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!' : ''
);
}
}
Sonuçlar beklediğim, yani: parçalarını uygun sırayla ve yükseltilmiş olarak tanıtılan olarak işaretlenmiş albümlerin bir listesi.
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)
Yani ne yanlış?
Bu kod neyin yanlış olduğunu gösterir:
foreach ($album->getTracklist() as $track) {
echo $track->getTrack()->getTitle();
}
Album::getTracklist()
AlbumTrackReference
nesneler yerine bir Track
nesne dizisi döndürür . Proxy yöntemleri oluşturamıyorum neden her ikisi de neden olur Album
ve yöntemi Track
olurdu getTitle()
? Album::getTracklist()
Yöntem içinde bazı ekstra işlemler yapabilirdim ama bunu yapmanın en basit yolu nedir? Böyle bir şey yazmaya zorlandım mı?
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
DÜZENLE
@beberlei proxy yöntemlerini kullanmanızı önerdi:
class AlbumTrackReference {
public function getTitle() {
return $this->getTrack()->getTitle()
}
}
Bu iyi bir fikir olurdu ama ben her iki taraftan bu "referans nesnesi" kullanıyorum: $album->getTracklist()[12]->getTitle()
ve $track->getAlbums()[1]->getTitle()
, böylece getTitle()
yöntem çağırma bağlamına göre farklı veriler dönmelidir.
Gibi bir şey yapmak zorunda kalacak:
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();
}
Ve bu çok temiz bir yol değil.
$album->getTracklist()[12]
bir AlbumTrackRef
nesne, yani $album->getTracklist()[12]->getTitle()
(proxy yöntemi kullanıyorsanız) her zaman şarkının başlığını dönecektir. Nesne olsa $track->getAlbums()[1]
da Album
, $track->getAlbums()[1]->getTitle()
her zaman albümün başlığını döndürür.
AlbumTrackReference
iki proxy yöntemi üzerinde kullanmaktır getTrackTitle()
ve getAlbumTitle
.