Symfony5:自引用多对多的形式
Symfony5: form for self-referencing many-to-many
在 Symfony5 应用程序中,我们如何利用表单组件正确设置自引用、多对多关系?
注意:这纯粹是形式方面的事情,Doctrine已经很高兴了。
假设我们有一个“人”实体,这些人实例托管客人,他们也是人:
#[Entity(repositoryClass: PersonRepository::class)]
class Person
{
#[Id]
#[GeneratedValue]
#[Column(type: Types::INTEGER)]
private int $id;
#[Column(type: Types::STRING, length: 255)]
private string $firstName;
#[Column(type: Types::STRING, length: 255)]
private string $lastName;
/** @var Collection<Person> */
#[ManyToMany(targetEntity: self::class, inversedBy: 'guestsHosted')]
private Collection $hosts;
/** @var Collection<Person> */
#[ManyToMany(targetEntity: self::class, mappedBy: 'hosts')]
private Collection $guestsHosted;
public function __construct()
{
$this->fellowshipHosts = new ArrayCollection();
$this->fellowsHosted = new ArrayCollection();
}
// getters/setters ommitted for brevity
public function addHost(self $host): self
{
}
public function removeHost(self $host): self
{
}
public function addGuestsHosted(self $guestHosted): self
{
}
public function removeGuestsHosted(self $guestsHosted): self
{
}
我想要实现的是一个包含 addable/removable 行集合的表单,用户可以在其中添加一行,select 现有的其他人作为他们的主持人,添加另一行,select 另一个人,依此类推。一个简单的模型:
我如何使用 Symfony 的表单组件实现这一点?由于相同表单类型的递归,我现在在那里的内容不起作用:
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('firstName', null, [])
->add('lastName', null, [])
->add('hosts', CollectionType::class, [
'entry_type' => self::class,
'entry_options' => ['label' => false],
'required' => false,
'allow_delete' => true,
'allow_add' => true,
'by_reference' => false,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Person::class,
]);
}
}
注意:我过去构建过类似的 UI,并且知道涉及 form.vars.prototype
的小舞蹈并将 JS 连接到它。我迷路的地方是如何在表单级别映射此关联,以便我获得所有人员的下拉列表,并将其映射到实体的 $hosts
属性。
@Bossman 的评论非常正确,但以防万一你想用单一的表单类型来做,我会做这样的事情:
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('firstName', null, [])
->add('lastName', null, []);
if (!$options['is_host']) {
->add('hosts', CollectionType::class, [
'entry_type' => self::class,
'entry_options' => ['label' => false, 'is_host' => true],
'required' => false,
'allow_delete' => true,
'allow_add' => true,
'by_reference' => false,
]);
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Person::class,
'is_host' => false,
]);
}
}
基本上我在这里做的是通过明确地将 'is_host' 传递给表单选项来防止递归,这表明这是否是一个子实体,然后我正在构建 'hosts' 表单仅适用于根实体的字段。
如果您想在现有主机中 select,请使用 EntityType:
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// You'll be able to use this later in the 'query_builder' option
$personId = $options['person_id'];
$builder
->add('firstName', null, [])
->add('lastName', null, [])
->add('hosts', CollectionType::class, [
'entry_type' => EntityType::class,
'entry_options' => ['label' => false, 'class' => Person::class],
'required' => false,
'allow_delete' => true,
'allow_add' => true,
'by_reference' => false,
])
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Person::class,
]);
$resolver->setAllowedTypes('person_id', ['int', 'null']);
}
}
记住以下几点很重要:
您必须将当前人员 ID 作为表单的 person_id
选项传递(或对于新条目为 null)。如果需要,您也可以从 $builder 变量中检索此值。
根据您的需要修改 EntityType 选项中的 'query_builder',例如不要从主机列表等中排除当前(root)人
这种方法 可能 会导致多个数据库查询(每个集合项 1 个),但我现在不确定。如果是这样,你应该自己想办法解决这个问题。
在 Symfony5 应用程序中,我们如何利用表单组件正确设置自引用、多对多关系?
注意:这纯粹是形式方面的事情,Doctrine已经很高兴了。
假设我们有一个“人”实体,这些人实例托管客人,他们也是人:
#[Entity(repositoryClass: PersonRepository::class)]
class Person
{
#[Id]
#[GeneratedValue]
#[Column(type: Types::INTEGER)]
private int $id;
#[Column(type: Types::STRING, length: 255)]
private string $firstName;
#[Column(type: Types::STRING, length: 255)]
private string $lastName;
/** @var Collection<Person> */
#[ManyToMany(targetEntity: self::class, inversedBy: 'guestsHosted')]
private Collection $hosts;
/** @var Collection<Person> */
#[ManyToMany(targetEntity: self::class, mappedBy: 'hosts')]
private Collection $guestsHosted;
public function __construct()
{
$this->fellowshipHosts = new ArrayCollection();
$this->fellowsHosted = new ArrayCollection();
}
// getters/setters ommitted for brevity
public function addHost(self $host): self
{
}
public function removeHost(self $host): self
{
}
public function addGuestsHosted(self $guestHosted): self
{
}
public function removeGuestsHosted(self $guestsHosted): self
{
}
我想要实现的是一个包含 addable/removable 行集合的表单,用户可以在其中添加一行,select 现有的其他人作为他们的主持人,添加另一行,select 另一个人,依此类推。一个简单的模型:
我如何使用 Symfony 的表单组件实现这一点?由于相同表单类型的递归,我现在在那里的内容不起作用:
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('firstName', null, [])
->add('lastName', null, [])
->add('hosts', CollectionType::class, [
'entry_type' => self::class,
'entry_options' => ['label' => false],
'required' => false,
'allow_delete' => true,
'allow_add' => true,
'by_reference' => false,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Person::class,
]);
}
}
注意:我过去构建过类似的 UI,并且知道涉及 form.vars.prototype
的小舞蹈并将 JS 连接到它。我迷路的地方是如何在表单级别映射此关联,以便我获得所有人员的下拉列表,并将其映射到实体的 $hosts
属性。
@Bossman 的评论非常正确,但以防万一你想用单一的表单类型来做,我会做这样的事情:
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('firstName', null, [])
->add('lastName', null, []);
if (!$options['is_host']) {
->add('hosts', CollectionType::class, [
'entry_type' => self::class,
'entry_options' => ['label' => false, 'is_host' => true],
'required' => false,
'allow_delete' => true,
'allow_add' => true,
'by_reference' => false,
]);
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Person::class,
'is_host' => false,
]);
}
}
基本上我在这里做的是通过明确地将 'is_host' 传递给表单选项来防止递归,这表明这是否是一个子实体,然后我正在构建 'hosts' 表单仅适用于根实体的字段。
如果您想在现有主机中 select,请使用 EntityType:
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// You'll be able to use this later in the 'query_builder' option
$personId = $options['person_id'];
$builder
->add('firstName', null, [])
->add('lastName', null, [])
->add('hosts', CollectionType::class, [
'entry_type' => EntityType::class,
'entry_options' => ['label' => false, 'class' => Person::class],
'required' => false,
'allow_delete' => true,
'allow_add' => true,
'by_reference' => false,
])
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Person::class,
]);
$resolver->setAllowedTypes('person_id', ['int', 'null']);
}
}
记住以下几点很重要:
您必须将当前人员 ID 作为表单的
person_id
选项传递(或对于新条目为 null)。如果需要,您也可以从 $builder 变量中检索此值。根据您的需要修改 EntityType 选项中的 'query_builder',例如不要从主机列表等中排除当前(root)人
这种方法 可能 会导致多个数据库查询(每个集合项 1 个),但我现在不确定。如果是这样,你应该自己想办法解决这个问题。