symfony 5 更新表单时,子子实体元素被意外删除

问题描述 投票:0回答:1

我有 4 个实体:
文件夹、面板、类别、链接。

关系: 文件夹 [oneToMany] -> Boards [oneToMany] -> 类别 [oneToMany] -> 链接

我有一个递归 delete_Folder 路由,可以删除所有子元素(BoardsCategoriesLinks)ok
我有一个 create_Folder 路线,包括 addBoard(s)

我有一条路线 update_Folder 包括 addBoard(s)
我有一个 update_Board 路线,包括 addCategory(s)
我有一个 update_Category 路线,包括 addLink(s)

系统基本可以运行。 但是,如果我在 flush 期间在 root level 编辑 Folder 数据,则子子元素将被删除,而第一个子元素 (Board) 不受影响并被保留。
编辑Board时的机制是相同的,它保留较低的相邻级别,但删除下一个级别Link

文件夹 [操作]:update_Folder 将删除类别和链接,但不会删除面板
创建_文件夹确定
delete_Folder 子元素OK
Board [操作]:update_Board 将删除链接,但不会删除类别 KO
类别 链接

我认为我的实体有问题。
在我的 easy_admin 管理中,问题不存在,它工作完美。

我的控制器

<?php

declare(strict_types=1);

namespace App\Controller;

use App\Entity\Foo;
use App\Form\FooType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

#[Route('/folder', name: 'folder_')]
class FolderController extends AbstractController
{
    #[Route('/{id}/update', name: 'update')]
    public function update(Request $request, Foo $foo): Response
    {
        $form = $this->createForm(FooType::class, $foo)->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $this->getDoctrine()->getManager()->flush();
            $this->addFlash('success', 'update folder OK !');

            return $this->redirectToRoute('main_list');
        }

        return $this->render('foo/update.html.twig', [
            "form" => $form->createView()
        ]);
    }
}

文件夹实体

<?php

declare(strict_types=1);

namespace App\Entity;

use App\Repository\FooRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass=FooRepository::class)
 */
class Foo
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private ?int $id = null;

    /**
     * @ORM\Column(type="string", length=255)
     */
    #[Assert\NotBlank]
    private string $name;

    /**
     * @ORM\Column(type="text", nullable=true)
     */
    private $description;

    /**
     * @var Collection<int, Bar>
     * @ORM\OneToMany(targetEntity=Bar::class, mappedBy="foo", orphanRemoval=true, cascade={"persist"})
     */
//    #[Assert\Count(min: 1)]
    #[Assert\Valid]
    private Collection $bars;

    public function __construct()
    {
        $this->bars = new ArrayCollection();
    }

    public function __toString()
    {
        return $this->name;
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(?string $description): self
    {
        $this->description = $description;

        return $this;
    }

    /**
     * @return Collection<int, Bar>
     */
    public function getBars(): Collection
    {
        return $this->bars;
    }

    public function addBar(Bar $bar): self
    {
        if (!$this->bars->contains($bar)) {
            $this->bars->add($bar);
            $bar->setFoo($this);
        }

        return $this;
    }

    public function removeBar(Bar $bar): self
    {
        if ($this->bars->removeElement($bar)) {
            if ($bar->getFoo() === $this) {
                $bar->setFoo(null);
            }
        }

        return $this;
    }
}

董事会实体

<?php

declare(strict_types=1);

namespace App\Entity;

use App\Repository\BarRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass=BarRepository::class)
 */
class Bar
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private ?int $id = null;

    /**
     * @ORM\Column(type="string", length=255)
     */
    #[Assert\NotBlank]
    #[Assert\Length(min: 2)]
    private string $name;

    /**
     * @ORM\Column(type="text", nullable=true)
     */
    private $description;

    /**
     * @ORM\ManyToOne(targetEntity=Foo::class, inversedBy="bars")
     * @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
     */
    private ?Foo $foo = null;

    /**
     * @var Collection<int, Baz>
     * @ORM\OneToMany(targetEntity=Baz::class, mappedBy="bar", orphanRemoval=true, cascade={"persist"})
     */
//    #[Assert\Count(min: 1)]
    #[Assert\Valid]
    private Collection $bazs;

    public function __construct()
    {
        $this->bazs = new ArrayCollection();
    }

    public function __toString()
    {
        return $this->name;
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getFoo(): ?Foo
    {
        return $this->foo;
    }

    public function setFoo(?Foo $foo): self
    {
        $this->foo = $foo;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(?string $description): self
    {
        $this->description = $description;

        return $this;
    }

    /**
     * @return Collection<int, Baz>
     */
    public function getBazs(): Collection
    {
        return $this->bazs;
    }

    public function addBaz(Baz $baz): self
    {
        if (!$this->bazs->contains($baz)) {
            $this->bazs->add($baz);
            $baz->setBar($this);
        }

        return $this;
    }

    public function removeBaz(Baz $baz): self
    {
        if ($this->bazs->removeElement($baz)) {
            // set the owning side to null (unless already changed)
            if ($baz->getBar() === $this) {
                $baz->setBar(null);
            }
        }

        return $this;
    }
}

类别实体

<?php

declare(strict_types=1);

namespace App\Entity;

use App\Repository\BazRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass=BazRepository::class)
 */
class Baz
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private ?int $id = null;

    /**
     * @ORM\Column(type="string", length=255)
     */
    #[Assert\NotBlank]
    #[Assert\Length(min: 2)]
    private string $name;

    /**
     * @ORM\Column(type="text", nullable=true)
     */
    private $description;

    /**
     * @ORM\ManyToOne(targetEntity=Bar::class, inversedBy="bazs")
     * @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
     */
    private ?Bar $bar = null;

    /**
     * @var Collection<int, Qux>
     * @ORM\OneToMany(targetEntity=Qux::class, mappedBy="baz", orphanRemoval=true, cascade={"persist"})
     */
//    #[Assert\Count(min: 1)]
    #[Assert\Valid]
    private Collection $quxes;

    public function __construct()
    {
        $this->quxes = new ArrayCollection();
    }

    public function __toString()
    {
        return $this->name;
    }

    /**
     * @return Collection<int, Qux>
     */
    public function getQuxes(): Collection
    {
        return $this->quxes;
    }

    public function addQux(Qux $qux): self
    {
        if (!$this->quxes->contains($qux)) {
            $this->quxes->add($qux);
            $qux->setBaz($this);
        }

        return $this;
    }

    public function removeQux(Qux $qux): self
    {
        if ($this->quxes->removeElement($qux)) {
            // set the owning side to null (unless already changed)
            if ($qux->getBaz() === $this) {
                $qux->setBaz(null);
            }
        }

        return $this;
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(?string $description): self
    {
        $this->description = $description;

        return $this;
    }

    public function getBar(): ?Bar
    {
        return $this->bar;
    }

    public function setBar(?Bar $bar): self
    {
        $this->bar = $bar;

        return $this;
    }
}

链接实体

<?php

declare(strict_types=1);

namespace App\Entity;

use App\Repository\QuxRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass=QuxRepository::class)
 */
class Qux
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private ?int $id = null;

    /**
     * @ORM\Column(type="string", length=255)
     */
    #[Assert\NotBlank]
    #[Assert\Length(min: 2)]
    private string $name;

    /**
     * @ORM\Column(type="text", nullable=true)
     */
    private $description;

    /**
     * @ORM\ManyToOne(targetEntity=Baz::class, inversedBy="quxes")
     * @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
     */
    private ?Baz $baz = null;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(?string $description): self
    {
        $this->description = $description;

        return $this;
    }

    public function getBaz(): ?Baz
    {
        return $this->baz;
    }

    public function setBaz(?Baz $baz): self
    {
        $this->baz = $baz;

        return $this;
    }
}

表格文件夹

<?php

declare(strict_types=1);

namespace App\Form;

use App\Entity\Foo;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class FooType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name', TextType::class,  [
                'empty_data' => '',
                'label' => 'Nom Dossier',
                'attr' => [
                    'placeholder' => 'Nommer le dossier'
                ]

            ])
            ->add('description', TextareaType::class,  [
                'empty_data' => '',
                'required' => false,
                'label' => 'Description Dossier',
                'attr' => [
                    'placeholder' => 'Décrire le dossier'
                ]

            ])
            ->add('bars', CollectionType::class, [
                'entry_type' => BarType::class,
                'by_reference' => false,
                'allow_add' => true,
                'allow_delete' => true,
                'label' => 'Boards',
                'error_bubbling' => false,
                'attr' => [
                    'placeholder' => 'Nommer le board'
                ]

            ]);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Foo::class,
            'csrf_protection' => true,
        ]);
    }
}

董事会表格

<?php

declare(strict_types=1);

namespace App\Form;

use App\Entity\Bar;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class BarType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name', TextType::class, [
                'label' => 'Nom Board',
                'empty_data' => '',
                'attr' => [
                    'placeholder' => 'Nommer le board'
                ]

            ])

            ->add('description', TextareaType::class, [
                'label' => 'Description Board',
                'required' => false,
                'empty_data' => '',
                'attr' => [
                    'placeholder' => 'Décrire le board'
                ]

            ])

            ->add('bazs', CollectionType::class, [
                'entry_type' => BazType::class,
                'by_reference' => false,
                'allow_add' => true,
                'allow_delete' => true,
                'label' => 'Categories',
                'error_bubbling' => false,
                'attr' => [
                    'placeholder' => 'Nommer la catégorie'
                ]

            ]);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Bar::class,
            'csrf_protection' => true,
        ]);
    }
}

类别表格

<?php

declare(strict_types=1);

namespace App\Form;

use App\Entity\Baz;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class BazType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name', TextType::class, [
                'label' => 'Nom Catégorie',
                'empty_data' => '',
                'attr' => [
                    'placeholder' => 'Nommer le catégorie'
                ]

            ])

            ->add('description', TextareaType::class, [
                'label' => 'Description Catégorie',
                'required' => false,
                'empty_data' => '',
                'attr' => [
                    'placeholder' => 'Décrire la catégorie'
                ]

            ])

            ->add('quxes', CollectionType::class, [
//                'mapped' => false,
                'entry_type' => QuxType::class,
                'by_reference' => false,
                'allow_add' => true,
                'allow_delete' => true,
                'label' => 'Eléments',
                'error_bubbling' => false,
                'attr' => [
                    'placeholder' => 'Nommer l\'élément'
                ]
            ]);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Baz::class,
            'csrf_protection' => true,
        ]);
    }
}

链接表格

<?php

declare(strict_types=1);

namespace App\Form;

use App\Entity\Qux;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class QuxType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name', TextType::class,[
                'label' => 'Nom',
                'attr' => [
                    'placeholder' => 'Nommer l\' élément'
                ]
            ])

            ->add('description', TextareaType::class,[
                'required' => false,
                'label' => 'Description',
                'attr' => [
                    'placeholder' => 'Décrire l\' élément'
                ]
            ]);
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Qux::class,
            'csrf_protection' => true,
        ]);
    }
}

感谢您的帮助

php symfony doctrine-orm doctrine
1个回答
0
投票

父实体必须有两个这样的方法:

public function addChild(Child $child): void
{
    if (!$this->children->contains($child)) {
        $this->children->add($child);
    }
}

public function removeChild(Child $child): void
{
    $this->children->removeElement($child);
}
© www.soinside.com 2019 - 2024. All rights reserved.