Symfony2 Doctrine 多对多关系与两个拥有方和 Doctrine cmd 行工具

标签 symfony many-to-many doctrine-orm

在我的 Symdony2 项目中,我有两个相关的实体:Service 和 ServiceGroup。这应该是多对多的关系,因为每个组可以有多个服务,每个服务又可以属于多个组。此外,我需要一个用户界面来管理服务和组。因此,在编辑服务时,用户应该能够选择它所属的组。类似地,在编辑 ServiceGroup 时,用户应该能够选择哪些服务属于该组。我已经通过在我的 Doctrine 实体中设置多对多关系来实现这一点。一切都像魅力一样工作,包括基于 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,因为这是我发现在用户界面编辑器中保存关系的唯一方法,如下所示:
服务编辑:

Service editor

Name: [ Service 1 ]

Groups to which this service belongs:

[x] Group A

[ ] Group B

[ ] Group C

[ SAVE ]


和 ServiceGroup 编辑器:

Group editor

Name: [ Group A ]

Services belongs to this group:

[x] Service 1

[ ] Service 2

[ ] Service 3

[ SAVE ]


有了这个多对多配置,我可以毫无问题地使用这个编辑器(表单),当使用没有 JoinTable 注释的多对多时,我只能完全使用一个表单,第二个是不保存“此服务所属的组”或“服务属于此组”选项中的更改(取决于我在多对多注释语句中设置 mappingBy 和 inversedBy 参数的方向)。
当尝试使用 Symfony2 命令更新模式时,我遇到的问题与学说模式生成机制有关:
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 错误/限制。任何帮助或建议将不胜感激。
更新:
我注意到我需要的是将 ManyToMany 关系配置为具有两个拥有方。默认是有一个拥有方和一个相反方。教义documentation告诉你在多对多关系中可以有两个拥有方,但没有解释太多。任何人都可以举个例子吗?
解决方法:
我找到了一个变通的解决方案,这可能并不理想,但它对我有用。
由于无法在多对多关系中拥有两个拥有方,因此我更改了我的实体的 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) 端建立了多对多关系,以及在保存时更新实体和关系的表单(服务实体的默认表单就足够了,但对于我在上面提供的 ServiceGroup)修改)。 Symfony/Doctrine 控制台工具就像一个魅力。这可能可以以更好(更简单?)的方式解决,但对我来说这现在就足够了。

最佳答案

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

在你的情况下,你有 mappedBy="groups"$services ServiceGroup 的属性(property)类(class)。这意味着,Service 和ServiceGroup 之间的这种关联由$groups 维护。 Service 的属性(property)实体。所以,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);
    }
}

注意事项
  • 确保您初始化了 $services$serviceGroups到实体创建时的学说集合,即在构造函数中。
  • 您需要拥有 'by_reference' => false在您的 services ServiceGroup 的 FormType 中的字段.

  • 引用:
    http://symfony.com/doc/2.8/cookbook/form/form_collections.html

    关于Symfony2 Doctrine 多对多关系与两个拥有方和 Doctrine cmd 行工具,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9582547/

    相关文章:

    objective-c - CoreData - NSPredicate 与 NSSet

    php - Symfony 3.2 "security.firewall.map.context.main"依赖于不存在的服务^我的身份验证处理程序^

    symfony - Doctrine ORM 通过标签列表选择文章

    sql - 选择匹配多个标签的项目

    symfony - Doctrine 2.5 : Unrecognized field (but only in Symfony's prod mode)

    php - Doctrine 的 Symfony Bundle repo 发生了什么?

    orm - 在 Doctrine 2 中,可以在运行时更改获取模式(Eager/Lazy 等)吗?

    php - symfony2 flash 消息作为事件监听器来处理错误

    symfony - 使用 Symfony 2 Forms 验证 API 输入

    php - Symfony 4,从 Controller 获取.env参数,这可能吗?如何?