我有一个Symfony表单,其中包含一个如下定义的集合:
<?php declare(strict_types=1);
namespace App\Form;
use App\Entity\Documents;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DocumentsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'documents',
CollectionType::class,
[
'entry_type' => DocumentType::class,
'by_reference' => false,
'entry_options' => [
'label' => false,
],
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'attr' => [
'class' => 'documents-collection',
'data-min-items' => 1,
],
'required' => true,
]
);
parent::buildForm($builder, $options);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => Documents::class,
]
);
}
}
和DocumentType这样:
<?php declare(strict_types=1);
namespace App\Form;
use App\Entity\Document;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DocumentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'description',
TextType::class,
[
'required' => true,
'attr' => [
'placeholder' => 'Document description, eg: Ticket, receipt, itinerary, map, etc…',
],
]
)
->add(
'document',
FileType::class,
[
'mapped' => false,
'required' => true,
]
);
parent::buildForm($builder, $options);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => Document::class,
]
);
}
}
文档实体为:
<?php declare(strict_types=1);
namespace App\Entity;
use App\Service\Uuid;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Documents
{
/**
* @ORM\Column(type="uuid")
* @ORM\GeneratedValue(strategy="UUID")
* @ORM\Id
*/
private $id;
/**
* @ORM\ManyToMany(
* targetEntity="Document",
* cascade={"persist", "remove"},
* orphanRemoval=true
* )
* @ORM\JoinTable(
* name="documents_document",
* joinColumns={
* @ORM\JoinColumn(name="documents_id", referencedColumnName="id"),
* },
* inverseJoinColumns={
* @ORM\JoinColumn(name="document_id", referencedColumnName="id", unique=true),
* }
* )
* @var Document[]
*/
private $documents;
public function __construct()
{
$this->id = Uuid::uuid4();
$this->documents = new ArrayCollection();
}
/**
* @return mixed
*/
public function getId()
{
return $this->id;
}
/**
* @return Collection
*/
public function getDocuments(): Collection
{
return $this->documents;
}
/**
* @param Document $document
*
* @return $this
*/
public function addDocument(Document $document): Documents
{
if (!$this->documents->contains($document)) {
$this->documents->add($document);
$document->setDocuments($this);
}
return $this;
}
/**
* @param Document $document
*
* @return bool
*/
public function hasDocument(Document $document): bool
{
return $this->documents->contains($document);
}
/**
* @param Document $document
*
* @return $this
*/
public function removeDocument(Document $document): Documents
{
if ($this->documents->contains($document)) {
$this->documents->removeElement($document);
}
return $this;
}
/**
* @param Collection $documents
*
* @return $this
*/
public function setDocuments(Collection $documents): Documents
{
$this->documents = $documents;
return $this;
}
/**
* @return $this
*/
public function clearDocuments(): Documents
{
$this->documents = new ArrayCollection();
return $this;
}
}
文档实体为:
<?php declare(strict_types=1);
namespace App\Entity;
use App\Service\Uuid;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Document
{
/**
* @var Uuid|string
* @ORM\Column(type="uuid")
* @ORM\GeneratedValue(strategy="UUID")
* @ORM\Id
*/
private $id;
/**
* @var Documents
* @ORM\ManyToOne(targetEntity="Documents")
*/
private $documents;
/**
* @var string
* @ORM\Column(type="string", length=1024, nullable=false)
*/
private $description;
public function __construct()
{
$this->id = Uuid::uuid4();
}
/**
* @return Uuid|string
*/
public function getId()
{
return $this->id;
}
/**
* @return Documents
*/
public function getDocuments(): Documents
{
return $this->documents;
}
/**
* @param Documents $documents
*
* @return $this
*/
public function setDocuments(Documents $documents): Document
{
$this->documents = $documents;
return $this;
}
/**
* @return string
*/
public function getDescription(): ?string
{
return $this->description;
}
/**
* @param string $description
*
* @return $this
*/
public function setDescription(string $description): Document
{
$this->description = $description;
return $this;
}
}
我在控制器中像这样创建表单:
$repo = $entityManager->getRepository(Documents::class);
$documents = $repo->findOneBy(['id' => $id]);
$form = $this->formFactory->create(
DocumentsType::class,
$documents
);
当我在呈现的表单中将新的Document条目添加到集合中,然后保存表单时,它们将正确保存到数据库并链接到Documents实体。
如果我删除了集合中的最后一个条目,那么它将被正确地从$ documents集合中删除,然后再从documents表中删除,因为不再有对其的引用。
但是,如果我在集合中间删除一个条目,Doctrine会从已删除条目及其追随者的其余条目中保存数据,然后删除列表中的最后一个实体,从而更改所有实体。
我正在使用UUID作为新文件名保存在document
的{{1}}字段中上传的文件,因此从集合中删除条目时,id必须保持相同。我尝试将一个已映射和未映射的id字段都添加到该集合中,但是未映射的字段将被完全忽略,并且该映射的字段将允许用户修改id列中的数据,因此不适合在此使用。>
我需要做些什么来修改此表单以使Doctrine保持集合中的数据与其在数据库中表示的实体之间的联系?
答案 0 :(得分:1)
因此,在回购的错误跟踪器中发现this issue具有类似行为后,the last linked issue指出了我的自述部分:
请勿更改字段名称
Symfony使用字段名称来排序集合,而不是位置 dom上的每个元素。因此,默认情况下,如果您删除 在中间,以下所有元素的索引都会减少 1(
field[3]
将变成field[2]
,依此类推),如果您添加一些 元素在中间,所有后续元素都将看到其索引 增加以留出新的空间。使用此实现,您可以确保在 单击“上移”和“下移”以获取示例。但是在一些 在这种情况下,您可能不想覆盖索引,很可能是 维持教义关系。
将
preserve_names
选项设置为true
,以永不触摸字段名称。但 请注意,此选项将禁用allow_up
,allow_down
,drag_drop
选项,并将add_at_the_end
强制为true。默认值:
$('.collection').collection({ preserve_names: false });
因此解决方案应该是使用选项preserve_names
设置为true
而不是默认值false
来初始化集合。
$('.collection').collection({
preserve_names: true // this is our fix
});