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

Symfony 6继承映射:如何根据单选按钮提交对应表单?

问题修复与实现方案

1. 实体层优化

首先移除User类中多余的typeCompte字段——Doctrine的JOINED继承已经通过DiscriminatorColumn(compte)区分实体类型,无需额外存储该字段:

// User.php
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\InheritanceType("JOINED")]
#[ORM\DiscriminatorColumn(name:"compte", type: "string")]
#[ORM\DiscriminatorMap(["professionnel"=>Professionel::class, "particulier"=> Particulier::class])]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private $id;

    #[ORM\Column(type: 'string', length: 180, unique: true)]
    #[Assert\NotBlank()]
    #[Assert\Length(min:2, max:80)]
    #[Assert\Email(message:"Choisir un autre email")]
    private $email;
    
    #[ORM\Column(type: 'boolean', nullable: true)]
    private $approve;
    
    // 保留原有getter/setter,移除typeCompte相关代码
}

2. 表单类型修正

核心问题修正:

  • 统一选项值大小写,避免判断逻辑失效
  • 移除手动设置的name属性,让Symfony自动处理表单字段命名
  • 修正PRE_SUBMIT事件中的空值判断逻辑
  • 移除冲突的默认值设置(PRE_SET_DATA与ChoiceType的data属性重复)

修改后的RegistrationFormType:

class RegistrationFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('typeCompte', ChoiceType::class, [
                'label' => 'Type de compte*',
                'choices'=>[
                    'Particulier' => 'particulier',
                    'Professionnel' => 'professionnel' // 统一值为小写,与DiscriminatorMap匹配
                ],
                'data' => 'particulier', // 默认选中个人用户
                'expanded' => true,
                'multiple' => false,
                'attr' => ['class' => 'form-check-input']
            ])
            ->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
                $data = $event->getData();
                $form = $event->getForm();

                // 处理空值情况,默认使用个人用户类型
                $accountType = $data['typeCompte'] ?? 'particulier';

                if ($accountType === 'particulier') {
                    $form->add('civility', ChoiceType::class, [
                        'label' => 'Civilité',
                        'choices' => [
                            'Madame' => 'Madame',
                            'Mademoiselle' => 'Mademoiselle',
                            'Monsieur' => 'Monsieur'
                        ],
                        'expanded' => true,
                        'multiple' => false,
                        'attr' => ['class' => 'form-check-input']
                    ])
                    ->add('prenom', TextType::class, [
                        'label' => 'Prénom*',
                        'attr' => [
                            'placeholder' => 'Votre prénom',
                            'class' => 'form-control'
                        ]
                    ])
                    ->add('nom', TextType::class, [
                        'label' => 'Nom*',
                        'attr' => [
                            'placeholder' => 'Votre nom',
                            'class' => 'form-control'
                        ]
                    ]);
                } elseif ($accountType === 'professionnel') {
                    $form->add('nomProfessionel', TextType::class, [
                        'label' => 'Concession*',
                        'attr' => [
                            'placeholder' => 'Nom concession',
                            'class' => 'form-control',
                            'id' => 'entreprise'
                        ]
                    ])
                    ->add('logo', FileType::class, [
                        'label' => 'Logo',
                        'attr' => [
                            'class' => 'form-control',
                            'id' => 'entreprise-logo'
                        ],
                        'required' => false
                    ]);
                }
            })
            ->add('email', EmailType::class, [
                'label' => 'Email*',
                'attr' => [
                    'placeholder' => 'Votre email',
                    'class' => 'form-control'
                ]
            ])
            ->add('password', PasswordType::class, [
                'label' => 'Mot de passe*',
                'attr' => [
                    'placeholder' => 'Votre mot de passe',
                    'class' => 'form-control'
                ]
            ]);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => null, // 允许动态绑定子类实例
            'csrf_protection' => true,
            'csrf_field_name' => '_token',
        ]);
    }
}

3. 控制器逻辑修正

核心问题修正:

  • 先根据提交的账户类型创建对应子类实例,再绑定表单
  • 修正getData()的错误用法,正确映射表单数据到实例
  • 完善表单处理流程,确保数据正确持久化

修改后的控制器代码:

#[Route('/Inscription', name: 'app_register')]
public function register(
    Request $request, 
    UserPasswordHasherInterface $userPasswordHasher,
    EntityManagerInterface $em
): Response {
    // 根据提交数据或默认值创建对应实例
    $submittedData = $request->request->get('registration_form');
    $accountType = $submittedData['typeCompte'] ?? 'particulier';
    
    $user = match($accountType) {
        'professionnel' => new Professionel(),
        default => new Particulier()
    };

    $form = $this->createForm(RegistrationFormType::class, $user);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        // 密码加密
        $user->setPassword(
            $userPasswordHasher->hashPassword(
                $user,
                $form->get('password')->getData()
            )
        );

        $em->persist($user);
        $em->flush();

        // 注册成功后跳转至登录页
        return $this->redirectToRoute('app_login');
    }

    return $this->renderForm('registration/register.html.twig', [
        'registrationForm' => $form,
    ]);
}

4. 前端模板注意事项

确保Twig模板正确渲染表单,若需要实时切换字段显示,可添加JS监听单选按钮变化:

{# registration/register.html.twig #}
{% extends 'base.html.twig' %}

{% block body %}
    <h1>Inscription</h1>
    {{ form_start(registrationForm) }}
        {{ form_widget(registrationForm.typeCompte) }}
        {{ form_rest(registrationForm) }}
        <button type="submit" class="btn btn-primary mt-3">S'inscrire</button>
    {{ form_end(registrationForm) }}

    {# 可选:JS实时显示/隐藏字段 #}
    <script>
        const typeInputs = document.querySelectorAll('input[name="registration_form[typeCompte]"]');
        typeInputs.forEach(input => {
            input.addEventListener('change', function() {
                // 根据选中值切换字段显示逻辑
                const isProfessionnel = this.value === 'professionnel';
                document.querySelector('[name="registration_form[prenom]"]').closest('.form-group').style.display = isProfessionnel ? 'none' : 'block';
                document.querySelector('[name="registration_form[nomProfessionel]"]')?.closest('.form-group').style.display = isProfessionnel ? 'block' : 'none';
            });
        });
        // 初始化显示状态
        document.querySelector('input[name="registration_form[typeCompte]"]:checked').dispatchEvent(new Event('change'));
    </script>
{% endblock %}

关键问题解释

  1. 空值错误原因:
    表单手动设置name="type_compte"导致提交字段名与代码中$data['typeCompte']的键不匹配,移除手动name属性后,Symfony会自动生成与表单字段名一致的键,解决空值问题。

  2. 继承实体表单绑定:
    不固定表单data_class,允许动态绑定Particulier或Professionel实例,确保子类属性能正确映射表单字段。

  3. 选项值一致性:
    统一选项值为小写,与Doctrine的DiscriminatorMap值一致,避免判断逻辑出错。

内容的提问来源于stack exchange,提问作者kasali

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 19:05:27