Saya bertanya-tanya apa yang terbaik, paling bersih dan cara paling sederhana untuk bekerja dengan banyak-ke-banyak hubungan di Doctrine2.
Mari kita asumsikan bahwa kita punya album seperti Master of Puppets oleh Metallica dengan beberapa lagu. Tapi tolong perhatikan fakta bahwa satu lagu mungkin muncul di lebih dari satu album, seperti yang dilakukan Battery oleh Metallica - tiga album menampilkan lagu ini.
Jadi yang saya butuhkan adalah hubungan banyak-ke-banyak antara album dan trek, menggunakan tabel ketiga dengan beberapa kolom tambahan (seperti posisi trek di album tertentu). Sebenarnya saya harus menggunakan, seperti yang ditunjukkan oleh doktrin Doktrin, hubungan ganda satu-ke-banyak untuk mencapai fungsi itu.
/** @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;
}
}
Contoh data:
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 |
+----+----------+----------+----------+------------+
Sekarang saya dapat menampilkan daftar album dan trek yang terkait dengannya:
$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!' : ''
);
}
}
Hasilnya adalah apa yang saya harapkan, yaitu: daftar album dengan trek mereka dalam urutan yang sesuai dan yang dipromosikan ditandai sebagai dipromosikan.
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)
Jadi apa yang salah?
Kode ini menunjukkan apa yang salah:
foreach ($album->getTracklist() as $track) {
echo $track->getTrack()->getTitle();
}
Album::getTracklist()
mengembalikan array AlbumTrackReference
objek, bukan Track
objek. Saya tidak dapat membuat metode proksi karena bagaimana jika keduanya, Album
dan Track
akan memiliki getTitle()
metode? Saya bisa melakukan beberapa pemrosesan tambahan dalam Album::getTracklist()
metode tetapi apa cara paling sederhana untuk melakukan itu? Apakah saya dipaksa menulis sesuatu seperti itu?
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
EDIT
@beberlei menyarankan untuk menggunakan metode proxy:
class AlbumTrackReference {
public function getTitle() {
return $this->getTrack()->getTitle()
}
}
Itu akan menjadi ide yang bagus tapi saya menggunakan "objek referensi" dari kedua sisi: $album->getTracklist()[12]->getTitle()
dan $track->getAlbums()[1]->getTitle()
, jadi getTitle()
metode harus mengembalikan data yang berbeda berdasarkan konteks doa.
Saya harus melakukan sesuatu seperti:
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();
}
Dan itu bukan cara yang sangat bersih.
$album->getTracklist()[12]
isAlbumTrackRef
object, jadi$album->getTracklist()[12]->getTitle()
akan selalu mengembalikan judul trek (jika Anda menggunakan metode proxy). Sementara$track->getAlbums()[1]
iniAlbum
objek, sehingga$track->getAlbums()[1]->getTitle()
akan kembali selalu judul album.AlbumTrackReference
dua metode proxy,getTrackTitle()
dangetAlbumTitle
.Jawaban:
Saya telah membuka pertanyaan serupa di milis pengguna Doctrine dan mendapatkan jawaban yang sangat sederhana;
pertimbangkan relasi many to many sebagai entitas itu sendiri, dan kemudian Anda sadar bahwa Anda memiliki 3 objek, yang dihubungkan di antara mereka dengan relasi satu ke banyak dan banyak-ke-satu.
http://groups.google.com/group/doctrine-user/browse_thread/thread/d1d87c96052e76f7/436b896e83c10868#436b896e83c10868
Setelah relasi memiliki data, itu bukan lagi relasi!
sumber
app/console doctrine:mapping:import AppBundle yml
masih menghasilkan manyToMany relasi untuk dua tabel asli dan mengabaikan tabel ketiga alih-alih menganggapnya sebagai entitas:/
foreach ($album->getTracklist() as $track) { echo $track->getTrack()->getTitle(); }
disediakan oleh @Crozin danconsider the relationship as an entity
? Saya pikir apa yang ingin dia tanyakan adalah bagaimana cara melompati entitas relasional dan mengambil judul trek dengan menggunakanforeach ($album->getTracklist() as $track) { echo $track->getTitle(); }
Dari $ album-> getTrackList () Anda akan mendapatkan entitas "AlbumTrackReference" kembali, jadi bagaimana dengan menambahkan metode dari Track dan proxy?
Dengan cara ini, perulangan Anda sangat disederhanakan, juga semua kode lain yang terkait dengan perulangan trek sebuah album, karena semua metode hanya diproksi di dalam AlbumTrakcReference:
Btw Anda harus mengganti nama AlbumTrackReference (misalnya "AlbumTrack"). Jelas bukan hanya referensi, tetapi mengandung logika tambahan. Karena mungkin ada juga Tracks yang tidak terhubung ke album tetapi hanya tersedia melalui promo-cd atau sesuatu ini memungkinkan pemisahan yang lebih bersih juga.
sumber
Btw You should rename the AlbumT(...)
- Poin bagus$album->getTracklist()[1]->getTrackTitle()
sama baiknya / buruknya$album->getTracklist()[1]->getTrack()->getTitle()
. Namun sepertinya saya harus memiliki dua kelas yang berbeda: satu untuk referensi album-> trek dan satu lagi untuk referensi track-> album - dan itu terlalu sulit untuk diterapkan. Jadi mungkin itu solusi terbaik sejauh ini ...Tidak ada yang mengalahkan contoh yang bagus
Untuk orang yang mencari contoh pengkodean yang bersih dari asosiasi satu-ke-banyak / banyak-ke-satu antara 3 kelas yang berpartisipasi untuk menyimpan atribut tambahan dalam relasi, periksa situs ini:
contoh yang bagus dari asosiasi satu-ke-banyak / banyak-ke-satu antara 3 kelas yang berpartisipasi
Pikirkan tentang kunci utama Anda
Juga pikirkan kunci utama Anda. Anda sering dapat menggunakan kunci komposit untuk hubungan seperti ini. Ajaran asli mendukung ini. Anda dapat membuat entitas yang dirujuk menjadi id. Periksa dokumentasi pada kunci komposit di sini
sumber
Saya pikir saya akan pergi dengan saran @ beberlei untuk menggunakan metode proxy. Apa yang dapat Anda lakukan untuk membuat proses ini lebih sederhana adalah dengan mendefinisikan dua antarmuka:
Kemudian, Anda
Album
dan AndaTrack
dapat menerapkannya, sementaraAlbumTrackReference
masih dapat menerapkan keduanya, sebagai berikut:Dengan cara ini, dengan menghapus logika Anda yang secara langsung mereferensikan a
Track
atau aAlbum
, dan hanya menggantinya sehingga menggunakan aTrackInterface
atauAlbumInterface
, Anda bisa menggunakan AndaAlbumTrackReference
dalam keadaan apa pun. Yang Anda perlukan adalah membedakan metode antar antarmuka sedikit.Ini tidak akan membedakan DQL atau logika Repositori, tetapi layanan Anda hanya akan mengabaikan fakta bahwa Anda melewati suatu
Album
atauAlbumTrackReference
, atau aTrack
atauAlbumTrackReference
karena Anda telah menyembunyikan segala sesuatu di balik antarmuka :)Semoga ini membantu!
sumber
Pertama, saya sebagian besar setuju dengan beberlei tentang sarannya. Namun, Anda mungkin mendesain diri sendiri menjadi jebakan. Domain Anda tampaknya mempertimbangkan judul sebagai kunci alami untuk sebuah trek, yang kemungkinan besar merupakan kasus 99% dari skenario yang Anda temui. Namun, bagaimana jika Battery pada Master of the Puppets adalah versi yang berbeda (panjang berbeda, live, akustik, remix, remaster, dll) daripada versi di The Metallica Collection .
Bergantung pada bagaimana Anda ingin menangani (atau mengabaikan) kasing itu, Anda bisa pergi dengan rute yang disarankan beberlei, atau hanya pergi dengan logika tambahan yang Anda usulkan di Album :: getTracklist (). Secara pribadi, saya pikir logika ekstra dibenarkan untuk menjaga API Anda bersih, tetapi keduanya memiliki kelebihan.
Jika Anda ingin mengakomodasi kasus penggunaan saya, Anda dapat memiliki Lagu yang berisi referensi sendiri OneToMany ke Lagu lain, mungkin $ similarTracks. Dalam hal ini, akan ada dua entitas untuk trek Battery , satu untuk The Metallica Collection dan satu untuk Master of the Puppets . Kemudian setiap entitas Track yang serupa akan berisi referensi satu sama lain. Juga, itu akan menghilangkan kelas AlbumTrackReference saat ini dan menghilangkan "masalah" Anda saat ini. Saya setuju bahwa itu hanya memindahkan kompleksitas ke titik yang berbeda, tetapi ia mampu menangani usecase yang sebelumnya tidak mampu.
sumber
Anda meminta "cara terbaik" tetapi tidak ada cara terbaik. Ada banyak cara dan Anda sudah menemukan beberapa di antaranya. Bagaimana Anda ingin mengelola dan / atau merangkum manajemen asosiasi ketika menggunakan kelas asosiasi sepenuhnya terserah Anda dan domain konkret Anda, tidak ada yang bisa menunjukkan "cara terbaik" kepada saya.
Terlepas dari itu, pertanyaannya dapat disederhanakan banyak dengan menghapus Doktrin dan basis data relasional dari persamaan. Inti dari pertanyaan Anda adalah pertanyaan tentang bagaimana menangani kelas asosiasi di OOP.
sumber
Saya mendapatkan dari konflik dengan tabel bergabung yang ditentukan dalam kelas asosiasi (dengan bidang khusus tambahan) anotasi dan tabel gabungan yang didefinisikan dalam anotasi banyak ke banyak.
Definisi pemetaan dalam dua entitas dengan hubungan banyak-ke-banyak langsung tampaknya menghasilkan pembuatan tabel bergabung otomatis menggunakan anotasi 'joinTable'. Namun tabel bergabung sudah ditentukan oleh anotasi di kelas entitas yang mendasarinya dan saya ingin menggunakan definisi bidang kelas entitas asosiasi ini sendiri untuk memperluas tabel bergabung dengan bidang kustom tambahan.
Penjelasan dan solusinya adalah yang diidentifikasi oleh FMaz008 di atas. Dalam situasi saya, itu berkat pos ini di forum ' Pertanyaan Anotasi Doktrin '. Posting ini menarik perhatian pada dokumentasi Doktrin mengenai hubungan ManyToMany Uni-directional . Lihatlah catatan mengenai pendekatan menggunakan 'kelas entitas asosiasi' sehingga menggantikan pemetaan anotasi banyak-ke-banyak secara langsung antara dua kelas entitas utama dengan anotasi satu-ke-banyak di kelas entitas utama dan dua 'banyak-ke-banyak -satu penjelasan di kelas entitas asosiatif. Ada contoh yang disediakan dalam forum ini yang mengaitkan model Asosiasi dengan bidang tambahan :
sumber
Ini contoh yang sangat berguna. Tidak ada dalam doktrin dokumentasi 2.
Terima kasih banyak
Untuk fungsi proxy dapat dilakukan:
dan
sumber
Yang Anda maksud adalah metadata, data tentang data. Saya memiliki masalah yang sama untuk proyek yang sedang saya kerjakan dan harus meluangkan waktu untuk mencari tahu. Terlalu banyak informasi untuk dikirim di sini, tetapi di bawah ini ada dua tautan yang mungkin berguna bagi Anda. Mereka merujuk kerangka kerja Symfony, tetapi didasarkan pada ORM Doktrin.
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/
Semoga sukses, dan referensi Metallica yang bagus!
sumber
Solusinya ada dalam dokumentasi Doktrin. Di FAQ Anda dapat melihat ini:
http://docs.doctrine-project.org/en/2.1/reference/faq.html#how-can-i-add-columns-to-a-many-to-many-table
Dan tutorialnya ada di sini:
http://docs.doctrine-project.org/en/2.1/tutorials/composite-primary-keys.html
Jadi Anda tidak lagi melakukan
manyToMany
tetapi Anda harus membuat Entitas tambahan dan menempatkanmanyToOne
ke dua entitas Anda.ADD untuk @ f00bar komentar:
itu sederhana, Anda hanya perlu melakukan sesuatu seperti ini:
Jadi, Anda membuat entitas ArticleTag
Saya harap ini membantu
sumber
:(
Adakah yang bisa berbagi contoh kasus penggunaan ketiga menggunakan format yml? Saya akan benar-benar mencari tahu:#
Searah. Cukup tambahkan inversedBy: (Nama Kolom Asing) untuk menjadikannya Dua Arah.
Saya harap ini membantu. Sampai jumpa.
sumber
Anda mungkin dapat mencapai apa yang Anda inginkan dengan Class Table Inheritance di mana Anda mengubah AlbumTrackReference ke AlbumTrack:
Dan
getTrackList()
akan berisiAlbumTrack
objek yang bisa Anda gunakan seperti yang Anda inginkan:Anda perlu memeriksa ini secara menyeluruh untuk memastikan Anda tidak menderita dari segi kinerja.
Pengaturan Anda saat ini sederhana, efisien, dan mudah dimengerti bahkan jika beberapa semantik tidak cocok dengan Anda.
sumber
Saat mendapatkan semua trek album dalam kelas album, Anda akan menghasilkan satu permintaan lagi untuk satu catatan lagi. Itu karena metode proxy. Ada contoh lain dari kode saya (lihat posting terakhir dalam topik): http://groups.google.com/group/doctrine-user/browse_thread/thread/d1d87c96052e76f7/436b896e83c10868#436b896e83c10868
Apakah ada metode lain untuk menyelesaikannya? Bukankah satu orang bergabung dengan solusi yang lebih baik?
sumber
Berikut adalah solusinya seperti yang dijelaskan dalam Dokumentasi Doctrine2
sumber