在我的 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/