symfony2中可选的多个oneToMany关系

时间:2014-07-19 10:07:32

标签: php mysql symfony database-design doctrine-orm

我在Doctrine Symfony2中有实体:用户,频道,视频和评论;用户可以报告其中一个。我使用以下字段设计了报表实体:

  • 用户id
  • 状态
  • reportTime
  • 描述

如何引用报告的实体?,因为所有报告的字段对于我只想使用一个表用于报告的所有实体都相似,并将这些字段添加到报告实体:

  • referenceEntityName(一个字符串,可能是以下之一:用户,频道,视频,评论)
  • 频道(与频道实体的ManytoOne关系)
  • 视频(ManytoOne与视频实体的关系)
  • 评论(ManytoOne与评论实体的关系)
  • 用户(与用户实体的ManytoOne关系)

这是最佳做法还是我应该为每种报告创建单独的表格?

修改: 基于@Alex答案,我改进了Report类并添加了这些方法:

setEntity($entity){
  if ($obj instanceof Video){
     $this->referenceEntityName = 'Video';
     $this->setVideo();
  }
  elseif($obj instanceof Comment){
     $this->referenceEntityName == 'Comment'
     $this->setComment();
  }
  //...
}

getEntity(){

   if($this->referenceEntityName == 'Video'){
     $this->getVideo()
   }// ifelse statements for other entities ...
}

我有4个关系,每个实例只使用其中一个,不是有点凌乱!? 又是最佳做法,还是我应该做点其他事情? 如果我想使用FormBuilder类怎么办,有什么问题吗?

1 个答案:

答案 0 :(得分:2)

在一个简单的解决方案中,例如你只有用户(而不是视频,评论和频道),解决方案很简单;每个用户可以有多个报告,每个报告只能属于一个用户。这是一对多的关系 - 一个用户有很多报告。在Symfony 2和Doctrine中,这将被建模为:

// src/Acme/DemoBundle/Entity/User.php

// ...
use Doctrine\Common\Collections\ArrayCollection;

class User
{
    // ...

    /**
     * @ORM\OneToMany(targetEntity="Report", mappedBy="user")
     */
    protected $reports;

    public function __construct()
    {
        $this->reports = new ArrayCollection();
    }

    // ...
}

// src/Acme/DemoBundle/Entity/Report.php

// ...

class Report
{
    // ...

    /**
     * @ORM\ManyToOne(targetEntity="User", inversedBy="reports")
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
     */
    protected $user;

    // ...

}

在这种情况下,要创建报告并将其与用户关联,我们会:

// get the User the Report will belong to
$user = $em->getRepository('AcmeDemoBundle:User')->find(1);
// create the Report
$report = new Report();
// add the User to the Report
$report->setUser($user);
// then persist it, etc ...

请注意,setUser()方法可用,因为运行了控制台命令以自动生成它们。强烈建议这样做,因为它为您创建了必要的类型提示。对于Symfony 2.5之前的安装,命令为:

php app/console doctrine:generate:entities Acme

> = 2.5次安装,命令为:

php bin/console doctrine:generate:entities Acme

您的要求在某种程度上使这个简单示例复杂化,因为报告也可以属于评论和视频等。为了示例,我们将这些事物称为实体。一个糟糕的方法是简单地向报表添加3个新属性,每个新实体一个,然后为实体添加3个新的setter方法。这有两个原因:报告只属于其中一个实体,因此3个属性和setter方法永远不会用于每个Report实体。其次,如果您将新实体添加到业务模型中,或者删除一个实体,则需要编辑报表实体以及数据库架构。

更好的方法是在报告中简单地使用一个属性和set方法,该方法可以应用于所有实体。因此,我们可以调用setUser而不是调用setEntity,而是让它接受任何4个。考虑到这种方法,让我们回顾第一个示例,并注意函数签名中为setUser方法生成的提示类型:

public function setUser(Acme\DemoBundle\Entity\User $user)

看到它需要是Acme\DemoBundle\Entity\User类型。我们如何克服这一点,让它接受4个实体中的任何一个?解决方案是让所有实体都从父类派生。然后在基类中创建函数类型提示:

public function setUser(Acme\DemoBundle\Entity\Base $entity)

基类将包含所有常见元素,特别是名称'以及报告的数组集合:

// src/Acme/DemoBundle/Entity/Base.php

// ...
use Doctrine\Common\Collections\ArrayCollection;

class Base
{
    // ...

    /**
     * @ORM\Column(name="name", type="text")
     */
    protected $name

    /**
     * @ORM\OneToMany(targetEntity="Report", mappedBy="baseEntity")
     */
    protected $reports;

    public function __construct()
    {
        $this->reports = new ArrayCollection();
    }

    // ...
}

然后为每个孩子,例如用户和视频:

// src/Acme/DemoBundle/Entity/User.php

// ...
use AcmeDemoBundle\Entity\Base;

class User extends Base
{
    /** 
     * @ORM\Column(name="firstname", type="text")
     */
    protected $firstName;

    // ...
}

和视频

// src/Acme/DemoBundle/Entity/Video.php

// ...
use AcmeDemoBundle\Entity\Base;

class Video extends Base
{
    /** 
     * @ORM\Column(name="title", type="text")
     */
    protected $title;

    // ...

并更改我们的报告实体:

// src/Acme/DemoBundle/Entity/Report.php

// ...

class Report
{
    // ...

    /**
     * @ORM\ManyToOne(targetEntity="Base", inversedBy="reports")
     * @ORM\JoinColumn(name="base_id", referencedColumnName="id")
     */
    protected $baseEntity;

    // ...

}

请记住运行doctrine命令以生成setBaseEntity方法。当你这样做时,请注意它现在将接受任何派生的基础

然后,以视频报告为例,我们获取视频,创建报告,并将视频添加到报告中:

$video = // get the video you want
$report = new Report();
$report->setBaseEntity($video);

要检索属于评论的所有报告,我们会获得评论,并获取报告:

$video = // get the video you want
$reports = $video->getReports();
foreach($reports as $report){
    $reportText = $report->getText(); // assuming the Report has a `text` field
}

更新

这些实体之间的继承关系可以使用Doctrine使用单表继承在数据库中建模:

/**
 * @ORM\Entity
 * @ORM\Table(name="base_entities")
 * @ORM\InheritanceType("SINGLE_TYPE")
 * @ORM\Discriminator(name="entity_type", type="string")
 * @ORM\DiscriminatorMap({"user" = "User", "comment" = "Comment", "video" = "Video", "channel" = "Channel"})
 */