source

원칙 2:참조 표에 추가 열을 사용하여 다대다를 처리하는 가장 좋은 방법

lovecheck 2022. 10. 29. 10:05
반응형

원칙 2:참조 표에 추가 열을 사용하여 다대다를 처리하는 가장 좋은 방법

저는 독트린2에서 다대다 관계에서 일하기 위한 최선의 방법, 가장 깔끔한 방법, 그리고 가장 단순한 방법이 무엇인지 궁금합니다.

Master of Puppets by Metalica와 같은 여러 곡이 수록된 앨범이 있다고 가정해 봅시다.하지만 Battery by Metalica처럼 곡이 한 앨범에 수록될 수 있다는 사실에 주목하십시오. 세 개의 앨범에는 이 곡이 수록되어 있습니다.

그래서 앨범과 트랙의 다대다 관계가 필요하고, 세 번째 표와 몇 개의 추가 열(특정 앨범의 트랙 위치처럼)을 사용해야 합니다.실제로 이 기능을 실현하기 위해서는 독트린의 문서에서 알 수 있듯이 1대 다의 이중 관계를 사용해야 합니다.

/** @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() 、 の returns of of of of of の을 반환합니다.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();
 }

그리고 그것은 매우 깨끗한 방법이 아니다.

독트린 유저 메일링 리스트에서도 같은 질문을 하고, 매우 심플한 답변을 받았습니다.

다대다 관계를 하나의 엔티티로 간주하면, 그 사이에 1대 다와 다대 1의 관계가 있는 3개의 객체가 있다는 것을 알게 됩니다.

http://groups.google.com/group/doctrine-user/browse_thread/thread/d1d87c96052e76f7/436b896e83c10868#436b896e83c10868

일단 관계가 데이터를 갖게 되면, 그것은 더 이상 관계가 아니다!

$album->getTrackList()에서 항상 "AlbumTrackReference" 엔티티를 돌려받을 수 있으므로 Track과 프록시에서 메서드를 추가하는 것은 어떻습니까?

class AlbumTrackReference
{
    public function getTitle()
    {
        return $this->getTrack()->getTitle();
    }

    public function getDuration()
    {
        return $this->getTrack()->getDuration();
    }
}

이렇게 하면 앨범 트랙 루프와 관련된 다른 모든 코드뿐만 아니라 루프가 상당히 간소화됩니다. 모든 메서드는 앨범 트랙 참조 내에서 프록시 처리되기 때문입니다.

foreach ($album->getTracklist() as $track) {
    echo sprintf("\t#%d - %-20s (%s) %s\n", 
        $track->getPosition(),
        $track->getTitle(),
        $track->getDuration()->format('H:i:s'),
        $track->isPromoted() ? ' - PROMOTED!' : ''
    );
}

참고로 AlbumTrackReference(예: "AlbumTrack")의 이름을 변경해야 합니다.이것은 분명히 참고 자료일 뿐만 아니라 추가적인 논리를 포함하고 있다.앨범과 연결되지 않고 프로모션 CD 등을 통해 제공되는 트랙도 있을 수 있기 때문에 더욱 깔끔하게 분리할 수 있습니다.

좋은 본보기가 되는 것은 없다.

3개의 참가 클래스 간의 1 대 다 / 다 대 1 어소시에이션의 깨끗한 코딩 예를 찾고 있는 유저는, 이 사이트를 참조해 주세요.

참가하고 있는 3개의 클래스간의 1대 다/다대 1의 어소시에이션의 좋은 예

기본 키에 대해 생각해 보십시오.

또, 주된 열쇠에 대해서도 생각해 주세요.이러한 관계에는 복합 키를 사용하는 경우가 많습니다.원칙은 기본적으로 이것을 지지한다.참조된 엔티티를 ID로 만들 수 있습니다.복합 키에 대한 설명서는 여기를 참조하십시오.

@beberlei의 프록시 메서드 사용 제안에 찬성합니다.이 프로세스를 간단하게 하기 위해서 할 수 있는 것은, 다음의 2개의 인터페이스를 정의하는 것입니다.

interface AlbumInterface {
    public function getAlbumTitle();
    public function getTracklist();
}

interface TrackInterface {
    public function getTrackTitle();
    public function getTrackDuration();
}

두 분 .Album 당신의 ★★★★★★★★.Track 수 만, 「」의 경우는,AlbumTrackReference는, 양쪽 수 있습니다.

class Album implements AlbumInterface {
    // implementation
}

class Track implements TrackInterface {
    // implementation
}

/** @Entity whatever */
class AlbumTrackReference implements AlbumInterface, TrackInterface
{
    public function getTrackTitle()
    {
        return $this->track->getTrackTitle();
    }

    public function getTrackDuration()
    {
        return $this->track->getTrackDuration();
    }

    public function getAlbumTitle()
    {
        return $this->album->getAlbumTitle();
    }

    public function getTrackList()
    {
        return $this->album->getTrackList();
    }
}

하면 할 수 .Track ★★★Album , , , , , , , , , , , 을합니다.TrackInterface ★★★★★★★★★★★★★★★★★」AlbumInterface하시는 '사용하실 수 AlbumTrackReference한 것은, 을 조금입니다.필요한 것은, 인터페이스간의 방식을 조금 구별하는 것입니다.

, 로직은 DQL이 됩니다.Album ★★★AlbumTrackReference , " " "Track ★★★AlbumTrackReference에 모든 모든 것을 숨겼습니다.

이게 도움이 됐으면 좋겠네요!

첫째, 나는 베벌레이의 제안에 대부분 동의한다.하지만, 당신은 스스로를 함정에 빠뜨릴 수도 있습니다.당신의 도메인은 트랙의 자연스러운 키로 제목을 고려하고 있는 것 같습니다.그것은 당신이 마주치는 시나리오의 99%에 해당합니다.단, 배터리 온 더 퍼핏스가 메탈리카 컬렉션 버전과는 다른 버전(길이, 라이브, 어쿠스틱, 리믹스, 리마스터드 등)이라면?

이 경우의 처리 방법에 따라서는, berlei 의 추천 루트를 참조하거나, 앨범::getTracklist() 의 추가 로직을 참조하거나 할 수 있습니다.개인적으로는 API를 깔끔하게 유지하기 위해 추가 로직이 정당하다고 생각합니다만, 둘 다 장점이 있습니다.

만약 당신이 나의 사용 사례를 수용하고 싶다면, 당신은 트랙에 다른 트랙(아마 $similarTracks)을 참조하는 OneToMany를 포함시킬 수 있습니다.이 경우 Battery 트랙에는 The Metalica Collection용Master of the Puppets용 두 개의 엔티티가 있습니다.그러면 유사한 각 트랙 엔티티는 서로에 대한 참조를 포함할 것입니다.또, 현재의 Album Track Reference 클래스가 없어져, 현재의 「문제」가 없어집니다.복잡함을 다른 지점으로 옮긴다는 것은 인정하지만, 이전에는 할 수 없었던 유스케이스를 처리할 수 있습니다.

당신은 "최선의 방법"을 요구하지만, 최선의 방법은 없습니다.여러 가지 방법이 있는데 당신은 이미 그 중 몇 가지를 발견했어요.어소시에이션 클래스를 사용할 때 어소시에이션 관리를 어떻게 관리하거나 캡슐화할지는 전적으로 귀하와 귀하의 구체적인 도메인에 달려 있습니다.아무도 "최선의 방법"을 보여줄 수 없습니다.

그것과는 별도로, 방정식에서 독트린과 관계형 데이터베이스를 제거함으로써 질문을 많이 단순화할 수 있다.질문의 본질은 플레인 OOP에서 어소시에이션 클래스를 어떻게 다루어야 하는지에 대한 질문으로 요약됩니다.

어소시에이션클래스(추가 커스텀필드)에 정의되어 있는 조인테이블과 다대다 주석으로 정의되어 있는 조인테이블과의 경합에서 벗어나고 있었습니다.

직접 다대다 관계를 가진 두 엔티티에서의 매핑 정의는 'joinTable' 주석을 사용하여 조인 테이블을 자동으로 생성하는 것으로 나타났습니다.그러나 조인 테이블은 기본 엔티티 클래스의 주석으로 이미 정의되어 있으므로 추가 사용자 지정 필드로 조인 테이블을 확장하기 위해 이 연결 엔티티 클래스의 자체 필드 정의를 사용하도록 했습니다.

설명과 해결방법은 위의 FMaz008에서 확인한 것입니다.제 처지에서는 '닥터린 주석 질문'이라는 포럼에 올린 글 덕분입니다.이 투고는 ManyToMany 단방향 관계에 관한 독트린 문서에 주목합니다.'연관 엔티티 클래스'를 사용하여 두 주 엔티티 클래스 간의 다대다 주석 매핑을 주 엔티티 클래스의 일대다 주석과 연관 엔티티 클래스의 두 개의 '다대일' 주석으로 직접 대체하는 접근법에 관한 참고 사항을 살펴본다.이 포럼 포스트 어소시에이션모델에는 추가 필드가 포함되어 있습니다.

public class Person {

  /** @OneToMany(targetEntity="AssignedItems", mappedBy="person") */
  private $assignedItems;

}

public class Items {

    /** @OneToMany(targetEntity="AssignedItems", mappedBy="item") */
    private $assignedPeople;
}

public class AssignedItems {

    /** @ManyToOne(targetEntity="Person")
    * @JoinColumn(name="person_id", referencedColumnName="id")
    */
private $person;

    /** @ManyToOne(targetEntity="Item")
    * @JoinColumn(name="item_id", referencedColumnName="id")
    */
private $item;

}

이것은 매우 유용한 예입니다.문서화 원칙 2에 결여되어 있습니다.

정말 감사합니다.

프록시 기능을 수행할 수 있습니다.

class AlbumTrack extends AlbumTrackAbstract {
   ... proxy method.
   function getTitle() {} 
}

class TrackAlbum extends AlbumTrackAbstract {
   ... proxy method.
   function getTitle() {}
}

class AlbumTrackAbstract {
   private $id;
   ....
}

그리고.

/** @OneToMany(targetEntity="TrackAlbum", mappedBy="album") */
protected $tracklist;

/** @OneToMany(targetEntity="AlbumTrack", mappedBy="track") */
protected $albumsFeaturingThisTrack;

해결책은 독트린의 문서에 있습니다.FAQ에서 다음을 확인할 수 있습니다.

http://docs.doctrine-project.org/en/2.1/reference/faq.html#how-can-i-add-columns-to-a-many-to-many-table

튜토리얼은 다음과 같습니다.

http://docs.doctrine-project.org/en/2.1/tutorials/composite-primary-keys.html

이제 더 안 해요.manyToMany, 추가 하여 ""를 합니다.manyToOne당신의 두 실체에 대해서요

@f00bar 코멘트에 대한 ADD:

간단해, 다음과 같은 조작을 실시하면 됩니다.

Article  1--N  ArticleTag  N--1  Tag

따라서 엔티티 ArticleTag를 만듭니다.

ArticleTag:
  type: entity
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  manyToOne:
    article:
      targetEntity: Article
      inversedBy: articleTags
  fields: 
    # your extra fields here
  manyToOne:
    tag:
      targetEntity: Tag
      inversedBy: articleTags

도움이 되었으면 좋겠다

단방향.inversedBy:(외부 열 이름)를 추가하여 양방향으로 만듭니다.

# config/yaml/ProductStore.dcm.yml
ProductStore:
  type: entity
  id:
    product:
      associationKey: true
    store:
      associationKey: true
  fields:
    status:
      type: integer(1)
    createdAt:
      type: datetime
    updatedAt:
      type: datetime
  manyToOne:
    product:
      targetEntity: Product
      joinColumn:
        name: product_id
        referencedColumnName: id
    store:
      targetEntity: Store
      joinColumn:
        name: store_id
        referencedColumnName: id

도움이 됐으면 좋겠어요.나중에 봐요.

AlbumTrackReference를 AlbumTrack으로 변경하는 Class Table Inheritance를 사용하여 원하는 것을 달성할 수 있습니다.

class AlbumTrack extends Track { /* ... */ }

★★★★★★★★★★★★★★★★★.getTrackList() would would would를 AlbumTrack「 」 「 」 、 「 」 、 「 」

foreach($album->getTrackList() as $albumTrack)
{
    echo sprintf("\t#%d - %-20s (%s) %s\n", 
        $albumTrack->getPosition(),
        $albumTrack->getTitle(),
        $albumTrack->getDuration()->format('H:i:s'),
        $albumTrack->isPromoted() ? ' - PROMOTED!' : ''
    );
}

퍼포먼스에 문제가 생기지 않도록 이 문제를 철저히 검토해야 합니다.

현재 설정은 단순하고 효율적이며 의미론 중 일부가 적절하지 않더라도 이해하기 쉽습니다.

앨범 클래스 내에서 모든 앨범 트랙을 가져오는 동안 하나 더 레코드에 대한 쿼리를 하나 더 생성합니다.그건 프록시 방식 때문이에요.코드의 다른 예가 있습니다(토픽의 마지막 투고 참조).http://groups.google.com/group/doctrine-user/browse_thread/thread/d1d87c96052e76f7/436b896e83c10868#436b896e83c10868

그것을 해결할 다른 방법이 있나요?한 번 가입하는 것이 더 낫지 않나요?

다음은 '독트린2' 매뉴얼에 기재되어 있는 해결책입니다.

<?php
use Doctrine\Common\Collections\ArrayCollection;

/** @Entity */
class Order
{
    /** @Id @Column(type="integer") @GeneratedValue */
    private $id;

    /** @ManyToOne(targetEntity="Customer") */
    private $customer;
    /** @OneToMany(targetEntity="OrderItem", mappedBy="order") */
    private $items;

    /** @Column(type="boolean") */
    private $payed = false;
    /** @Column(type="boolean") */
    private $shipped = false;
    /** @Column(type="datetime") */
    private $created;

    public function __construct(Customer $customer)
    {
        $this->customer = $customer;
        $this->items = new ArrayCollection();
        $this->created = new \DateTime("now");
    }
}

/** @Entity */
class Product
{
    /** @Id @Column(type="integer") @GeneratedValue */
    private $id;

    /** @Column(type="string") */
    private $name;

    /** @Column(type="decimal") */
    private $currentPrice;

    public function getCurrentPrice()
    {
        return $this->currentPrice;
    }
}

/** @Entity */
class OrderItem
{
    /** @Id @ManyToOne(targetEntity="Order") */
    private $order;

    /** @Id @ManyToOne(targetEntity="Product") */
    private $product;

    /** @Column(type="integer") */
    private $amount = 1;

    /** @Column(type="decimal") */
    private $offeredPrice;

    public function __construct(Order $order, Product $product, $amount = 1)
    {
        $this->order = $order;
        $this->product = $product;
        $this->offeredPrice = $product->getCurrentPrice();
    }
}

언급URL : https://stackoverflow.com/questions/3542243/doctrine2-best-way-to-handle-many-to-many-with-extra-columns-in-reference-table

반응형