PHP项目Symfony form与过滤器

wen PHP项目 2

本文目录导读:

PHP项目Symfony form与过滤器

  1. 基础表单过滤器
  2. 高级过滤器(使用DTO)
  3. Repository查询构建器
  4. Controller实现
  5. Twig模板
  6. 高级特性:AJAX筛选
  7. 最佳实践

我来详细介绍 Symfony 表单与过滤器的结合使用。

基础表单过滤器

创建过滤器表单类型

<?php
// src/Form/UserFilterType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\SearchType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserFilterType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('search', SearchType::class, [
                'label' => '搜索',
                'required' => false,
                'attr' => [
                    'placeholder' => '输入关键词...',
                    'class' => 'form-control'
                ]
            ])
            ->add('status', ChoiceType::class, [
                'label' => '状态',
                'required' => false,
                'placeholder' => '所有状态',
                'choices' => [
                    '启用' => 'active',
                    '禁用' => 'inactive',
                    '待验证' => 'pending'
                ]
            ])
            ->add('role', ChoiceType::class, [
                'label' => '角色',
                'required' => false,
                'placeholder' => '所有角色',
                'choices' => [
                    '管理员' => 'ROLE_ADMIN',
                    '用户' => 'ROLE_USER',
                    '编辑' => 'ROLE_EDITOR'
                ],
                'expanded' => false,  // 下拉选择
                'multiple' => false
            ])
            ->add('dateFrom', DateType::class, [
                'label' => '开始日期',
                'required' => false,
                'widget' => 'single_text',
                'attr' => ['class' => 'form-control datepicker']
            ])
            ->add('dateTo', DateType::class, [
                'label' => '结束日期',
                'required' => false,
                'widget' => 'single_text',
                'attr' => ['class' => 'form-control datepicker']
            ])
            ->add('submit', SubmitType::class, [
                'label' => '筛选',
                'attr' => ['class' => 'btn btn-primary']
            ])
            ->add('reset', SubmitType::class, [
                'label' => '重置',
                'attr' => ['class' => 'btn btn-secondary']
            ]);
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'method' => 'GET',
            'csrf_protection' => false,  // GET请求通常不需要CSRF
            'attr' => ['class' => 'filter-form']
        ]);
    }
}

高级过滤器(使用DTO)

创建过滤器DTO

<?php
// src/Filter/UserFilter.php
namespace App\Filter;
use Symfony\Component\Validator\Constraints as Assert;
class UserFilter
{
    private ?string $search = null;
    private ?string $status = null;
    private ?array $roles = null;
    private ?\DateTimeInterface $dateFrom = null;
    private ?\DateTimeInterface $dateTo = null;
    private ?string $sortBy = 'createdAt';
    private ?string $sortOrder = 'DESC';
    // Getters and Setters
    public function getSearch(): ?string
    {
        return $this->search;
    }
    public function setSearch(?string $search): self
    {
        $this->search = $search;
        return $this;
    }
    // 其他getter/setter...
}

创建匹配的FilterType

<?php
// src/Form/AdvancedUserFilterType.php
namespace App\Form;
use App\Filter\UserFilter;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AdvancedUserFilterType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        // 多选角色
        $builder->add('roles', ChoiceType::class, [
            'label' => '角色',
            'required' => false,
            'multiple' => true,
            'expanded' => true,  // 复选框
            'choices' => [
                'ROLE_ADMIN' => '管理员',
                'ROLE_USER' => '用户',
                'ROLE_EDITOR' => '编辑'
            ]
        ]);
        // 排序
        $builder->add('sortBy', ChoiceType::class, [
            'label' => '排序字段',
            'required' => false,
            'choices' => [
                '创建时间' => 'createdAt',
                '用户名' => 'username',
                '邮箱' => 'email',
                '最后登录' => 'lastLogin'
            ]
        ]);
        $builder->add('sortOrder', ChoiceType::class, [
            'label' => '排序方式',
            'required' => false,
            'choices' => [
                '降序' => 'DESC',
                '升序' => 'ASC'
            ]
        ]);
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => UserFilter::class,
            'method' => 'GET',
            'csrf_protection' => false,
            'allow_extra_fields' => true
        ]);
    }
}

Repository查询构建器

<?php
// src/Repository/UserRepository.php
namespace App\Repository;
use App\Filter\UserFilter;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class UserRepository extends ServiceEntityRepository
{
    public function findByFilter(UserFilter $filter): array
    {
        $qb = $this->createQueryBuilder('u');
        // 搜索
        if ($filter->getSearch()) {
            $qb->andWhere('u.username LIKE :search OR u.email LIKE :search')
               ->setParameter('search', '%' . $filter->getSearch() . '%');
        }
        // 状态筛选
        if ($filter->getStatus()) {
            $qb->andWhere('u.status = :status')
               ->setParameter('status', $filter->getStatus());
        }
        // 角色筛选
        if ($filter->getRoles()) {
            $qb->andWhere('u.roles LIKE :roles')
               ->setParameter('roles', '%' . $filter->getRoles() . '%');
        }
        // 日期范围
        if ($filter->getDateFrom()) {
            $qb->andWhere('u.createdAt >= :dateFrom')
               ->setParameter('dateFrom', $filter->getDateFrom());
        }
        if ($filter->getDateTo()) {
            $qb->andWhere('u.createdAt <= :dateTo')
               ->setParameter('dateTo', $filter->getDateTo());
        }
        // 排序
        $sortBy = $filter->getSortBy() ?: 'createdAt';
        $sortOrder = $filter->getSortOrder() ?: 'DESC';
        $qb->orderBy('u.' . $sortBy, $sortOrder);
        return $qb->getQuery()->getResult();
    }
}

Controller实现

<?php
// src/Controller/Admin/UserController.php
namespace App\Controller\Admin;
use App\Form\AdvancedUserFilterType;
use App\Filter\UserFilter;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class UserController extends AbstractController
{
    #[Route('/admin/users', name: 'admin_users')]
    public function index(Request $request, UserRepository $userRepository): Response
    {
        $filter = new UserFilter();
        $form = $this->createForm(AdvancedUserFilterType::class, $filter, [
            'action' => $this->generateUrl('admin_users'),
            'method' => 'GET'
        ]);
        $form->handleRequest($request);
        // 保存筛选条件到session(可选)
        if ($form->isSubmitted() && $form->isValid()) {
            $session = $request->getSession();
            $session->set('user_filter', $filter);
        } elseif ($request->query->count() === 0 && $session = $request->getSession()) {
            // 从session恢复上次的筛选条件
            $savedFilter = $session->get('user_filter');
            if ($savedFilter) {
                $filter = $savedFilter;
                $form = $this->createForm(AdvancedUserFilterType::class, $filter, [
                    'action' => $this->generateUrl('admin_users'),
                    'method' => 'GET'
                ]);
            }
        }
        // 获取结果
        $users = $userRepository->findByFilter($filter);
        // 导出功能
        if ($request->query->get('export') === 'csv') {
            return $this->exportCsv($users);
        }
        return $this->render('admin/users/index.html.twig', [
            'users' => $users,
            'filter_form' => $form->createView(),
            'activeFilters' => $filter
        ]);
    }
    private function exportCsv(array $users): Response
    {
        $response = new Response();
        $response->headers->set('Content-Type', 'text/csv');
        $response->headers->set('Content-Disposition', 'attachment; filename="users.csv"');
        $output = fopen('php://temp', 'r+');
        fputcsv($output, ['ID', '用户名', '邮箱', '状态', '创建时间']);
        foreach ($users as $user) {
            fputcsv($output, [
                $user->getId(),
                $user->getUsername(),
                $user->getEmail(),
                $user->getStatus(),
                $user->getCreatedAt()->format('Y-m-d H:i:s')
            ]);
        }
        rewind($output);
        $response->setContent(stream_get_contents($output));
        fclose($output);
        return $response;
    }
}

Twig模板

{# templates/admin/users/index.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<div class="container-fluid">
    <h1>用户管理</h1>
    <!-- 筛选表单 -->
    <div class="card mb-4">
        <div class="card-header">
            <button class="btn btn-link" data-toggle="collapse" data-target="#filterCollapse">
                筛选条件
                {% if activeFilters.search or activeFilters.status %}
                    <span class="badge badge-info">已筛选</span>
                {% endif %}
            </button>
        </div>
        <div class="card-body collapse" id="filterCollapse">
            {{ form_start(filter_form, {'attr': {'class': 'form-inline'}}) }}
                {% for field in filter_form %}
                    <div class="form-group mr-3 mb-2">
                        {{ form_label(field) }}
                        {{ form_widget(field, {'attr': {'class': 'form-control'}}) }}
                    </div>
                {% endfor %}
                <div class="form-group mb-2">
                    <button type="submit" class="btn btn-primary mr-2">
                        <i class="fas fa-search"></i> 筛选
                    </button>
                    <a href="{{ path('admin_users') }}" class="btn btn-secondary">
                        <i class="fas fa-undo"></i> 重置
                    </a>
                </div>
            {{ form_end(filter_form) }}
        </div>
    </div>
    <!-- 操作按钮 -->
    <div class="mb-3">
        <a href="{{ path('admin_users', app.request.query.all|merge({'export': 'csv'})) }}" 
           class="btn btn-success">
            <i class="fas fa-download"></i> 导出CSV
        </a>
    </div>
    <!-- 用户列表 -->
    <table class="table table-striped">
        <thead>
            <tr>
                <th>ID</th>
                <th>用户名</th>
                <th>邮箱</th>
                <th>状态</th>
                <th>角色</th>
                <th>创建时间</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
            {% for user in users %}
            <tr>
                <td>{{ user.id }}</td>
                <td>{{ user.username }}</td>
                <td>{{ user.email }}</td>
                <td>
                    <span class="badge badge-{{ user.status == 'active' ? 'success' : 'danger' }}">
                        {{ user.status }}
                    </span>
                </td>
                <td>{{ user.roles|join(', ') }}</td>
                <td>{{ user.createdAt|date('Y-m-d H:i') }}</td>
                <td>
                    <a href="#" class="btn btn-sm btn-info">编辑</a>
                    <a href="#" class="btn btn-sm btn-danger">删除</a>
                </td>
            </tr>
            {% else %}
            <tr>
                <td colspan="7" class="text-center">没有找到用户</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
    <!-- 分页 -->
    {% if users|length > 0 %}
    <nav>
        <ul class="pagination">
            <li class="page-item"><a class="page-link" href="#">上一页</a></li>
            <li class="page-item active"><a class="page-link" href="#">1</a></li>
            <li class="page-item"><a class="page-link" href="#">2</a></li>
            <li class="page-item"><a class="page-link" href="#">3</a></li>
            <li class="page-item"><a class="page-link" href="#">下一页</a></li>
        </ul>
    </nav>
    {% endif %}
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
// 自动提交筛选表单
$(document).ready(function() {
    $('.filter-form select').change(function() {
        $(this).closest('form').submit();
    });
    // 重置按钮
    $('.reset-filter').click(function(e) {
        e.preventDefault();
        window.location.href = '{{ path('admin_users') }}';
    });
});
</script>
{% endblock %}

高级特性:AJAX筛选

// 支持AJAX的控制器
#[Route('/admin/users/async', name: 'admin_users_async')]
public function asyncFilter(Request $request, UserRepository $userRepository): Response
{
    $filter = new UserFilter();
    $form = $this->createForm(AdvancedUserFilterType::class, $filter, [
        'method' => 'GET',
        'csrf_protection' => false
    ]);
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
        $users = $userRepository->findByFilter($filter);
        return $this->json([
            'success' => true,
            'html' => $this->renderView('admin/users/_table.html.twig', [
                'users' => $users
            ]),
            'count' => count($users)
        ]);
    }
    return $this->json(['success' => false]);
}

最佳实践

优化性能

<?php
// 添加缓存
use Symfony\Contracts\Cache\CacheInterface;
public function findByFilterCached(UserFilter $filter, CacheInterface $cache): array
{
    $cacheKey = 'users_filter_' . md5(serialize($filter));
    return $cache->get($cacheKey, function() use ($filter) {
        return $this->findByFilter($filter);
    }, 3600); // 缓存1小时
}

验证筛选条件

<?php
// 添加验证约束
use Symfony\Component\Validator\Constraints as Assert;
class UserFilter
{
    // 添加验证
    #[Assert\Length(max: 255)]
    private ?string $search = null;
    #[Assert\Choice(choices: ['active', 'inactive', 'pending'], message: '无效的状态')]
    private ?string $status = null;
    #[Assert\Type(\DateTimeInterface::class)]
    #[Assert\LessThanOrEqual(propertyPath: 'dateTo', message: '开始日期不能晚于结束日期')]
    private ?\DateTimeInterface $dateFrom = null;
    // ...
}

这个完整方案提供了:

  1. 灵活的表单筛选 - 支持各种字段类型
  2. DTO模式 - 清晰的业务逻辑分离
  3. 安全查询 - 防止SQL注入
  4. Session保持 - 保存用户筛选偏好
  5. 导出功能 - 支持CSV/Excel导出
  6. AJAX支持 - 异步筛选体验
  7. 性能优化 - 缓存策略
  8. 验证 - 确保数据安全

你可以根据项目需求调整和扩展这些功能。

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