Есть три сущности, связанные отношениями ManyToOne: «Страница» может иметь много «Блоков». (Блок может быть связан с одной страницей). «Блок» может иметь много «кнопок», («кнопка» может находиться в одном блоке).
У меня есть система форм, в которой я могу добавить несколько блоков, а внутри каждого блока - несколько кнопок. Таким образом, есть встроенная форма внутри встроенного для (2 слоя)
Проблема заключается в том, что при сохранении нескольких файлов в базу данных сохраняется только одна кнопка.
Но когда я сохраняю одну кнопку Я могу go вернуться и сохранить несколько сразу. Я не могу понять это поведение.
/**
* @ORM\Entity(repositoryClass="App\Repository\PageRepository")
*/
class Page
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
...
/**
* @ORM\OneToMany(targetEntity="App\Entity\Block", mappedBy="page", orphanRemoval=true, cascade={"persist"})
* @ORM\OrderBy({"ordering" = "ASC"})
*/
private $blocks;
...
public function __construct()
{
$this->blocks = new ArrayCollection();
}
/**
* @return Collection|Block[]
*/
public function getBlocks(): Collection
{
return $this->blocks;
}
public function addBlock(Block $block): self
{
if (!$this->blocks->contains($block)) {
$this->blocks[] = $block;
$block->setPage($this);
}
return $this;
}
public function removeBlock(Block $block): self
{
if ($this->blocks->contains($block)) {
$this->blocks->removeElement($block);
// set the owning side to null (unless already changed)
if ($block->getPage() === $this) {
$block->setPage(null);
}
}
return $this;
}
}
Блок:
/**
* @ORM\Entity(repositoryClass="App\Repository\BlockRepository")
*/
class Block
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\OneToMany(targetEntity="App\Entity\Block\Button", mappedBy="block",
orphanRemoval=true, cascade={"persist"})
*/
private $buttons;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Page", inversedBy="blocks")
* @ORM\JoinColumn(nullable=false)
*/
private $page;
public function __construct()
{
$this->buttons = new ArrayCollection();
}
/**
* @return Collection|Button[]
*/
public function getButtons(): Collection
{
return $this->buttons;
}
public function addButton(Button $button): self
{
if (!$this->buttons->contains($button)) {
$this->buttons[] = $button;
$button->setBlock($this);
}
return $this;
}
public function removeButton(Button $button): self
{
if ($this->buttons->contains($button)) {
$this->buttons->removeElement($button);
// set the owning side to null (unless already changed)
if ($button->getBlock() === $this) {
$button->setBlock(null);
}
}
return $this;
}
}
Кнопка:
/**
* @ORM\Entity(repositoryClass="App\Repository\Block\ButtonRepository")
* @ORM\Table(name="block_button")
*/
class Button
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Block", inversedBy="buttons")
* @ORM\JoinColumn(nullable=false)
*/
private $block;
/**
* @ORM\Column(type="string", length=255)
*/
private $label;
public function getBlock(): ?Block
{
return $this->block;
}
public function setBlock(?Block $block): self
{
$this->block = $block;
return $this;
}
}
Ниже приведены формы, соответствующие типам форм:
PageType:
class PageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('blocks', CollectionType::class, [
'entry_type' => BlockType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'help' => '<a data-collection="add" class="btn btn-info btn-sm" href="#">Add Block</a>',
'help_html' => true
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Page::class,
]);
}
}
BlockType
class BlockType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('buttons', CollectionType::class, [
'entry_type' => BlockButtonType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'help' => '<a data-collection="add" class="btn btn-info btn-sm" href="#">Add Button</a>',
'help_html' => true,
'attr' => [
'data-field' => 'buttons'
]
])
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Block::class,
]);
}
}
BlockButtonType
class BlockButtonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('label');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Button::class,
]);
}
}
Ответственные методы для обработки новой формы и измените их следующим образом:
/**
* @Route("/admin/pages")
*/
class PageController extends AbstractController
{
/**
* @Route("/new", name="admin_page_new", methods={"GET","POST"})
*/
public function new(Request $request, FileUploader $fileUploader):Response
{
$page = new Page();
$form = $this->createForm(PageType::class, $page);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($form->get('publish')->isClicked()) {
$page->setPublished(true);
}
// handle uploads here
$this->handleUploads($fileUploader, $page, $form);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($page);
$entityManager->flush();
if ($form->get('close')->isClicked()) {
return $this->redirectToRoute('admin_page_index');
}
$this->addFlash('success', 'Page saved');
return $this->redirectToRoute('admin_page_edit', [
'id' => $page->getId(),
]);
}
return $this->render('admin/page/new.html.twig', [
'page' => $page,
'form' => $form->createView(),
]);
}
/**
* @Route("/{id}/edit", name="admin_page_edit", methods={"GET","POST"})
*/
public function edit(Request $request, FileUploader $fileUploader, Page $page): Response
{
$form = $this->createForm(PageType::class, $page);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($page->getParent() != null && $page->getParent()->getId() == $page->getId()) {
$page->setParent(null);
$this->addFlash('error', 'You cannot set a page to be a parent of itself');
return $this->render('admin/page/edit.html.twig', [
'page' => $page,
'form' => $form->createView(),
]);
}
if ($form->get('publish')->isClicked()) {
$page->setPublished(true);
}
// get all blocks and check if anything was uploaded?
$this->handleUploads($fileUploader, $page, $form);
$this->getDoctrine()->getManager()->flush();
if ($form->get('close')->isClicked()) {
return $this->redirectToRoute('admin_page_index');
}
$this->addFlash('success', 'Page saved');
return $this->redirectToRoute('admin_page_edit', [
'id' => $page->getId(),
]);
}
}
Наконец, веточка html, которая отображает форму:
{% extends 'admin/layouts/admin.html.twig' %}
{% block body %}
{{ form_start(form) }}
<div class="tab-content">
<div class="tab-pane show active" id="details">
{{ form_row(form.blocks) }}
</div>
</div>
{{ form_widget(form.save) }}
{{ form_widget(form.close) }}
{{ form_widget(form.publish) }}
{{ form_rest(form) }}
{{ form_end(form) }}
{% endblock %}