You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 18:02:02