如何在EasyAdmin 4中实现CRUD嵌套?Symfony7关联实体管理
实现方案:在EasyAdmin4的Formation页面内嵌Prix的完整CRUD管理
针对Symfony7 + EasyAdmin4的一对多关联场景,要在Formation的创建/编辑页添加标签页内嵌Prix的完整CRUD(列表、增改删),可以按以下步骤实现:
1. 确保实体关联配置正确
首先确认Formation和Prix的一对多映射已配置:
Formation实体
// src/Entity/Formation.php use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity] class Formation { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; // 其他业务字段... #[ORM\OneToMany(mappedBy: 'formation', targetEntity: Prix::class, cascade: ['persist', 'remove'], orphanRemoval: true)] private Collection $prix; public function __construct() { $this->prix = new ArrayCollection(); } public function getPrix(): Collection { return $this->prix; } }
Prix实体
// src/Entity/Prix.php use Doctrine\ORM\Mapping as ORM; #[ORM\Entity] class Prix { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; // 其他业务字段(如金额、有效期等)... #[ORM\ManyToOne(targetEntity: Formation::class, inversedBy: 'prix')] #[ORM\JoinColumn(nullable: false)] private ?Formation $formation = null; // Getter/Setter... public function getFormation(): ?Formation { return $this->formation; } public function setFormation(?Formation $formation): self { $this->formation = $formation; return $this; } }
2. 配置Prix的CrudController
创建PrixCrudController,处理关联过滤、自动绑定Formation,以及操作后的跳转逻辑:
// src/Controller/Admin/PrixCrudController.php use App\Entity\Formation; use App\Entity\Prix; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField; use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; use EasyCorp\Bundle\EasyAdminBundle\Filter\EntityFilter; use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; use Symfony\Component\HttpFoundation\RedirectResponse; class PrixCrudController extends AbstractCrudController { public static function getEntityFqcn(): string { return Prix::class; } public function configureCrud(Crud $crud): Crud { return $crud->setPageTitle('new', 'Ajouter un prix pour cette formation'); } public function configureFields(string $pageName): iterable { $fields = [ IdField::new('id')->onlyOnIndex(), // 替换成你的Prix业务字段,比如: // NumberField::new('montant', 'Montant'), // DateField::new('dateDebut', 'Date de début'), ]; // 创建页面自动绑定Formation,隐藏关联字段 if ($pageName === Crud::PAGE_NEW && $this->get('request_stack')->getCurrentRequest()->query->has('formationId')) { $formation = $this->getDoctrine()->getRepository(Formation::class)->find( $this->get('request_stack')->getCurrentRequest()->query->get('formationId') ); $this->getContext()->getEntity()->getInstance()->setFormation($formation); } else { $fields[] = AssociationField::new('formation')->onlyOnForms(); } return $fields; } public function configureFilters(\EasyCorp\Bundle\EasyAdminBundle\Config\Filters $filters): \EasyCorp\Bundle\EasyAdminBundle\Config\Filters { // 列表页过滤当前Formation的Prix $request = $this->get('request_stack')->getCurrentRequest(); if ($request && $request->query->has('formationId')) { $filters->add(EntityFilter::new('formation')->setQueryParameter('formationId')); } return $filters; } // 编辑后跳回Formation的Prix标签页 public function editEntity(\Doctrine\ORM\EntityManagerInterface $entityManager, $entity): void { parent::editEntity($entityManager, $entity); $this->redirectToFormationTab($entity->getFormation()->getId()); } // 删除后跳回Formation的Prix标签页 public function deleteEntity(\Doctrine\ORM\EntityManagerInterface $entityManager, $entity): void { $formationId = $entity->getFormation()->getId(); parent::deleteEntity($entityManager, $entity); $this->redirectToFormationTab($formationId); } // 创建完成后跳回Formation的Prix标签页 public function createEntity(string $entityFqcn) { $entity = parent::createEntity($entityFqcn); $request = $this->get('request_stack')->getCurrentRequest(); if ($request->query->has('formationId')) { $formation = $this->getDoctrine()->getRepository(Formation::class)->find($request->query->get('formationId')); $entity->setFormation($formation); } return $entity; } private function redirectToFormationTab(int $formationId): never { $redirectUrl = $this->get(AdminUrlGenerator::class) ->setController(FormationCrudController::class) ->setAction('edit') ->setEntityId($formationId) ->setTab('Prix') ->generateUrl(); throw new RedirectResponse($redirectUrl); } }
3. 修改FormationCrudController添加Prix标签页
在FormationCrudController中配置Tabs,根据Formation是否已保存(有无ID)显示不同内容:
// src/Controller/Admin/FormationCrudController.php use App\Entity\Formation; use EasyCorp\Bundle\EasyAdminBundle\Config\Tab; use EasyCorp\Bundle\EasyAdminBundle\Config\Tabs; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; class FormationCrudController extends AbstractCrudController { public static function getEntityFqcn(): string { return Formation::class; } public function configureTabs(Tabs $tabs): Tabs { $entity = $this->getContext()->getEntity(); $formationId = $entity->getInstance()->getId(); // 默认Formation标签页 $tabs = $tabs->addTab(Tab::new('Formation')->setDefault(true)->addFields([ // 替换成你的Formation业务字段 // TextField::new('titre', 'Titre'), // TextareaField::new('description', 'Description'), ])); // 未保存的Formation:显示提示 if (!$formationId) { $tabs->addTab(Tab::new('Prix')->setTemplatePath('admin/prix_empty_tab.html.twig')); } else { // 已保存的Formation:内嵌Prix CRUD $prixNewUrl = $this->get(AdminUrlGenerator::class) ->setController(PrixCrudController::class) ->setAction('new') ->set('formationId', $formationId) ->generateUrl(); $tabs->addTab(Tab::new('Prix')->setTemplatePath('admin/embedded_prix_crud.html.twig') ->setVariables([ 'formationId' => $formationId, 'prixNewUrl' => $prixNewUrl, ])); } return $tabs; } }
4. 创建自定义模板
空状态模板(未保存Formation时显示)
创建templates/admin/prix_empty_tab.html.twig:
<div class="alert alert-info mt-4"> Veuillez d'abord sauvegarder cette formation pour pouvoir ajouter et gérer des prix. </div>
内嵌CRUD模板(已保存Formation时显示)
创建templates/admin/embedded_prix_crud.html.twig:
{% extends '@EasyAdmin/crud/base.html.twig' %} {% block content %} <div class="d-flex justify-content-between align-items-center mb-3"> <h2>Gestion des prix</h2> <a href="{{ prixNewUrl }}" class="btn btn-primary">Ajouter un prix</a> </div> {# 渲染Prix的列表视图,自动过滤当前Formation的数据 #} {{ render(controller('App\\Controller\\Admin\\PrixCrudController::indexAction', { 'formationId': formationId })) }} {% endblock %}
效果说明
- Formation创建页:Prix标签页显示提示,需先保存Formation才能管理价格
- Formation编辑页:Prix标签页显示当前Formation的所有Prix列表,包含完整的编辑、删除操作;点击"添加"按钮打开Prix创建页,自动关联当前Formation,操作完成后自动返回Formation的Prix标签页
内容的提问来源于stack exchange,提问作者Matthieu Le Verger
相关产品推荐
相关产品推荐

