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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 22:13:14