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

Symfony多对多表单报错:关联字段类型不匹配求助

解决多商品订单表单的关联错误&实现方案

这个错误一眼就能看出是中间表Bestelregel的实体关联映射搞反了——你把$bestelordernummer字段错误关联到了Artikel实体,但它实际上应该关联到Bestelling订单实体。咱们一步步拆解解决:

1. 修正Bestelregel实体的核心关联

先把Bestelregel的ORM映射改对,它作为中间表,需要同时关联Bestelling和Artikel,并且用这两个关联字段作为复合主键:

<?php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Table(name="bestelregel")
 * @ORM\Entity
 */
class Bestelregel
{
    /**
     * @ORM\Id
     * @ORM\ManyToOne(targetEntity="Bestelling", inversedBy="bestelregels")
     * @ORM\JoinColumn(name="bestelordernummer", referencedColumnName="bestelordernummer")
     */
    private $bestelordernummer; // 关联到Bestelling实体

    /**
     * @ORM\Id
     * @ORM\ManyToOne(targetEntity="Artikel", inversedBy="bestelregels")
     * @ORM\JoinColumn(name="artikelnummer", referencedColumnName="artikelnummer")
     */
    private $artikelnummer; // 关联到Artikel实体

    /**
     * @ORM\Column(type="integer")
     */
    private $aantal; // 商品数量

    // 构造函数、Getter和Setter
    public function __construct() {}

    public function getBestelordernummer(): ?Bestelling
    {
        return $this->bestelordernummer;
    }

    public function setBestelordernummer(?Bestelling $bestelling): self
    {
        $this->bestelordernummer = $bestelling;
        return $this;
    }

    public function getArtikelnummer(): ?Artikel
    {
        return $this->artikelnummer;
    }

    public function setArtikelnummer(?Artikel $artikel): self
    {
        $this->artikelnummer = $artikel;
        return $this;
    }

    public function getAantal(): ?int
    {
        return $this->aantal;
    }

    public function setAantal(int $aantal): self
    {
        $this->aantal = $aantal;
        return $this;
    }
}

2. 完善Bestelling实体的关联集合

Bestelling实体需要包含一个bestelregels集合,用来关联对应的订单行,并且添加添加/删除订单行的方法:

<?php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * @ORM\Table(name="bestelling")
 * @ORM\Entity
 */
class Bestelling
{
    // 这里补充你的Bestelling原有字段,比如主键bestelordernummer、订单日期datum、关联客户klant等
    /**
     * @ORM\Column(name="bestelordernummer", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $bestelordernummer;

    /**
     * @ORM\OneToMany(targetEntity="Bestelregel", mappedBy="bestelordernummer", cascade={"persist", "remove"})
     */
    private $bestelregels;

    public function __construct()
    {
        $this->bestelregels = new ArrayCollection();
    }

    // 补充其他字段的Getter/Setter...

    public function getBestelregels(): \Doctrine\Common\Collections\Collection
    {
        return $this->bestelregels;
    }

    public function addBestelregel(Bestelregel $bestelregel): self
    {
        if (!$this->bestelregels->contains($bestelregel)) {
            $this->bestelregels[] = $bestelregel;
            $bestelregel->setBestelordernummer($this); // 自动关联订单
        }
        return $this;
    }

    public function removeBestelregel(Bestelregel $bestelregel): self
    {
        if ($this->bestelregels->removeElement($bestelregel)) {
            if ($bestelregel->getBestelordernummer() === $this) {
                $bestelregel->setBestelordernummer(null);
            }
        }
        return $this;
    }
}

3. 创建支持多商品的表单类型

3.1 单个订单行的表单(BestelregelType)

用来选择商品和填写数量:

<?php
namespace AppBundle\Form;

use AppBundle\Entity\Artikel;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;

class BestelregelType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('artikelnummer', EntityType::class, [
                'class' => Artikel::class,
                'choice_label' => function(Artikel $artikel) {
                    return $artikel->getArtikelnummer() . ' - ' . $artikel->getOmschrijving();
                },
                'label' => 'Artikel'
            ])
            ->add('aantal', IntegerType::class, [
                'label' => 'Aantal',
                'attr' => ['min' => 1]
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => \AppBundle\Entity\Bestelregel::class,
        ]);
    }
}

3.2 主订单表单(BestellingType)

嵌入订单行集合,支持动态添加/删除:

<?php
namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;

class BestellingType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            // 这里添加Bestelling的其他字段,比如客户选择、订单日期等
            // ->add('klant', EntityType::class, ['class' => Klant::class, 'choice_label' => 'naam'])
            ->add('bestelregels', CollectionType::class, [
                'entry_type' => BestelregelType::class,
                'allow_add' => true, // 允许添加新行
                'allow_delete' => true, // 允许删除行
                'prototype' => true, // 启用模板生成新行
                'by_reference' => false, // 必须设为false,才能触发addBestelregel方法
                'label' => 'Bestelregels'
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => \AppBundle\Entity\Bestelling::class,
        ]);
    }
}

4. 控制器处理表单提交

编写控制器逻辑,处理表单的创建、验证和持久化:

<?php
namespace AppBundle\Controller;

use AppBundle\Entity\Bestelling;
use AppBundle\Form\BestellingType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

class BestellingController extends AbstractController
{
    /**
     * @Route("/bestelling/nieuw", name="bestelling_nieuw")
     */
    public function nieuwAction(Request $request)
    {
        $bestelling = new Bestelling();
        // 可选:预先添加一个空订单行,方便用户直接输入
        // $bestelling->addBestelregel(new \AppBundle\Entity\Bestelregel());

        $form = $this->createForm(BestellingType::class, $bestelling);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $entityManager = $this->getDoctrine()->getManager();
            $entityManager->persist($bestelling);
            $entityManager->flush();

            $this->addFlash('success', 'Bestelling is succesvol aangemaakt!');
            return $this->redirectToRoute('bestelling_overzicht'); // 替换成你的订单列表路由
        }

        return $this->render('bestelling/nieuw.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}

5. Twig模板支持动态添加商品行

在模板里添加JS,实现点击按钮动态添加/删除订单行:

{% extends 'base.html.twig' %}

{% block body %}
    <h1>Nieuwe Bestelling</h1>

    {{ form_start(form) }}
        {{ form_widget(form) }}

        <div id="bestelregels-container" class="mt-3">
            {% for bestelregel in form.bestelregels %}
                <div class="bestelregel-item p-2 border mb-2">
                    {{ form_widget(bestelregel) }}
                    <button type="button" class="remove-btn btn btn-danger ms-2">Verwijder</button>
                </div>
            {% endfor %}
        </div>

        <button type="button" id="add-bestelregel" class="btn btn-primary mt-2">Voeg Artikel Toe</button>
        <button type="submit" class="btn btn-success mt-2 ms-2">Opslaan</button>
    {{ form_end(form) }}

    <script>
        // 获取Symfony生成的订单行模板
        var prototype = '{{ form_widget(form.bestelregels.vars.prototype)|escape('js') }}';
        var container = document.getElementById('bestelregels-container');
        var addButton = document.getElementById('add-bestelregel');
        var rowCount = {{ form.bestelregels|length }};

        // 添加新行的事件
        addButton.addEventListener('click', function() {
            // 替换模板里的__name__占位符为当前行数
            var newRow = prototype.replace(/__name__/g, rowCount);
            container.insertAdjacentHTML('beforeend', `<div class="bestelregel-item p-2 border mb-2">${newRow}<button type="button" class="remove-btn btn btn-danger ms-2">Verwijder</button></div>`);
            
            // 给新添加的删除按钮绑定事件
            bindRemoveButtons();
            rowCount++;
        });

        // 初始化删除按钮事件
        bindRemoveButtons();

        function bindRemoveButtons() {
            document.querySelectorAll('.remove-btn').forEach(btn => {
                btn.addEventListener('click', function() {
                    this.parentElement.remove();
                });
            });
        }
    </script>
{% endblock %}

最后验证

完成以上修改后,先执行Doctrine的 schema 更新命令:

php bin/console doctrine:schema:update --force

然后访问订单创建页面,就能正常添加多个商品到订单里了,之前的类型不匹配错误也会消失。

内容的提问来源于stack exchange,提问作者Mr J.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:20:11