[解決済み】Doctrine2: 参照テーブルで余分なカラムを持つ多対多を処理する最良の方法
質問
Doctrine2 で多対多のリレーションを扱うのに、もっとも簡単でクリーンな方法は何でしょうか?
例えば、次のようなアルバムがあるとします。 マスター・オブ・パペッツ by メタリカ を複数のトラックで表示します。しかし、1つの曲が複数のアルバムに収録されている場合がありますので、ご注意ください。 バッテリー メタリカ は、3枚のアルバムがこの曲を収録しています。
そこで、私が必要としているのは、アルバムとトラックの間の多対多の関係で、いくつかの追加列(指定されたアルバム内のトラックの位置など)を持つ3番目のテーブルを使用します。実際には、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
EDIT
プロキシメソッドを使用するように@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();
}
そして、それはあまりきれいな方法とは言えません。
解決方法は?
Doctrine ユーザーメーリングリストに同様の質問を投稿したところ、とてもシンプルな回答が返ってきました。
多対多のリレーションをエンティティとして考え、3つのオブジェクトを持ち、それらの間を一対多と多対一のリレーションでリンクしていることに気づきます。
リレーションがデータを持ったら、それはもうリレーションではない!?
関連
-
[解決済み】空の配列要素を削除する
-
[解決済み】pdo - 非オブジェクトのメンバー関数prepare()への呼び出し【重複】。
-
[解決済み】mysqli_select_db()は、パラメータ1がmysqliであることを期待し、文字列が与えられる。
-
[解決済み】警告。数値でない値に遭遇しました
-
[解決済み】phpMyAdmin: シークレットパスフレーズ?
-
[解決済み] [Solved] Fatal error: メンバ関数 query() の null への呼び出し。
-
[解決済み] 入力ファイルが指定されていない
-
[解決済み】phpのシンタックスエラー、予期しないT_IFエラーを修正する方法は?[クローズド]。
-
[解決済み] Forbidden :このサーバーの /phpmyadmin にアクセスする権限がありません。
-
[解決済み] libapache2-mod-php7 パッケージの場所がわからない
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】XAMPPポート80をPID 4の「Unable to open process」が使用中 [重複] XAMPPポート80をPID 4の「Unable to open process」が使用中。]
-
[解決済み】Fatal error: 未定義の関数 sqlsrv_connect() を呼び出した。
-
[解決済み] PHP & MySQL: mysqli_num_rows() expects parameter 1 to be mysqli_result, boolean given [重複] PHP & MySQL: mysqli_num_rows() expects parameter 1 to be mysqli_result, boolean given.
-
[解決済み] SAJAXは死んだか?何を置き換えるべきか?
-
[解決済み】メンバ関数をnullで呼び出す?
-
[解決済み】PHPからPythonスクリプトを実行する
-
[解決済み】Netbeans 7.4 for PHPで「スーパーグローバルな$_POST配列に直接アクセスしないでください」という警告が発生する。
-
MacでPHPを実行した際に、メモリサイズが134217728バイトも消費される問題の解決方法について
-
[解決済み] PHP product.php?id=1 のような URL を作成する方法
-
[解決済み] libapache2-mod-php7 パッケージの場所がわからない