Symfony/EasyAdmin:如何自定义CollectionType键值对表单渲染?
问题描述
我在实体字段中存储了如下JSON格式的数据:
{ 'key': { 'value': 42, 'field': 'IntegerField' }, 'key2': { 'value': 'myValue', 'field': 'TextField' } }
该字段对应Symfony的Collection类型。我希望在EasyAdmin的新建/编辑页面中,将此JSON转换为关联数组并渲染成「键:值」的表单样式,但不知道如何重写模板块{% block collection_entry_row %}。我尝试过创建自定义字段与类型,但仍不清楚如何在EasyAdmin中完成模板渲染,相关CrudController代码如下:
class MyEntityCrudController extends AbstractCrudController { public static function getEntityFqcn(): string { return MyEntity::class; } public function configureFields(string $pageName): array { return [ ...parent::configureFields($pageName), TextField::new('public'), CollectionField::new('ressources'), // 这里需要在EasyAdmin中重写渲染逻辑 ]; } // ...其他代码 }
解决办法
1. 编写自定义表单类型处理JSON与数组互转
先创建ResourceCollectionType,负责把数据库中的JSON字符串解析为关联数组,提交表单时再将数组转回JSON格式:
// src/Form/Type/ResourceCollectionType.php namespace App\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolver; class ResourceCollectionType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { // 渲染表单前将JSON转成关联数组 $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { $data = $event->getData(); if (is_string($data)) { $event->setData(json_decode($data, true)); } }); // 提交表单前将关联数组转回JSON $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) { $event->setData(json_encode($event->getData())); }); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'entry_type' => ResourceEntryType::class, 'allow_add' => true, 'allow_delete' => true, 'prototype' => true, 'label' => false, ]); } public function getParent(): string { return CollectionType::class; } }
再创建每个条目对应的表单类型ResourceEntryType,用来生成「键+值」的输入结构:
// src/Form/Type/ResourceEntryType.php namespace App\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; class ResourceEntryType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('key', TextType::class, [ 'label' => '键', 'attr' => ['placeholder' => '输入键名'], ]) ->add('value', TextType::class, [ 'label' => '值', 'attr' => ['placeholder' => '输入对应值'], ]); // 后续可根据field字段动态切换输入框类型 } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults(['data_class' => null]); } }
2. 在EasyAdmin中配置自定义字段
修改MyEntityCrudController,将原CollectionField替换为指定自定义表单类型的字段,并绑定自定义模板:
// src/Controller/Admin/MyEntityCrudController.php use App\Form\Type\ResourceCollectionType; use EasyCorp\Bundle\EasyAdminBundle\Field\CollectionField; class MyEntityCrudController extends AbstractCrudController { public static function getEntityFqcn(): string { return MyEntity::class; } public function configureFields(string $pageName): array { return [ ...parent::configureFields($pageName), TextField::new('public'), CollectionField::new('ressources') ->setFormType(ResourceCollectionType::class) ->setTemplatePath('admin/field/resource_collection.html.twig'), ]; } // ...其他代码 }
3. 自定义模板重写渲染样式
创建templates/admin/field/resource_collection.html.twig,重写collection_entry_row块,实现「键:输入框 值:输入框」的样式:
{% extends '@EasyAdmin/crud/field/collection.html.twig' %} {% block collection_entry_row %} <div class="d-flex align-items-center gap-3 mb-2"> <div> {{ form_label(form.key) }}: {{ form_widget(form.key) }} </div> <div> {{ form_label(form.value) }}: {{ form_widget(form.value) }} </div> {% if form.parent.vars.allow_delete %} <button type="button" class="btn btn-danger btn-sm collection-delete-btn">删除</button> {% endif %} </div> {% endblock %} {% block collection_widget %} {{ parent() }} {% if prototype is defined and allow_add %} <button type="button" class="btn btn-success btn-sm mt-2 collection-add-btn" data-prototype="{{ form_widget(prototype)|e('html_attr') }}"> 添加新条目 </button> {% endif %} {% endblock %}
4. 可选:根据field类型动态切换输入框
如果需要根据存储的field值(如IntegerField、TextField)渲染不同类型的输入框,可以修改ResourceEntryType的buildForm方法,添加事件监听动态生成字段:
use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\FormEvents; // ... public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('key', TextType::class, [ 'label' => '键', 'attr' => ['placeholder' => '输入键名'], ]); // 渲染前根据field类型选择对应输入框 $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { $data = $event->getData(); $form = $event->getForm(); // 移除已存在的value字段避免重复 if ($form->has('value')) { $form->remove('value'); } $fieldType = $data['field'] ?? 'TextField'; $inputType = match($fieldType) { 'IntegerField' => IntegerType::class, 'TextField' => TextType::class, // 可扩展更多类型,如EmailType、PasswordType等 default => TextType::class, }; $form->add('value', $inputType, [ 'label' => '值', 'attr' => ['placeholder' => '输入对应值'], ]); }); }
内容的提问来源于stack exchange,提问作者Loïc
相关产品推荐
相关产品推荐

