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

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">&lt;<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 '';
    }
}

关键修改说明

  1. 前端表单name属性调整

    • 给初始语言选择框添加name="user[wishedlanguages][]",确保提交时以数组形式传递所有选择值
    • JavaScript克隆新选择框时,同步设置name为数组形式,避免字段值被覆盖
  2. 表单数据转换器添加

    • 在UserType中加入CallbackTransformer,处理双向转换:
      • 从数据库字符串解析为表单数组(适配用户编辑场景)
      • 将表单提交的数组转换为指定格式的字符串(存入数据库)
    • 配置LanguageType的choice_label,让选择框显示语言名称而非默认的代码
  3. JavaScript逻辑优化

    • 修正id生成逻辑,避免删除操作出现id匹配错误
    • 添加边界判断,防止删除不存在的选择框

修改完成后,表单即可提交所有动态添加的语言选择值,并自动转换为要求的格式存入数据库的单个字段中。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 23:25:26