本文目录导读:

我来为你详细介绍Symfony Form与Bootstrap模态框的结合使用。
基本结构
Twig模板中的模态框
{# templates/demo/index.html.twig #}
<!-- 触发按钮 -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createModal">
新增记录
</button>
<!-- 模态框 -->
<div class="modal fade" id="createModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">新增记录</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
{{ form_start(form) }}
<div class="modal-body">
{{ form_widget(form) }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
{{ form_end(form) }}
</div>
</div>
</div>
创建Symfony表单
表单类型
// src/Form/ProductType.php
namespace App\Form;
use App\Entity\Product;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class, [
'label' => '产品名称',
'attr' => ['class' => 'form-control']
])
->add('price', NumberType::class, [
'label' => '价格',
'attr' => ['class' => 'form-control']
])
->add('description', TextareaType::class, [
'label' => '描述',
'required' => false,
'attr' => ['class' => 'form-control', 'rows' => 3]
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Product::class,
]);
}
}
控制器处理
创建和编辑
// src/Controller/ProductController.php
namespace App\Controller;
use App\Entity\Product;
use App\Form\ProductType;
use App\Repository\ProductRepository;
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('/products', name: 'product_list')]
public function index(ProductRepository $repository, Request $request, EntityManagerInterface $em): Response
{
// 创建表单
$product = new Product();
$createForm = $this->createForm(ProductType::class, $product, [
'action' => $this->generateUrl('product_create'),
'method' => 'POST',
]);
// 获取所有产品
$products = $repository->findAll();
return $this->render('product/index.html.twig', [
'products' => $products,
'form' => $createForm->createView(),
]);
}
#[Route('/product/create', name: 'product_create')]
public function create(Request $request, EntityManagerInterface $em): Response
{
$product = new Product();
$form = $this->createForm(ProductType::class, $product);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($product);
$em->flush();
$this->addFlash('success', '产品创建成功!');
if ($request->isXmlHttpRequest()) {
return $this->json([
'success' => true,
'product' => [
'id' => $product->getId(),
'name' => $product->getName(),
'price' => $product->getPrice(),
]
]);
}
return $this->redirectToRoute('product_list');
}
return $this->json([
'success' => false,
'errors' => $this->getFormErrors($form)
], 400);
}
private function getFormErrors($form): array
{
$errors = [];
foreach ($form->getErrors(true) as $error) {
$errors[] = $error->getMessage();
}
return $errors;
}
}
编辑模态框
Twig模板 - 编辑模态框
{# templates/product/index.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<div class="container mt-4">
<h1>产品列表</h1>
<!-- 新增按钮 -->
<button type="button" class="btn btn-primary mb-3" data-bs-toggle="modal" data-bs-target="#createModal">
新增产品
</button>
<!-- 产品列表 -->
<table class="table" id="productTable">
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>价格</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for product in products %}
<tr>
<td>{{ product.id }}</td>
<td>{{ product.name }}</td>
<td>{{ product.price }}</td>
<td>
<button class="btn btn-sm btn-warning edit-btn"
data-id="{{ product.id }}"
data-bs-toggle="modal"
data-bs-target="#editModal">
编辑
</button>
<button class="btn btn-sm btn-danger delete-btn"
data-id="{{ product.id }}">
删除
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- 创建模态框 -->
{% include 'product/_create_modal.html.twig' with {'form': form} %}
<!-- 编辑模态框 -->
{% include 'product/_edit_modal.html.twig' %}
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script src="{{ asset('js/product.js') }}"></script>
{% endblock %}
创建模态框组件
{# templates/product/_create_modal.html.twig #}
<div class="modal fade" id="createModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">新增产品</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
{{ form_start(form, {'attr': {'id': 'createForm', 'class': 'needs-validation'}}) }}
<div class="modal-body">
{{ form_widget(form) }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
{{ form_end(form) }}
</div>
</div>
</div>
编辑模态框组件
{# templates/product/_edit_modal.html.twig #}
<div class="modal fade" id="editModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">编辑产品</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="editForm" method="POST" action="">
<div class="modal-body">
<div class="mb-3">
<label for="edit_name" class="form-label">产品名称</label>
<input type="text" id="edit_name" name="product[name]"
class="form-control" required>
</div>
<div class="mb-3">
<label for="edit_price" class="form-label">价格</label>
<input type="number" id="edit_price" name="product[price]"
class="form-control" step="0.01" required>
</div>
<div class="mb-3">
<label for="edit_description" class="form-label">描述</label>
<textarea id="edit_description" name="product[description]"
class="form-control" rows="3"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="submit" class="btn btn-primary">更新</button>
</div>
</form>
</div>
</div>
</div>
JavaScript处理
// public/js/product.js
document.addEventListener('DOMContentLoaded', function() {
// 编辑按钮点击事件
document.querySelectorAll('.edit-btn').forEach(button => {
button.addEventListener('click', function() {
const productId = this.dataset.id;
loadProductData(productId);
});
});
// 编辑表单提交
document.getElementById('editForm').addEventListener('submit', function(e) {
e.preventDefault();
updateProduct(this);
});
// 删除按钮点击事件
document.querySelectorAll('.delete-btn').forEach(button => {
button.addEventListener('click', function() {
if (confirm('确定要删除这个产品吗?')) {
deleteProduct(this.dataset.id);
}
});
});
});
// 加载产品数据到编辑模态框
function loadProductData(id) {
fetch(`/product/${id}/edit`)
.then(response => response.json())
.then(data => {
document.getElementById('edit_name').value = data.name;
document.getElementById('edit_price').value = data.price;
document.getElementById('edit_description').value = data.description;
// 设置表单action
document.getElementById('editForm').action = `/product/${id}/update`;
})
.catch(error => console.error('Error:', error));
}
// 更新产品
function updateProduct(form) {
const formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 关闭模态框
const modal = bootstrap.Modal.getInstance(document.getElementById('editModal'));
modal.hide();
// 刷新页面或更新表格
location.reload();
} else {
alert('更新失败: ' + data.errors.join(', '));
}
})
.catch(error => console.error('Error:', error));
}
// 删除产品
function deleteProduct(id) {
fetch(`/product/${id}/delete`, {
method: 'DELETE',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('删除失败');
}
})
.catch(error => console.error('Error:', error));
}
// AJAX创建产品
document.getElementById('createForm')?.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
fetch(this.action, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
const modal = bootstrap.Modal.getInstance(document.getElementById('createModal'));
modal.hide();
location.reload();
} else {
alert('创建失败: ' + data.errors.join(', '));
}
})
.catch(error => console.error('Error:', error));
});
编辑控制器方法
// 添加到 ProductController.php
#[Route('/product/{id}/edit', name: 'product_edit_data')]
public function editData(Product $product): Response
{
return $this->json([
'id' => $product->getId(),
'name' => $product->getName(),
'price' => $product->getPrice(),
'description' => $product->getDescription(),
]);
}
#[Route('/product/{id}/update', name: 'product_update', methods: ['POST'])]
public function update(Request $request, Product $product, EntityManagerInterface $em): Response
{
$form = $this->createForm(ProductType::class, $product);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->flush();
if ($request->isXmlHttpRequest()) {
return $this->json([
'success' => true,
'product' => [
'id' => $product->getId(),
'name' => $product->getName(),
'price' => $product->getPrice(),
]
]);
}
return $this->redirectToRoute('product_list');
}
return $this->json([
'success' => false,
'errors' => $this->getFormErrors($form)
], 400);
}
#[Route('/product/{id}/delete', name: 'product_delete', methods: ['DELETE'])]
public function delete(Product $product, EntityManagerInterface $em): Response
{
$em->remove($product);
$em->flush();
return $this->json(['success' => true]);
}
最佳实践建议
表单验证
// 在实体中添加验证
use Symfony\Component\Validator\Constraints as Assert;
class Product
{
/**
* @Assert\NotBlank(message="产品名称不能为空")
* @Assert\Length(min=2, max=50)
*/
private $name;
/**
* @Assert\NotBlank
* @Assert\Positive
*/
private $price;
}
CSRF保护
{# 在模态框表单中添加CSRF token #}
<input type="hidden" name="_token" value="{{ csrf_token('product-form') }}">
这样你就拥有了一个完整的Symfony表单与模态框的结合解决方案,包括创建、编辑和删除功能。