首先让我概述一下这个场景。我有一个可以分配给许多不同对象的Note对象
我想象数据库看起来像:
图书
id | title | pages
1 | harry potter | 200
2 | game of thrones | 500
图片
id | full | thumb
2 | image.jpg | image-thumb.jpg
地址
id | street | city
1 | 123 street | denver
2 | central ave | tampa
注意
id | object_id | object_type | content | date
1 | 1 | image | "lovely pic" | 2015-02-10
2 | 1 | image | "red tint" | 2015-02-30
3 | 1 | address | "invalid" | 2015-01-05
4 | 2 | book | "boobies" | 2014-09-06
5 | 1 | book | "prettygood" | 2016-05-05
问题是,我如何在Doctrine中对此进行建模。我读到的关于单表继承的讨论,没有一对多的关系。
要注意(没有双关语;),您可能会注意到基于与其相关的对象,笔记永远不需要唯一属性。
理想情况下,我可以执行$address->getNotes()
,它将返回$image->getNotes()
将返回的相同类型的Note对象。
至少我要解决的问题是避免使用三个不同的表:image_notes,book_notes和address_notes。
答案 0 :(得分:4)
我个人不会在这里使用超类。认为Book
,Image
和Address
可以实现接口的更多案例:
interface iNotable
{
public function getNotes();
}
以书为例:
class Book implements iNotable {
/**
* @OneToMany(targetEntity="Note", mappedBy="book")
**/
protected $notes;
// ...
public function getNotes()
{
return $this->notes;
}
}
Note
然后需要@ManyToOne
关系,只有一个关系适用于每个实体实例:
class Note {
/**
* @ManyToOne(targetEntity="Book", inversedBy="notes")
* @JoinColumn
**/
protected $book;
/**
* @ManyToOne(targetEntity="Image", inversedBy="notes")
* @JoinColumn
**/
protected $image;
/**
* @ManyToOne(targetEntity="Address", inversedBy="notes")
* @JoinColumn
**/
protected $address;
// ...
}
如果您不喜欢这里对每个值得注意的类的多个引用,那么可以使用继承,并使用单个表继承具有类似AbstractNotable
超类的内容。虽然现在看起来可能比较整洁,但我不推荐它的原因是随着数据模型的增长,您可能需要引入类似的模式,最终可能会使继承树变得无法管理。
编辑:通过Note
,$book
或{{1}确切地确定$image
验证器确保数据完整性也很有用为每个实例设置。像这样:
$address
答案 1 :(得分:4)
这个问题为应用程序带来了不必要的复杂性。仅仅因为音符具有相同的结构并不意味着它们是同一个实体。在3NF中对数据库建模时,它们不是同一个实体,因为无法将笔记从书籍移动到地址。在您的描述中,book和book_note等之间存在确定的父子关系,因此请对其进行建模。
对于数据库而言,更多的表不是问题,但正如此问题所示,不必要的代码复杂性。聪明的人只是为了聪明。这是ORM的问题,人们不再进行完全规范化,也不能正确建模数据库。
答案 2 :(得分:2)
我再次阅读你的问题之后又添加了另一个答案,并意识到你说过“我想避免添加另一个连接表”。
创建一个Note
基础实体,并在3个不同的注释实体中扩展此类,例如:BookNote
,AddressNote
和ImageNote
。在我的解决方案中,注释表将如下所示:
id | address_id | book_id | image_id | object_type | content | date
1 | null | null | 1 | image | "lovely pic" | 2015-02-10
2 | null | null | 1 | image | "red tint" | 2015-02-30
3 | 1 | null | null | address | "invalid" | 2015-01-05
4 | null | 2 | null | book | "boobies" | 2014-09-06
5 | null | 1 | null | book | "prettygood" | 2016-05-05
由于外键约束,您不能使用一个公共列object_id
。
带有setter和getter的NotesTrait
用于防止代码重复的注释:
<?php
namespace Application\Entity;
use Doctrine\Common\Collections\Collection;
/**
* @property Collection $notes
*/
trait NotesTrait
{
/**
* Add note.
*
* @param Note $note
* @return self
*/
public function addNote(Note $note)
{
$this->notes[] = $note;
return $this;
}
/**
* Add notes.
*
* @param Collection $notes
* @return self
*/
public function addNotes(Collection $notes)
{
foreach ($notes as $note) {
$this->addNote($note);
}
return $this;
}
/**
* Remove note.
*
* @param Note $note
*/
public function removeNote(Note $note)
{
$this->notes->removeElement($note);
}
/**
* Remove notes.
*
* @param Collection $notes
* @return self
*/
public function removeNotes(Collection $notes)
{
foreach ($notes as $note) {
$this->removeNote($note);
}
return $this;
}
/**
* Get notes.
*
* @return Collection
*/
public function getNotes()
{
return $this->notes;
}
}
您的Note
实体:
<?php
namespace Application\Entity;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="object_type", type="string")
* @DiscriminatorMap({"note"="Note", "book"="BookNote", "image"="ImageNote", "address"="AddressNote"})
*/
class Note
{
/**
* @var integer
* @ORM\Id
* @ORM\Column(type="integer", nullable=false)
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @var string
* @ORM\Column(type="string")
*/
protected $content
/**
* @var string
* @ORM\Column(type="date")
*/
protected $date
}
您的BookNote
实体:
<?php
namespace Application\Entity;
/**
* @Entity
*/
class BookNote extends Note
{
/** MANY-TO-ONE BIDIRECTIONAL, OWNING SIDE
* @var Book
* @ORM\ManyToOne(targetEntity="Application\Entity\Book", inversedBy="notes")
* @ORM\JoinColumn(name="book_id", referencedColumnName="id", nullable=true)
*/
protected $book;
}
您的AddressNote
实体:
<?php
namespace Application\Entity;
/**
* @Entity
*/
class AddressNote extends Note
{
/** MANY-TO-ONE BIDIRECTIONAL, OWNING SIDE
* @var Address
* @ORM\ManyToOne(targetEntity="Application\Entity\Address", inversedBy="notes")
* @ORM\JoinColumn(name="address_id", referencedColumnName="id", nullable=true)
*/
protected $address;
}
您的ImageNote
实体:
<?php
namespace Application\Entity;
/**
* @Entity
*/
class ImageNote extends Note
{
/** MANY-TO-ONE BIDIRECTIONAL, OWNING SIDE
* @var Image
* @ORM\ManyToOne(targetEntity="Application\Entity\Image", inversedBy="notes")
* @ORM\JoinColumn(name="image_id", referencedColumnName="id", nullable=true)
*/
protected $image;
}
您的Book
实体:
<?php
namespace Application\Entity;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
class Book
{
use NotesTrait;
/**
* @var integer
* @ORM\Id
* @ORM\Column(type="integer", nullable=false)
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/** ONE-TO-MANY BIDIRECTIONAL, INVERSE SIDE
* @var Collection
* @ORM\OneToMany(targetEntity="Application\Entity\BookNote", mappedBy="book")
*/
protected $notes;
/**
* Constructor
*/
public function __construct()
{
$this->notes = new ArrayCollection();
}
}
您的Address
实体:
<?php
namespace Application\Entity;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
class Address
{
use NotesTrait;
/**
* @var integer
* @ORM\Id
* @ORM\Column(type="integer", nullable=false)
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/** ONE-TO-MANY BIDIRECTIONAL, INVERSE SIDE
* @var Collection
* @ORM\OneToMany(targetEntity="Application\Entity\AddressNote", mappedBy="address")
*/
protected $notes;
/**
* Constructor
*/
public function __construct()
{
$this->notes = new ArrayCollection();
}
}
您的Image
实体:
<?php
namespace Application\Entity;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
class Image
{
use NotesTrait;
/**
* @var integer
* @ORM\Id
* @ORM\Column(type="integer", nullable=false)
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/** ONE-TO-MANY BIDIRECTIONAL, INVERSE SIDE
* @var Collection
* @ORM\OneToMany(targetEntity="Application\Entity\ImageNote", mappedBy="image")
*/
protected $notes;
/**
* Constructor
*/
public function __construct()
{
$this->notes = new ArrayCollection();
}
}
答案 3 :(得分:1)
由于你不能在一列上有几个外键,你有2个solutuons:
one2many
关系,因此它会为book_note,image_note等每个关系创建一个连接表,其中包含id对book.id-note.id等(我的意思是one2many而不是many2many,因为笔记通常没有必要知道它属于哪本书或图像或地址)。或制作与图书/图片/地址相关的多个实体BookNote
,ImageNote
,AddressNote
以及注释和其他数据,例如,如果某种类型的笔记有其中的一些其他数据。
您可以使用单个表继承,如此问题的已接受答案中所述,但它是一个类似于Steven提出的解决方案,它使您在鉴别器列中保存其他数据。
Multiple JoinColumns in Symfony2 using Doctrine annotations?
Doctrine文档还提到了某些情况下的性能损失。
Steve Chambers给出的解决方案解决了一个问题,但你强制注释表保存不必要的数据(其他对象的空id列)
答案 4 :(得分:1)
最接近的方法是使用带有连接表的单向一对多。 In doctrine this is done with a unidirectional Many-To-Many with a unique constraint on the join column
这种解决方案的缺点是它是单向的,意味着你的笔记不知道关系的拥有方。但乍一看,在您的情况下看起来这应该不是问题。
您将丢失注释表中的object_id
和object_type
列。
在完整代码中,这将是这样的:
带有setter和getter的NotesTrait
用于防止代码重复的注释:
<?php
namespace Application\Entity;
use Doctrine\Common\Collections\Collection;
/**
* @property Collection $notes
*/
trait NotesTrait
{
/**
* Add note.
*
* @param Note $note
* @return self
*/
public function addNote(Note $note)
{
$this->notes[] = $note;
return $this;
}
/**
* Add notes.
*
* @param Collection $notes
* @return self
*/
public function addNotes(Collection $notes)
{
foreach ($notes as $note) {
$this->addNote($note);
}
return $this;
}
/**
* Remove note.
*
* @param Note $note
*/
public function removeNote(Note $note)
{
$this->notes->removeElement($note);
}
/**
* Remove notes.
*
* @param Collection $notes
* @return self
*/
public function removeNotes(Collection $notes)
{
foreach ($notes as $note) {
$this->removeNote($note);
}
return $this;
}
/**
* Get notes.
*
* @return Collection
*/
public function getNotes()
{
return $this->notes;
}
}
您的Book
实体:
<?php
namespace Application\Entity;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
class Book
{
use NotesTrait;
/**
* @var integer
* @ORM\Id
* @ORM\Column(type="integer", nullable=false)
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @ORM\ManyToMany(targetEntity="Note")
* @ORM\JoinTable(name="book_notes",
* inverseJoinColumns={@ORM\JoinColumn(name="note_id", referencedColumnName="id")},
* joinColumns={@ORM\JoinColumn(name="book_id", referencedColumnName="id", unique=true)}
* )
*/
$notes;
/**
* Constructor
*/
public function __construct()
{
$this->notes = new ArrayCollection();
}
}
您的Address
实体:
<?php
namespace Application\Entity;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
class Address
{
use NotesTrait;
/**
* @var integer
* @ORM\Id
* @ORM\Column(type="integer", nullable=false)
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @ORM\ManyToMany(targetEntity="Note")
* @ORM\JoinTable(name="address_notes",
* inverseJoinColumns={@ORM\JoinColumn(name="note_id", referencedColumnName="id")},
* joinColumns={@ORM\JoinColumn(name="address_id", referencedColumnName="id", unique=true)}
* )
*/
$notes;
/**
* Constructor
*/
public function __construct()
{
$this->notes = new ArrayCollection();
}
}
您的Image
实体:
<?php
namespace Application\Entity;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
class Image
{
use NotesTrait;
/**
* @var integer
* @ORM\Id
* @ORM\Column(type="integer", nullable=false)
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @ORM\ManyToMany(targetEntity="Note")
* @ORM\JoinTable(name="image_notes",
* inverseJoinColumns={@ORM\JoinColumn(name="note_id", referencedColumnName="id")},
* joinColumns={@ORM\JoinColumn(name="image_id", referencedColumnName="id", unique=true)}
* )
*/
$notes;
/**
* Constructor
*/
public function __construct()
{
$this->notes = new ArrayCollection();
}
}