Symfony 表单多对一一对多
Symfony Form ManyToOne OneToMany
我有三个实体,Block、BlockPlacement、BlockPosition:
class BlockEntity
{
private $bid;
/**
* @ORM\OneToMany(
* targetEntity="BlockPlacementEntity",
* mappedBy="block",
* cascade={"remove"})
*/
private $placements;
}
class BlockPlacementEntity
{
/**
* The id of the block postion
*
* @ORM\Id
* @ORM\ManyToOne(targetEntity="BlockPositionEntity", inversedBy="placements")
* @ORM\JoinColumn(name="pid", referencedColumnName="pid", nullable=false)
*/
private $position;
/**
* The id of the block
*
* @var BlockEntity
* @ORM\Id
* @ORM\ManyToOne(targetEntity="BlockEntity", inversedBy="placements")
* @ORM\JoinColumn(name="bid", referencedColumnName="bid", nullable=false)
*/
private $block;
private $sortorder;
}
class BlockPositionEntity
{
private $pid;
/**
* @ORM\OneToMany(
* targetEntity="BlockPlacementEntity",
* mappedBy="position",
* cascade={"remove"})
* @ORM\OrderBy({"sortorder" = "ASC"})
*/
private $placements;
}
所以,你可以看到关系:Block < OneToMany > Placement < ManyToOne > Position。
现在我正在尝试为 create/edit 块构造一个表单:
$builder
->add($builder->create('placements', 'entity', [
'class' => 'Zikula\BlocksModule\Entity\BlockPositionEntity',
'choice_label' => 'name',
'multiple' => true,
'required' => false
]))
;
这给了我一个很好的 select 盒子,里面有多个 selection 可能有一个合适的位置列表可供选择。但它不显示以前的 select 放置离子(我使用现有数据)例如将位置标记为 'selected'。我还没有尝试创建新块,只是编辑现有数据。
我怀疑我需要使用 addModelTransformer()
或 addViewTransformer()
,但已经尝试了其中一些,但无法正常工作。
我查看了 collection
表单类型,但我不喜欢该解决方案,因为它不是多 select 框。它需要 JS,并且不像简单的 select 元素那样直观。
这对人们来说似乎是一个普遍的问题。我搜索过,没有找到共同的答案,也没有任何帮助。
更新:请看这个example repo
更新 2: 我已经更新了 repo。
我用表单事件侦听器和未映射的选择字段完成了它。
仔细看看 BlockType form type
如有任何问题,请随时提出。
好的 - 最后,我找到了不同的方法。 @Stepan Yudin 的回答有效,但很复杂(听众等)并且不像我希望的那样。
所以,我有相同的三个实体。 BlockPlacement 和 BlockPosition 保持不变(因此没有重新post编辑,见上文)但我对 BlockEntity 做了一些更改:
class BlockEntity
{
private $bid;
/**
* @ORM\OneToMany(
* targetEntity="BlockPlacementEntity",
* mappedBy="block",
* cascade={"remove", "persist"},
* orphanRemoval=true)
*/
private $placements;
/**
* Get an ArrayCollection of BlockPositionEntity that are assigned to this Block
* @return ArrayCollection
*/
public function getPositions()
{
$positions = new ArrayCollection();
foreach($this->getPlacements() as $placement) {
$positions->add($placement->getPosition());
}
return $positions;
}
/**
* Set BlockPlacementsEntity from provided ArrayCollection of positionEntity
* requires
* cascade={"remove, "persist"}
* orphanRemoval=true
* on the association of $this->placements
* @param ArrayCollection $positions
*/
public function setPositions(ArrayCollection $positions)
{
// remove placements and skip existing placements.
foreach ($this->placements as $placement) {
if (!$positions->contains($placement->getPosition())) {
$this->placements->removeElement($placement);
} else {
$positions->removeElement($placement->getPosition()); // remove from positions to add.
}
}
// add new placements
foreach ($positions as $position) {
$placement = new BlockPlacementEntity();
$placement->setPosition($position);
// sortorder is irrelevant at this stage.
$placement->setBlock($this); // auto-adds placement
}
}
}
因此您可以看到 BlockEntity 现在正在处理实体中根本不存在的 positions
参数。这是相关的表单组件:
$builder
->add('positions', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', [
'class' => 'Zikula\BlocksModule\Entity\BlockPositionEntity',
'choice_label' => 'name',
'multiple' => true,
])
请注意,自从我第一次使用 post
以来,我已更改为 Symfony 2.8 表单样式
这会在页面上呈现多个 select 元素,该元素接受任意数量的位置并在提交时将它们转换为 ArrayCollection。然后由表单的 get/set 位置方法直接处理,这些方法将 to/from 位置转换为 属性。 cascade
和 orphanRemoval
很重要,因为它们会照顾 'clean up' 剩余的实体。
因为上面引用的是BlockPlacementsetBlock($block)
方法:
public function setBlock(BlockEntity $block = null)
{
if ($this->block !== null) {
$this->block->removePlacement($this);
}
if ($block !== null) {
$block->addPlacement($this);
}
$this->block = $block;
return $this;
}
我有三个实体,Block、BlockPlacement、BlockPosition:
class BlockEntity
{
private $bid;
/**
* @ORM\OneToMany(
* targetEntity="BlockPlacementEntity",
* mappedBy="block",
* cascade={"remove"})
*/
private $placements;
}
class BlockPlacementEntity
{
/**
* The id of the block postion
*
* @ORM\Id
* @ORM\ManyToOne(targetEntity="BlockPositionEntity", inversedBy="placements")
* @ORM\JoinColumn(name="pid", referencedColumnName="pid", nullable=false)
*/
private $position;
/**
* The id of the block
*
* @var BlockEntity
* @ORM\Id
* @ORM\ManyToOne(targetEntity="BlockEntity", inversedBy="placements")
* @ORM\JoinColumn(name="bid", referencedColumnName="bid", nullable=false)
*/
private $block;
private $sortorder;
}
class BlockPositionEntity
{
private $pid;
/**
* @ORM\OneToMany(
* targetEntity="BlockPlacementEntity",
* mappedBy="position",
* cascade={"remove"})
* @ORM\OrderBy({"sortorder" = "ASC"})
*/
private $placements;
}
所以,你可以看到关系:Block < OneToMany > Placement < ManyToOne > Position。
现在我正在尝试为 create/edit 块构造一个表单:
$builder
->add($builder->create('placements', 'entity', [
'class' => 'Zikula\BlocksModule\Entity\BlockPositionEntity',
'choice_label' => 'name',
'multiple' => true,
'required' => false
]))
;
这给了我一个很好的 select 盒子,里面有多个 selection 可能有一个合适的位置列表可供选择。但它不显示以前的 select 放置离子(我使用现有数据)例如将位置标记为 'selected'。我还没有尝试创建新块,只是编辑现有数据。
我怀疑我需要使用 addModelTransformer()
或 addViewTransformer()
,但已经尝试了其中一些,但无法正常工作。
我查看了 collection
表单类型,但我不喜欢该解决方案,因为它不是多 select 框。它需要 JS,并且不像简单的 select 元素那样直观。
这对人们来说似乎是一个普遍的问题。我搜索过,没有找到共同的答案,也没有任何帮助。
更新:请看这个example repo
更新 2: 我已经更新了 repo。
我用表单事件侦听器和未映射的选择字段完成了它。 仔细看看 BlockType form type 如有任何问题,请随时提出。
好的 - 最后,我找到了不同的方法。 @Stepan Yudin 的回答有效,但很复杂(听众等)并且不像我希望的那样。
所以,我有相同的三个实体。 BlockPlacement 和 BlockPosition 保持不变(因此没有重新post编辑,见上文)但我对 BlockEntity 做了一些更改:
class BlockEntity
{
private $bid;
/**
* @ORM\OneToMany(
* targetEntity="BlockPlacementEntity",
* mappedBy="block",
* cascade={"remove", "persist"},
* orphanRemoval=true)
*/
private $placements;
/**
* Get an ArrayCollection of BlockPositionEntity that are assigned to this Block
* @return ArrayCollection
*/
public function getPositions()
{
$positions = new ArrayCollection();
foreach($this->getPlacements() as $placement) {
$positions->add($placement->getPosition());
}
return $positions;
}
/**
* Set BlockPlacementsEntity from provided ArrayCollection of positionEntity
* requires
* cascade={"remove, "persist"}
* orphanRemoval=true
* on the association of $this->placements
* @param ArrayCollection $positions
*/
public function setPositions(ArrayCollection $positions)
{
// remove placements and skip existing placements.
foreach ($this->placements as $placement) {
if (!$positions->contains($placement->getPosition())) {
$this->placements->removeElement($placement);
} else {
$positions->removeElement($placement->getPosition()); // remove from positions to add.
}
}
// add new placements
foreach ($positions as $position) {
$placement = new BlockPlacementEntity();
$placement->setPosition($position);
// sortorder is irrelevant at this stage.
$placement->setBlock($this); // auto-adds placement
}
}
}
因此您可以看到 BlockEntity 现在正在处理实体中根本不存在的 positions
参数。这是相关的表单组件:
$builder
->add('positions', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', [
'class' => 'Zikula\BlocksModule\Entity\BlockPositionEntity',
'choice_label' => 'name',
'multiple' => true,
])
请注意,自从我第一次使用 post
以来,我已更改为 Symfony 2.8 表单样式这会在页面上呈现多个 select 元素,该元素接受任意数量的位置并在提交时将它们转换为 ArrayCollection。然后由表单的 get/set 位置方法直接处理,这些方法将 to/from 位置转换为 属性。 cascade
和 orphanRemoval
很重要,因为它们会照顾 'clean up' 剩余的实体。
因为上面引用的是BlockPlacementsetBlock($block)
方法:
public function setBlock(BlockEntity $block = null)
{
if ($this->block !== null) {
$this->block->removePlacement($this);
}
if ($block !== null) {
$block->addPlacement($this);
}
$this->block = $block;
return $this;
}