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
相关产品推荐
相关产品推荐

