本文目录导读:

我来详细介绍 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支持 - 异步筛选体验
- 性能优化 - 缓存策略
- 验证 - 确保数据安全
你可以根据项目需求调整和扩展这些功能。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播