PHP项目Symfony form与模态框

wen PHP项目 2

本文目录导读:

PHP项目Symfony form与模态框

  1. 基本结构
  2. 创建Symfony表单
  3. 控制器处理
  4. 编辑模态框
  5. JavaScript处理
  6. 编辑控制器方法
  7. 最佳实践建议

我来为你详细介绍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表单与模态框的结合解决方案,包括创建、编辑和删除功能。

抱歉,评论功能暂时关闭!