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

Symfony 6:AJAX提交表单时在模态框显示验证错误

实现Symfony 6 AJAX表单提交+Bootstrap模态框内显示验证错误

1. 控制器逻辑调整

修改产品创建控制器,区分AJAX请求与普通请求,验证失败时仅返回带错误的表单片段:

// src/Controller/ProductController.php
namespace App\Controller;

use App\Entity\Product;
use App\Form\ProductType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class ProductController extends AbstractController
{
    #[Route('/product/new', name: 'product_new', methods: ['GET', 'POST'])]
    public function new(Request $request, EntityManagerInterface $em): Response
    {
        $product = new Product();
        $form = $this->createForm(ProductType::class, $product);
        $form->handleRequest($request);

        if ($form->isSubmitted()) {
            if ($form->isValid()) {
                $em->persist($product);
                $em->flush();

                // AJAX请求返回成功状态
                if ($request->isXmlHttpRequest()) {
                    return $this->json([
                        'success' => true,
                        'message' => '产品创建成功'
                    ]);
                }
                return $this->redirectToRoute('product_index');
            } else {
                // 验证失败时,AJAX请求返回带错误的表单HTML
                if ($request->isXmlHttpRequest()) {
                    return $this->render('product/_form.html.twig', [
                        'form' => $form->createView(),
                        'product' => $product,
                    ]);
                }
            }
        }

        // 初始加载或非AJAX请求的渲染逻辑
        if ($request->isXmlHttpRequest()) {
            return $this->render('product/_form.html.twig', [
                'form' => $form->createView(),
                'product' => $product,
            ]);
        }

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

2. 抽离表单为Twig片段

创建独立的表单模板片段,方便AJAX返回时直接替换模态框内容:

{# templates/product/_form.html.twig #}
{{ form_start(form, {
    'attr': {
        'id': 'product-form',
        'class': 'needs-validation',
        'novalidate': 'novalidate'
    }
}) }}
    <div class="mb-3">
        {{ form_label(form.name) }}
        {{ form_widget(form.name, {'attr': {'class': 'form-control'}}) }}
        {{ form_errors(form.name) }}
    </div>
    <div class="mb-3">
        {{ form_label(form.price) }}
        {{ form_widget(form.price, {'attr': {'class': 'form-control'}}) }}
        {{ form_errors(form.price) }}
    </div>
    <!-- 其他表单字段按此格式添加 -->

    <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
        {{ form_widget(form.submit, {'attr': {'class': 'btn btn-primary'}}) }}
    </div>
{{ form_end(form) }}

3. 模态框主模板

在页面模板中引入Bootstrap模态框,嵌入上述表单片段:

{# 示例:templates/product/index.html.twig #}
<!-- 新增产品模态框 -->
<div class="modal fade" id="productModal" tabindex="-1" aria-labelledby="productModalLabel" aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="productModalLabel">新增产品</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body" id="modal-form-container">
                {{ include('product/_form.html.twig') }}
            </div>
        </div>
    </div>
</div>

<!-- 触发模态框按钮 -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#productModal">
    新增产品
</button>

4. 前端AJAX处理逻辑

添加JS代码监听表单提交,发送AJAX请求并处理响应:

document.addEventListener('DOMContentLoaded', function() {
    const modal = new bootstrap.Modal(document.getElementById('productModal'));
    const modalFormContainer = document.getElementById('modal-form-container');

    // 绑定表单提交事件
    function bindFormSubmit() {
        const form = document.getElementById('product-form');
        if (!form) return;

        form.addEventListener('submit', function(e) {
            e.preventDefault();
            const formData = new FormData(form);

            fetch(form.action, {
                method: form.method,
                body: formData,
                headers: {'X-Requested-With': 'XMLHttpRequest'}
            })
            .then(response => {
                if (response.ok) return response.json();
                return response.text();
            })
            .then(data => {
                if (typeof data === 'object' && data.success) {
                    // 成功:关闭模态框,可添加列表刷新逻辑
                    modal.hide();
                    alert(data.message);
                } else {
                    // 失败:替换表单显示错误
                    modalFormContainer.innerHTML = data;
                    bindFormSubmit();
                }
            })
            .catch(err => console.error('请求失败:', err));
        });
    }

    // 初始绑定
    bindFormSubmit();
});

关键说明

  • 控制器通过$request->isXmlHttpRequest()识别AJAX请求,验证失败时仅返回表单HTML,避免整页刷新
  • 表单抽离为独立片段,确保AJAX返回的内容能直接替换模态框内的表单区域
  • 表单替换后需重新绑定提交事件,否则新表单无法触发AJAX逻辑
  • Symfony表单组件会自动渲染字段错误,无需额外处理错误样式(可结合Bootstrap样式优化显示)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 10:52:53