Symfony2 Doctrine2与两个Owning Sides和Doctrine cmd行工具的多对多关系

时间:2012-03-06 11:15:52

标签: symfony many-to-many doctrine-orm

在我的Symdony2项目中,我有两个相关的实体:Service和ServiceGroup。这应该是多对多关系,因为每个组可以有许多服务,并且每个服务可以属于许多组。而且,我需要一个用户界面来管理服务和组。因此,在编辑服务时,用户应该能够选择它所属的组。类似地,在编辑ServiceGroup时,用户应该能够选择属于该组的服务。我已经通过在我的学说中建立多对多关系来实现这一目标。一切都像魅力一样,包括在Symfony2中构建自定义表单类型的用户界面(我使用“实体”表单字段类型,以允许用户在ServiceGroup编辑器和服务编辑器中的组中选择服务)。我唯一的问题是我不能再使用Doctrine命令行来更新数据库模式。

以下是我的服务实体源代码的一部分:

class Service
{
    /**
     * @var ArrayCollection $groups
     * @ORM\ManyToMany(targetEntity="ServiceGroup")
     * @ORM\JoinTable(
     *      name="service_servicegroup",
     *      joinColumns={@ORM\JoinColumn(name="service_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="servicegroup_id", referencedColumnName="id")}
     * )
     */
    private $groups;
}

这是我的ServiceGroup实体源代码的一部分:

class ServiceGroup
{
    /**
     * @var ArrayCollection $services
     * @ORM\ManyToMany(targetEntity="Service")
     * @ORM\JoinTable(
     *      name="service_servicegroup",
     *      joinColumns={@ORM\JoinColumn(name="servicegroup_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="service_id", referencedColumnName="id")}
     * )
     */
    private $services;
}

我在两种情况下都使用JoinTable,因为这是我在用户界面编辑器中保存关系时找到工作的唯一方法,如下所示:

服务编辑:

  

服务编辑器

     

姓名:[服务1]

     

此服务所属的群组:

     

[x] A组

     

[] B组

     

[] C组

     

[保存]

和ServiceGroup编辑:

  

群组编辑

     

姓名:[A组]

     

服务属于这个群组:

     

[x]服务1

     

[]服务2

     

[]服务3

     

[保存]

使用这种多对多配置我能够毫无问题地使用这些编辑器(表单),当使用没有JoinTable注释的多对多时,我只能完全使用一个表单,并且第二个不保存“此服务所属的组”或“服务属于此组”选项中的更改(取决于我在多对多注释语句中设置mappedBy和inversedBy参数的方向)。

当尝试使用Symfony2命令更新架构时,我遇到的问题与doctrine架构生成机制有关:

php app/console doctrine:schema:update --dump-sql

我遇到了这个例外:

[Doctrine\DBAL\Schema\SchemaException]                      
The table with name 'service_servicegroup' already exists.

看起来Doctrine试图为每个JoinTable语句创建“service_servicegroup”表。因此,它正在使用当前模式,我使用相同的命令在数据库中构建,但是一步一步,首先没有定义多对多关系,接下来只有一个多对多关系定义(对于服务实体)。当我向第二个实体(ServiceGroup)添加了“多对多”关系时,我的应用程序从用户角度看起来没有问题,但是我不能再使用“doctrine:schema:update”命令

我不知道我的代码有什么问题,也许这种关系应该以不同的方式实现,或者它可能是一个Doctrine bug /限制。任何帮助或建议将不胜感激。

更新

我注意到我需要的是将ManyToMany关系配置为拥有两个拥有方。默认是拥有一个拥有方和一个反方。 Doctrine documentation告诉你在ManyToMany关系中可以拥有两个拥有者,但不会解释很多。任何人都可以举个例子吗?

解决方法:

我找到了一种解决方案,可能并不理想,但它对我有用。 由于没有办法在多对多关系中拥有两个拥有的边,我已经为我的entites改变了Doctrine注释。服务实体现在是拥有方:

class Service
{
    /**
     * @var ArrayCollection $groups
     * @ORM\ManyToMany(targetEntity="ServiceGroup", inversedBy="services", cascade={"all"})
     * @ORM\JoinTable(
     *      name="service_to_group_assoc",
     *      joinColumns={@ORM\JoinColumn(name="service_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="group_id", referencedColumnName="id")}
     * )
     */
    private $groups;
}

ServiceGroup实体是反面的:

class ServiceGroup
{
    /**
     * @var ArrayCollection $services
     * @ORM\ManyToMany(targetEntity="Service", mappedBy="groups", cascade={"all"})
     */
    private $services;
}

不幸的是,使用此配置,仅在更新服务实体时更新关系。当我在ServiceGroup对象中更改$ services并保持它时,关系将保持不变。所以,我改变了我的Controller类,并借助小型解决方案解决方案,实现了预期的结果。这是我的Controller代码的一部分,它负责更新ServiceGroup实体(使用自定义表单类型):

// before update - get copy of currently related services:
$services = clone $group->getServices();

// ...

// when $form->isValid() etc. updating the ServiceGroup entity:

// add selected services to group
foreach($group->getServices() as $service)
{
    $service->addServiceGroup($group);
    $services->removeElement($service);
}

// remove unselected services from group
foreach($services as $service)
{
    $service->removeServiceGroup($group);
}

这是Service实体类的addServiceGroup和removeServiceGroup方法的实现:

/**
 * Add group
 *
 * @param ServiceGroup $groups
 */
public function addServiceGroup(ServiceGroup $groups)
{
    if(!in_array($groups, $this->groups->toArray()))
    {
        $this->groups[] = $groups;
    }
}

/**
 * Remove group
 *
 * @param ServiceGroup $groups
 */
public function removeServiceGroup(ServiceGroup $groups)
{
    $key = $this->groups->indexOf($groups);

    if($key!==FALSE) 
    {
        $this->groups->remove($key);
    }
}

现在我与拥有(Service)和反向(ServiceGroup)方面有多对多的关系,并且在保存时更新了实体和关系的表单(Service实体的默认表单已经足够,但对于ServiceGroup我来说)提供上述修改)。 Symfony / Doctrine控制台工具就像魅力一样。这可能是以更好(更简单?)的方式解决的,但对我来说这已经足够了。

4 个答案:

答案 0 :(得分:1)

多对多关联的默认行为将具有拥有方和反方。如果您正在处理该关联的拥有方,那么该关联将由ORM处理。但是,如果您正在处理反面,则由您明确处理此问题。

在您的情况下,mappedBy="groups"类的$services属性中有ServiceGroup。这意味着,Service和ServiceGroup之间的这种关联由$groups实体的Service属性维护。因此,Service成为此关联的拥有方。

现在,假设您正在创建或更新Service实体。当您向此ServiceGroup实体添加Service个实体并保留Service实体时,ORM会自动构建所有实体和相关关联。

但是,如果您要创建或更新ServiceGroup实体,当您向其添加Service个实体并保留ServiceGroup实体时,该关联由ORM构建。此处作为解决方法,您必须将ServiceGroup实体明确添加到Service实体。

// in ServiceGroup.php

public function addService(Service $service)
{
    $service->addServiceGroup($this);
    $this->services->add($service);
}

// in Service.php
public function addServiceGroup(ServiceGroup $group)
{
    if (!$this->serviceGroups->contains($group)) {
        $this->serviceGroups->add($group);
    }
}

要记住的事情

  1. 确保在创建实体时(即在构造函数中)初始化$services$serviceGroups到学说集合。
  2. 您需要在'by_reference' => false的FormType的services字段中添加ServiceGroup
  3. 参考: http://symfony.com/doc/2.8/cookbook/form/form_collections.html

答案 1 :(得分:0)

确保拥有方(服务)始终更新的示例。

class ServiceGroup
{
    public function addService($service)
    {
        if (!$service) return;

        $this->services[] = $service;
        $service->addGroup($this);
    }
}

答案 2 :(得分:0)

您是否尝试过使用中间实体和多对一协会进行编码?

虽然它可能不是您想要的,但它可能提供有关如何使其与多对多工作的见解。

(并且经常你最终意识到中间实体应该因为关联的某些属性而在那里。)

答案 3 :(得分:-1)