我有 4 个实体:
文件夹、面板、类别、链接。
关系: 文件夹 [oneToMany] -> Boards [oneToMany] -> 类别 [oneToMany] -> 链接
我有一个递归 delete_Folder 路由,可以删除所有子元素(Boards、Categories、Links)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,
]);
}
}
感谢您的帮助
父实体必须有两个这样的方法:
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);
}