承接 andanteproject/page-filter-form-bundle 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

andanteproject/page-filter-form-bundle

Composer 安装命令:

composer require andanteproject/page-filter-form-bundle

包简介

A Symfony Bundle to simplify the handling of page filters for lists/tables in admin panels.

README 文档

README

Andante Project Logo

Page Filter Form Bundle

Symfony Bundle - AndanteProject

Latest Version Github actions Framework Php7 PhpStan

A Symfony Bundle to simplify the handling of page filters for lists/tables in admin panels. 🧪

Requirements

Symfony 4.x-7.x and PHP 7.4-8.0.

Features

  • Use Symfony Form;
  • Keep your URL parameters clean as ?search=value&otherFilterName=anotherValue by default;
  • Form will work even if you render form elements outside the form tag, around the web page, exactly where you need, avoiding nested form conflicts.
  • Super easy to implement and maintain;
  • Works like magic ✨.

How to install

After installation, make sure you have the bundle registered in your Symfony bundles list (config/bundles.php):

return [
    /// bundles...
    Andante\PageFilterFormBundle\AndantePageFilterFormBundle::class => ['all' => true],
    /// bundles...
];

This should have been done automatically if you are using Symfony Flex. Otherwise, register it yourself.

The problem

Let's suppose you have this common admin panel controller with a page listing some Employee entities.

<?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,
        ]);
    }
}

To add filters to this page, let's create a Symfony form.

<?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'     
            ],
        ]);
    }
}

Let's add this Form to our controller page:

<?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()
        ]);
    }
}

The code above has some huge problems:

  • 👎 Handling all this filter logic inside the controller is not a good idea. Sure, you can move it into a dedicated service, but that means creating another class file alongside EmployeeFilterType to handle filters, and that still does not solve the second point in this list;
  • 👎 You need to carry around and match form element names. search, senior and orderBy are keys you could store in constants to avoid repeating yourself, but this will drive you crazy as the filter logic grows.

The solution with Page Filter Form

Use Andante\PageFilterFormBundle\Form\PageFilterType as parent of your filter form (why?) and implement target_callback option on your form elements like this:

<?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;
    }
}

Implement Andante\PageFilterFormBundle\PageFilterFormTrait in your controller (or inject Andante\PageFilterFormBundle\PageFilterManagerInterface as an argument) and use the form like this:

<?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()
        ]);
    }
}

✅ Done!

  • 👍 Controller is clean and easy to read;
  • 👍 We have just one class taking care of filters;
  • 👍 The option target_callback lets you avoid repeating yourself and carrying around form element names;
  • 👍 You can type-hint your callable 🥰 (check callback arguments);
  • 👍 We got you covered solving possible nested form problems (how?);

"target_callback" option

target_callback

type: null or callable default: null

The callable is going to have 3 parameters (third is optional):

Parameter What Mandatory Description
1 Filter $target yes The second argument of createAndHandleFilter. It can be whatever you want: a query builder, an array, a collection, an object. It does not matter as long as you match its type with this argument signature.
2 form data yes Equivalent to calling $form->getData() on the current form field. It will be a ?string for a TextType or a ?\DateTime for a DateTimeType.
3 form itself no The current $form instance.

Why use PageFilterType as form parent

You could avoid using Andante\PageFilterFormBundle\Form\PageFilterType as the parent of your form, but be aware it sets some useful defaults you may want to replicate:

Option Value Description
method GET You probably want filters to be part of the page URL, right?
csrf_protection false So users can share the page URL with others without running into problems.
allow_extra_fields true Allows other URL parameters outside your form values.
andante_smart_form_attr true Enables form elements to be rendered wherever you want on your page, even outside the form tag, while keeping them working properly (discover more).

Render the form in twig

As long as andante_smart_form_attr is true, you can render your form like this:

<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>

✅ The form.perPage element works properly even outside the form tag (how?!).

Give us a ⭐!

Built with love ❤️ by AndanteProject team.

andanteproject/page-filter-form-bundle 适用场景与选型建议

andanteproject/page-filter-form-bundle 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 87.4k 次下载、GitHub Stars 达 10, 最近一次更新时间为 2021 年 03 月 13 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「symfony」 「php」 「form」 「filters」 「admin-panel」 「symfony-bundle」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 andanteproject/page-filter-form-bundle 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 andanteproject/page-filter-form-bundle 我们能提供哪些服务?
定制开发 / 二次开发

基于 andanteproject/page-filter-form-bundle 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 87.4k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 10
  • 点击次数: 27
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 10
  • Watchers: 2
  • Forks: 2
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2021-03-13