【问题标题】:optional multiple oneToMany relation in symfony2symfony2中的可选多个oneToMany关系
【发布时间】:2014-09-10 10:15:24
【问题描述】:

我在 Doctrine Symfony2 中有实体:用户、频道、视频和评论;用户可以报告其中之一。我设计了带有这些字段的报告实体:

  • 用户ID
  • 状态
  • 报告时间
  • 说明

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

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

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

编辑: 基于@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类,有什么问题吗?

【问题讨论】:

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


    【解决方案1】:

    在一个简单的解决方案中,例如您只有用户(而不是视频、评论和频道),解决方案会很简单;每个用户可以有多个报表,每个报表必须只属于一个用户。这是一对多的关系——一个用户有多个报告。在 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 方法。这很糟糕,原因有两个:一个报告将永远属于一个实体,因此三个属性和设置方法将永远不会用于每个报告实体。其次,如果您在业务模型中添加或删除新实体,则需要编辑报表实体以及数据库架构。

    更好的方法是在您的报告中简单地使用一个属性和设置方法,可以将其应用于您的所有实体。因此,我们可以调用setEntity,而不是调用setUser,并让它接受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;
    
        // ...
    
    }
    

    记得运行学说命令来生成 setBaseEntity 方法。当您这样做时,请注意它现在将接受从 Base 派生的任何类

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

    $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
    }
    

    更新:

    这些Entities之间的继承关系可以用Doctrine在数据库中建模,使用Single Table Inheritance:

    /**
     * @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"})
     */
    

    【讨论】:

    • 感谢您的回答亚历克斯。我更新了我的问题,你的意见是什么?
    • 我认为,如果您正确地对实体进行编程,您将不需要在 get 和 set 方法中使用复杂的代码。在接下来的 36 小时内,我将离开我的电脑,但我会在我回来时发布一段非常简化的代码,它应该可以解决你的问题
    • 再次 tnx 回答,对于基类和其他实体之间的关系,我应该使用这样的东西吗?:docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/…
    猜你喜欢
    • 2018-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-04
    • 2014-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多