一尘不染

Doctrine2:处理引用表中额外列的多对多的最佳方法

php

我想知道在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()方法中进行一些额外的处理,但是最简单的方法是什么?我是否被迫写类似的东西?

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();
 }

那不是一个很干净的方法。


阅读 372

收藏
2020-05-26

共1个答案

一尘不染

我已经在“ Doctrine用户”邮件列表中打开了一个类似的问题,并且得到了一个非常简单的答案。

将多对多关系视为一个实体本身,然后您意识到您有3个对象,它们之间以一对多和多对一关系链接。

关系一旦有了数据,就不再是关系!

2020-05-26