PHP项目Symfony form与批量操作

wen PHP项目 3

本文目录导读:

PHP项目Symfony form与批量操作

  1. Symfony Form 基础
  2. 控制器实现批量操作
  3. 前端模板实现
  4. 改进版:使用Symfony表单集合处理批量编辑
  5. 高级功能:带确认步骤的批量操作
  6. 最佳实践建议

在Symfony项目中,表单(Form)与批量操作(Batch Actions)的结合是常见的需求,下面我会详细说明如何实现,并提供完整的示例代码。

Symfony Form 基础

创建一个用于批量操作的表单类型:

// src/Form/BatchActionType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class BatchActionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('action', ChoiceType::class, [
                'label' => '批量操作',
                'choices' => [
                    '删除选中' => 'delete',
                    '激活' => 'activate',
                    '禁用' => 'deactivate',
                    '导出CSV' => 'export_csv',
                ],
                'placeholder' => '选择操作',
            ])
            ->add('items', HiddenType::class, [
                'mapped' => false, // 不映射到实体
            ])
            ->add('apply', SubmitType::class, [
                'label' => '执行',
                'attr' => ['class' => 'btn btn-primary']
            ]);
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'csrf_protection' => true,
            'csrf_field_name' => '_token',
            'csrf_token_id' => 'batch_action',
        ]);
    }
}

控制器实现批量操作

// src/Controller/Admin/ProductController.php
namespace App\Controller\Admin;
use App\Form\BatchActionType;
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('/admin/products/batch', name: 'admin_products_batch', methods: ['POST'])]
    public function batchAction(
        Request $request,
        ProductRepository $productRepository,
        EntityManagerInterface $entityManager
    ): Response {
        $form = $this->createForm(BatchActionType::class);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $action = $form->get('action')->getData();
            $selectedIds = $request->request->all('selected_ids', []);
            if (empty($selectedIds)) {
                $this->addFlash('warning', '请选择要操作的项目');
                return $this->redirectToRoute('admin_products_index');
            }
            $products = $productRepository->findBy(['id' => $selectedIds]);
            switch ($action) {
                case 'delete':
                    $this->deleteProducts($products, $entityManager);
                    break;
                case 'activate':
                    $this->updateProductStatus($products, true, $entityManager);
                    break;
                case 'deactivate':
                    $this->updateProductStatus($products, false, $entityManager);
                    break;
                case 'export_csv':
                    return $this->exportProductsToCsv($products);
                default:
                    $this->addFlash('error', '未知的操作类型');
            }
            $this->addFlash('success', sprintf('批量操作完成,处理了 %d 个项目', count($products)));
        }
        return $this->redirectToRoute('admin_products_index');
    }
    private function deleteProducts(array $products, EntityManagerInterface $entityManager): void
    {
        foreach ($products as $product) {
            $entityManager->remove($product);
        }
        $entityManager->flush();
    }
    private function updateProductStatus(array $products, bool $active, EntityManagerInterface $entityManager): void
    {
        foreach ($products as $product) {
            $product->setActive($active);
        }
        $entityManager->flush();
    }
    private function exportProductsToCsv(array $products): Response
    {
        $csvData = [];
        // 添加表头
        $csvData[] = ['ID', '名称', '价格', '状态'];
        foreach ($products as $product) {
            $csvData[] = [
                $product->getId(),
                $product->getName(),
                $product->getPrice(),
                $product->isActive() ? '激活' : '禁用'
            ];
        }
        $response = new Response();
        $response->headers->set('Content-Type', 'text/csv');
        $response->headers->set('Content-Disposition', 'attachment; filename="products_export.csv"');
        $handle = fopen('php://temp', 'r+');
        foreach ($csvData as $row) {
            fputcsv($handle, $row);
        }
        rewind($handle);
        $response->setContent(stream_get_contents($handle));
        fclose($handle);
        return $response;
    }
}

前端模板实现

{# templates/admin/product/index.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<div class="container-fluid">
    <h1>产品管理</h1>
    {# 批量操作表单 #}
    {{ form_start(batchForm, {'attr': {'id': 'batch-form', 'class': 'mb-3'}}) }}
    <div class="row align-items-center">
        <div class="col-md-3">
            {{ form_widget(batchForm.action, {'attr': {'class': 'form-select'}}) }}
        </div>
        <div class="col-md-9">
            {{ form_widget(batchForm.apply) }}
        </div>
    </div>
    {{ form_end(batchForm) }}
    {# 产品列表 #}
    <table class="table table-striped" id="product-table">
        <thead>
            <tr>
                <th>
                    <input type="checkbox" id="select-all" class="form-check-input">
                </th>
                <th>ID</th>
                <th>名称</th>
                <th>价格</th>
                <th>状态</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
            {% for product in products %}
            <tr>
                <td>
                    <input type="checkbox" name="selected_ids[]" value="{{ product.id }}" class="form-check-input product-checkbox">
                </td>
                <td>{{ product.id }}</td>
                <td>{{ product.name }}</td>
                <td>{{ product.price|number_format(2) }} 元</td>
                <td>
                    {% if product.active %}
                        <span class="badge bg-success">激活</span>
                    {% else %}
                        <span class="badge bg-danger">禁用</span>
                    {% endif %}
                </td>
                <td>
                    <a href="{{ path('admin_product_edit', {'id': product.id}) }}" class="btn btn-sm btn-primary">编辑</a>
                </td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
    {# 分页 #}
    {{ knp_pagination_render(products) }}
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
document.addEventListener('DOMContentLoaded', function() {
    // 全选/取消全选
    const selectAllCheckbox = document.getElementById('select-all');
    const productCheckboxes = document.querySelectorAll('.product-checkbox');
    selectAllCheckbox.addEventListener('change', function() {
        productCheckboxes.forEach(checkbox => {
            checkbox.checked = this.checked;
        });
    });
    // 批量操作表单提交
    const batchForm = document.getElementById('batch-form');
    batchForm.addEventListener('submit', function(e) {
        const selectedCheckboxes = document.querySelectorAll('.product-checkbox:checked');
        if (selectedCheckboxes.length === 0) {
            e.preventDefault();
            alert('请至少选择一个项目');
            return false;
        }
        // 确认操作
        const action = document.querySelector('[name="batch_action[action]"]').value;
        if (action === 'delete') {
            if (!confirm(`确定要删除选中的 ${selectedCheckboxes.length} 个项目吗?`)) {
                e.preventDefault();
                return false;
            }
        }
    });
});
</script>
{% endblock %}

改进版:使用Symfony表单集合处理批量编辑

如果需要批量编辑多个实体,可以使用表单集合:

// src/Form/BatchEditType.php
namespace App\Form;
use App\Entity\Product;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
class BatchEditType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('products', CollectionType::class, [
                'entry_type' => ProductBatchType::class,
                'allow_add' => false,
                'allow_delete' => false,
                'by_reference' => false,
            ])
            ->add('save', SubmitType::class, [
                'label' => '批量保存',
                'attr' => ['class' => 'btn btn-primary']
            ]);
    }
}
// src/Form/ProductBatchType.php
namespace App\Form;
use App\Entity\Product;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProductBatchType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name', TextType::class, [
                'label' => '名称',
            ])
            ->add('price', NumberType::class, [
                'label' => '价格',
                'scale' => 2,
            ])
            ->add('active', CheckboxType::class, [
                'label' => '激活',
                'required' => false,
            ]);
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Product::class,
        ]);
    }
}

高级功能:带确认步骤的批量操作

// src/Form/BatchConfirmType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
class BatchConfirmType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('ids', HiddenType::class)
            ->add('action', HiddenType::class)
            ->add('confirm_note', TextareaType::class, [
                'label' => '操作说明',
                'required' => false,
                'attr' => ['rows' => 3]
            ])
            ->add('confirm', SubmitType::class, [
                'label' => '确认执行',
                'attr' => ['class' => 'btn btn-danger']
            ])
            ->add('cancel', SubmitType::class, [
                'label' => '取消',
                'attr' => ['class' => 'btn btn-secondary']
            ]);
    }
}

最佳实践建议

  1. 使用事务:批量操作时使用数据库事务确保数据一致性
  2. 权限检查:确保用户有执行批量操作的权限
  3. 性能优化:对于大量数据,使用查询构建器而非逐个操作
  4. 日志记录:记录批量操作的详细信息
  5. 错误处理:提供详细的错误信息和回滚机制
  6. 前端反馈:使用Ajax或加载指示器提供良好的用户体验

这个完整的实现涵盖了Symfony项目中表单与批量操作的各种场景,你可以根据具体需求进行调整。

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