如何在EasyAdmin的AssociationField中通过PRE_SUBMIT事件创建新标签?
问题描述
场景背景
我有一个LOBExercise实体,包含多对多关联字段:
#[ORM\ManyToMany(targetEntity: LOBTag::class, inversedBy: 'LOBExercises')] private Collection $lob_tag;
在LOBExerciseCrudController中配置了AssociationField:
yield AssociationField::new('lob_tag') ->hideOnIndex() ->addCssClass('border-none') ->setFormTypeOptions([ 'by_reference' => true, 'attr' => [ 'data-ea-widget' => "ea-autocomplete", 'data-ea-autocomplete-allow-item-create' => 'true', ], ]) ->setLabel('Tags');
我希望通过自动补全输入框创建新标签(即输入未存在的标签名称后,直接生成新标签条目),但Symfony抛出**"Choice is not valid"**错误,因为新标签不在数据库预选项中。
我尝试在Symfony表单的PRE_SUBMIT事件中提前创建新标签,但在EasyAdmin中无法生效:
$builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) { $data = $event->getData(); // Create new tags in the database $event->setData($data); }
试过自定义表单类型、集合字段、事件订阅者和Doctrine事件等方法,都没解决问题。
核心问题:如何在EasyAdmin的字段中使用FormEvents::PRE_SUBMIT事件?
解决方案
在EasyAdmin中要给AssociationField绑定表单事件,可通过以下两种可行方式实现:
方法1:直接在AssociationField中配置事件监听
无需创建自定义表单类型,直接在setFormTypeOptions中添加event_listeners配置:
use Symfony\Component\Form\FormEvents; use Doctrine\ORM\EntityManagerInterface; // 在LOBExerciseCrudController的configureFields方法中 yield AssociationField::new('lob_tag') ->hideOnIndex() ->addCssClass('border-none') ->setFormTypeOptions([ 'by_reference' => true, 'attr' => [ 'data-ea-widget' => "ea-autocomplete", 'data-ea-autocomplete-allow-item-create' => 'true', ], 'event_listeners' => [ FormEvents::PRE_SUBMIT => function(FormEvent $event) { $data = $event->getData(); // 从表单容器中获取EntityManager $entityManager = $event->getForm()->getConfig()->getContainer()->get(EntityManagerInterface::class); if (is_array($data)) { $processedTagIds = []; foreach ($data as $tagName) { // 检查标签是否已存在 $tag = $entityManager->getRepository(LOBTag::class)->findOneBy(['name' => $tagName]); if (!$tag) { // 创建新标签并持久化 $tag = new LOBTag(); $tag->setName($tagName); $entityManager->persist($tag); } // 用标签ID替换提交的名称,让表单识别有效选项 $processedTagIds[] = $tag->getId(); } // 更新表单提交数据 $event->setData($processedTagIds); // 提前flush,确保新标签存入数据库并生成ID $entityManager->flush(); } }, ], ]) ->setLabel('Tags');
关键注意点
- 获取EntityManager:通过表单配置的容器获取,避免在CrudController中额外注入依赖
- 替换提交数据:自动补全组件提交的是标签名称,需要替换为标签ID,否则表单会判定选项无效
- 提前执行flush:在
PRE_SUBMIT事件中持久化新标签后必须flush,否则Doctrine未生成ID,表单无法识别 - 适配实体字段:如果你的
LOBTag用其他字段作为显示标识(比如title),需对应修改查询条件
方法2:创建自定义表单类型复用逻辑
如果需要在多个地方复用该逻辑,可创建自定义表单类型:
// src/Form/Type/TagAutocompleteType.php namespace App\Form\Type; use App\Entity\LOBTag; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolver; class TagAutocompleteType extends AbstractType { public function __construct(private EntityManagerInterface $em) {} public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) { $data = $event->getData(); if (!is_array($data)) return; $processedTagIds = []; foreach ($data as $tagName) { $tag = $this->em->getRepository(LOBTag::class)->findOneBy(['name' => $tagName]); if (!$tag) { $tag = new LOBTag(); $tag->setName($tagName); $this->em->persist($tag); } $processedTagIds[] = $tag->getId(); } $event->setData($processedTagIds); $this->em->flush(); }); } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'class' => LOBTag::class, 'choice_label' => 'name', 'multiple' => true, 'attr' => [ 'data-ea-widget' => "ea-autocomplete", 'data-ea-autocomplete-allow-item-create' => 'true', ], ]); } public function getParent() { return ChoiceType::class; } }
然后在CrudController中使用该自定义类型:
yield AssociationField::new('lob_tag') ->hideOnIndex() ->addCssClass('border-none') ->setFormType(TagAutocompleteType::class) ->setLabel('Tags');
内容的提问来源于stack exchange,提问作者Michelium
相关产品推荐
相关产品推荐

