andanteproject / page-filter-form-bundle
一个简化管理面板中列表/表格页面过滤器处理的 Symfony Bundle。
Requires
- php: ^7.4 || ^8.0
- symfony/form: ^4.0 || ^5.0 || ^6.0 || ^7.0
Requires (Dev)
- ext-json: *
- friendsofphp/php-cs-fixer: ^3.58
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^1.11
- phpstan/phpstan-phpunit: ^1.4
- phpstan/phpstan-symfony: ^1.4
- phpunit/phpunit: ^9.6
- roave/security-advisories: dev-master
- symfony/framework-bundle: ^4.0 | ^5.0 | ^6.0 | ^7.0
This package is auto-updated.
Last update: 2024-08-29 11:10:55 UTC
README
页面过滤器表单Bundle
Symfony Bundle - AndanteProject
一个简化管理面板中列表/表格页面过滤器处理的 Symfony Bundle。🧪
需求
Symfony 4.x-7.x 和 PHP 7.4-8.0。
特性
- 使用 Symfony Form;
- 默认保持 URL 参数清洁,如
?search=value&otherFilterName=anotherValue
; - 即使你在网页中 表单标签外部 渲染表单元素,也可以使表单工作,正好满足你的需求,避免嵌套表单冲突。
- 易于实现和维护;
- 效果如同魔法 ✨。
如何安装
安装后,确保你的 Bundle 已在 symfony bundles 列表中注册(config/bundles.php
)
return [ /// bundles... Andante\PageFilterFormBundle\AndantePageFilterFormBundle::class => ['all' => true], /// bundles... ];
如果你使用的是 Symfony Flex,这将自动完成。否则,请自行注册。
问题
假设你有一个常见的行政面板控制器,其中有一个页面列出了一些 Employee
实体。
<?php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use App\Repository\EmployeeRepository; use Knp\Component\Pager\PaginatorInterface; class EmployeeController extends AbstractController{ public function index(Request $request, EmployeeRepository $employeeRepository, PaginatorInterface $paginator){ /** @var Doctrine\ORM\QueryBuilder $qb */ $qb = $employeeRepository->getFancyQueryBuilderLogic('employee'); $employees = $paginator->paginate($qb, $request); return $this->render('admin/employee/index.html.twig', [ 'employees' => $employees, ]); } }
要给这个页面添加过滤器,让我们创建一个 Symfony 表单。
<?php namespace App\Form\Admin; use Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; class EmployeeFilterType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('search', Type\SearchType::class); $builder->add('senior', Type\CheckboxType::class); $builder->add('orderBy', Type\ChoiceType::class, [ 'choices' => [ 'name' => 'name', 'age' => 'birthday' ], ]); } }
让我们将这个表单添加到我们的控制器页面
<?php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use App\Repository\EmployeeRepository; use Knp\Component\Pager\PaginatorInterface; use App\Form\Admin\EmployeeFilterType; class EmployeeController extends AbstractController{ public function index(Request $request, EmployeeRepository $employeeRepository, PaginatorInterface $paginator){ /** @var Doctrine\ORM\QueryBuilder $qb */ $qb = $employeeRepository->getFancyQueryBuilderLogic('employee'); $form = $this->createForm(EmployeeFilterType::class); $form->handleRequest($request); if($form->isSubmitted() && $form->isValid()){ $qb->expr()->like('employee.name',':name'); $qb->setParameter('name', $form->get('search')->getData()); $qb->expr()->like('employee.senior',':senior'); $qb->setParameter('senior', $form->get('senior')->getData()); $qb->orderBy('employee.'. $form->get('orderBy')->getData(), 'asc'); // Don't you see the problem here? } $employees = $paginator->paginate($qb, $request); return $this->render('admin/employee/index.html.twig', [ 'employees' => $employees, 'form' => $form->createView() ]); } }
上面的代码有一些严重问题
- 👎 在控制器中处理所有这些过滤逻辑不是一个好主意。当然,你可以将其移动到一个专用服务中,但这意味着我们需要创建一个与
EmployeeFilterType
一起的另一个文件类来处理过滤,但这甚至没有解决这个列表的第二个问题; - 👎 你需要携带和匹配表单元素名称。如
search
、senior
和orderBy
这样的键可以存储在某个常量中以避免重复,但随着过滤逻辑的增长,这会让你发疯。
使用页面过滤器表的解决方案
将 Andante\PageFilterFormBundle\Form\PageFilterType
作为你的过滤表单的父类(为什么?)并在你的表单元素上实现 target_callback
选项,如下所示
<?php namespace App\Form\Admin; use Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Andante\PageFilterFormBundle\Form\PageFilterType; use Doctrine\ORM\QueryBuilder; class EmployeeFilterType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('search', Type\SearchType::class, [ 'target_callback' => function(QueryBuilder $qb, ?string $searchValue):void { $qb->expr()->like('employee.name',':name'); // Don't want to guess for entity alias "employee"? $qb->setParameter('name', $searchValue); // Check andanteproject/shared-query-builder } ]); $builder->add('senior', Type\CheckboxType::class, [ 'target_callback' => function(QueryBuilder $qb, bool $seniorValue):void { $qb->expr()->like('employee.senior',':senior'); $qb->setParameter('senior', $seniorValue); } ]); $builder->add('orderBy', Type\ChoiceType::class, [ 'choices' => [ 'name' => 'name', 'age' => 'birthday' ], 'target_callback' => function(QueryBuilder $qb, string $orderByValue):void { $qb->orderBy('employee.'. $orderByValue, 'asc'); } ]); } public function getParent() : string { return PageFilterType::class; } }
在你的控制器中实现 Andante\PageFilterFormBundle\PageFilterFormTrait
(或注入一个 Andante\PageFilterFormBundle\PageFilterManagerInterface
参数)并像这样使用表单
<?php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use App\Repository\EmployeeRepository; use Knp\Component\Pager\PaginatorInterface; use App\Form\Admin\EmployeeFilterType; use Andante\PageFilterFormBundle\PageFilterFormTrait; class EmployeeController extends AbstractController{ use PageFilterFormTrait; public function index(Request $request, EmployeeRepository $employeeRepository, PaginatorInterface $paginator){ /** @var Doctrine\ORM\QueryBuilder $qb */ $qb = $employeeRepository->getFancyQueryBuilderLogic('employee'); $form = $this->createAndHandleFilter(EmployeeFilterType::class, $qb, $request); $employees = $paginator->paginate($qb, $request); return $this->render('admin/employee/index.html.twig', [ 'employees' => $employees, 'form' => $form->createView() ]); } }
✅ 完成!
- 👍 控制器干净且易于阅读;
- 👍 只有一个类负责处理过滤;
- 👍 选项
target_callback
允许你避免携带表单元素名称; - 👍 你可以对可调用参数进行类型提示 🥰(检查回调参数);
- 👍 我们解决了可能的嵌套表单问题(如何?);
"target_callback" 选项
target_callback
类型: null
或 callable
默认值: null
该 callable
将有 3 个参数(第三个是可选的)
为什么使用 PageFilterType 作为表单的父类
您可以选择不使用 Andante\PageFilterFormBundle\Form\PageFilterType
作为表单的父类,但请注意它设置了一些您可能希望复制的有用默认值。
在 twig 中渲染表单
只要 andante_smart_form_attr
设置为 true
,您就可以这样渲染表单
<div class="header-filters"> {{ form_start(form) }} {# id="list_filter" #} {{ form_errors(form) }} {{ form_row(form.search) }} {{ form_row(form.orderBy) }} {{ form_end(form, {'render_rest': false}) }} </div> <!-- --> <!-- Some other HTML content, like a table or even another Symfony form --> <!-- --> <div class="footer-filters"> {{ form_row(form.orderBy) }} {# has attribute form="list_filter" #} </div>
✅ form.perPage
元素即使在表单标签外部也能正常工作。(这是怎么回事?!)
给我们一颗 ⭐!
由 AndanteProject 团队用爱 ❤️ 构建而成。