本文目录导读:

我来详细介绍 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;
// ...
}
这个完整方案提供了:
- 灵活的表单筛选 - 支持各种字段类型
- DTO模式 - 清晰的业务逻辑分离
- 安全查询 - 防止SQL注入
- Session保持 - 保存用户筛选偏好
- 导出功能 - 支持CSV/Excel导出
- AJAX支持 - 异步筛选体验
- 性能优化 - 缓存策略
- 验证 - 确保数据安全
你可以根据项目需求调整和扩展这些功能。