Symfony框架下如何将多语言选项插入MySQL单字段
Symfony动态语言选择表单提交与格式化存储问题
我正在用Symfony框架搭建网站,用户注册表单里需要让用户选择想要学习的语言,支持通过JavaScript动态添加/删除选择框(数量不限)。需求是把用户选的多个语言按指定格式存入MySQL的单个字段:选2种时格式为“Spanish and Chinese”,选3种及以上时为“French, Italian and German”。目前已经实现了动态添加删除选择框,但表单只能提交最后一个选择框的内容,没法把所有选择的语言按要求格式存进数据库。
现有代码
register.html.twig
{% extends 'base.html.twig' %} {% block title %}Incris-toi !{% endblock %} {% block main %} {{ form_start(userform) }} <div class="form container"> <div class="formpage"> <div class="optional mb-3"> <p>Quelle langue souhaites-tu pratiquer ?</p> <div class="wishedlanguagelist"> {{ form_widget(userform.wishedlanguages, {'attr' : {'class' : 'form-control mb-3', 'name': 'user[wishedlanguages][]'}}) }} </div> <div class="d-flex justify-content-between"> <button type="button" class="question-button btn btn-outline-secondary" id="addwishedlanguage">+ Ajouter une langue</button> <button type="button" class="question-button btn btn-outline-secondary" id="removewishedlanguage">- Supprimer la dernière langue</button> </div> </div> <div class="d-flex"> <button type="button" class="prev btn btn-lg btn-outline-secondary mt-4 d-flex align-items-center me-auto"><<span class="previous">Précédent</span></button> {{ form_row(userform.save, {'attr' : {'class' : 'btn btn-lg btn-outline-primary'}}) }} </div> </div> </div> {{ form_end(userform) }} {% endblock %} {% block js %} <script src="../assets/js/scripts.js"></script> {% endblock %}
scripts.js
let idcounter = 1; document.getElementById("addwishedlanguage").onclick = function() { const wishedlanguageselect = document.getElementById("user_wishedlanguages"); const newwishedlanguageselect = wishedlanguageselect.cloneNode(true); idcounter++; newwishedlanguageselect.id = `user_wishedlanguages_${idcounter}`; // 设置name为数组形式,确保所有选择框的值都能提交 newwishedlanguageselect.name = "user[wishedlanguages][]"; document.querySelector('.wishedlanguagelist').appendChild(newwishedlanguageselect); document.getElementById("removewishedlanguage").style.display = "initial"; } document.getElementById("removewishedlanguage").onclick = function() { if (idcounter <= 1) return; document.getElementById(`user_wishedlanguages_${idcounter}`).remove(); idcounter--; if(idcounter === 1) { document.getElementById("removewishedlanguage").style.display = "none"; } }
UserType.php
<?php namespace App\Form; use App\Entity\User; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\LanguageType; class UserType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('wishedlanguages', LanguageType::class, [ 'placeholder' => 'Je choisis une langue étrangère', // 设置choice_label显示语言名称(法语),而不是默认的代码 'choice_label' => function ($value) { return \Locale::getDisplayLanguage($value, 'fr'); }, ]) ->add('save', SubmitType::class, [ 'label' => 'Je valide', ]); // 添加数据转换器,将数组格式的语言转换为指定字符串格式 $builder->get('wishedlanguages') ->addModelTransformer(new CallbackTransformer( // 从数据库字符串转换为表单数组(编辑场景用) function (?string $languages): array { if (!$languages) { return []; } // 解析现有格式的字符串为数组 $parts = preg_split('/, | and /', $languages); // 过滤空值并返回 return array_filter(array_map('trim', $parts)); }, // 从表单数组转换为数据库存储的字符串 function (?array $languages): ?string { if (!$languages || empty(array_filter($languages))) { return null; } // 过滤空选择项 $filtered = array_filter($languages); $count = count($filtered); if ($count === 1) { return reset($filtered); } elseif ($count === 2) { return implode(' and ', $filtered); } else { $last = array_pop($filtered); return implode(', ', $filtered) . ' and ' . $last; } } )); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => User::class, 'translation_domain' => 'forms' ]); } }
Entity/User.php
<?php namespace App\Entity; use App\Repository\UserRepository; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; #[ORM\Entity(repositoryClass: UserRepository::class)] class User implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; #[ORM\Column(length: 255, nullable: true)] private ?string $wishedlanguages = null; public function getId(): ?int { return $this->id; } public function getWishedlanguages(): ?string { return $this->wishedlanguages; } public function setWishedlanguages(?string $wishedlanguages): self { $this->wishedlanguages = $wishedlanguages; return $this; } // 实现UserInterface的必填方法 public function getRoles(): array { return ['ROLE_USER']; } public function eraseCredentials(): void { // 留空即可 } public function getUserIdentifier(): string { // 根据实际用户标识字段修改,例如email return ''; } public function getPassword(): ?string { return ''; } }
关键修改说明
前端表单name属性调整
- 给初始语言选择框添加
name="user[wishedlanguages][]",确保提交时以数组形式传递所有选择值 - JavaScript克隆新选择框时,同步设置name为数组形式,避免字段值被覆盖
- 给初始语言选择框添加
表单数据转换器添加
- 在UserType中加入CallbackTransformer,处理双向转换:
- 从数据库字符串解析为表单数组(适配用户编辑场景)
- 将表单提交的数组转换为指定格式的字符串(存入数据库)
- 配置LanguageType的choice_label,让选择框显示语言名称而非默认的代码
- 在UserType中加入CallbackTransformer,处理双向转换:
JavaScript逻辑优化
- 修正id生成逻辑,避免删除操作出现id匹配错误
- 添加边界判断,防止删除不存在的选择框
修改完成后,表单即可提交所有动态添加的语言选择值,并自动转换为要求的格式存入数据库的单个字段中。
内容的提问来源于stack exchange,提问作者Emilie Tossan
相关产品推荐
相关产品推荐

