ttskch / paginator-bundle
为 Symfony 服务的最轻量、最简单且可定制的分页器包
6.2.0
2024-08-28 02:30 UTC
Requires
- php: ^8.0
- symfony/config: ^5.0|^6.0|^7.0
- symfony/dependency-injection: ^5.0|^6.0|^7.0
- symfony/form: ^5.0|^6.0|^7.0
- symfony/http-foundation: ^5.0|^6.0|^7.0
- twig/twig: >=3.4.3
Requires (Dev)
- ext-pcov: *
- bamarni/composer-bin-plugin: *
- doctrine/orm: ^2.17
- phpspec/prophecy-phpunit: ^2.1
- phpunit/phpunit: ^9.6
- symfony/browser-kit: ^5.0|^6.0|^7.0
- symfony/framework-bundle: ^5.0|^6.0|^7.0
- symfony/http-kernel: ^5.0|^6.0|^7.0
- symfony/phpunit-bridge: ^7.0
- symfony/twig-bundle: ^5.0|^6.0|^7.0
- symfony/yaml: ^5.0|^6.0|^7.0
README
为 Symfony 提供最轻量、最简单且可定制的分页器包。
功能
- 非常轻量
- 严格类型化(PHPStan 级别最大)
- 除了 Symfony 和 Twig 之外,不依赖任何其他内容
- 但也很容易与 Doctrine ORM 一起使用
- 当然可以分页任何内容
- 可定制的 twig-templated 视图
- 非常易用的 可排序链接 功能
- 易于与 您自己的搜索表单 一起使用
- 预设美观的 Bootstrap4/5 主题
需求
- PHP: ^8.0
- Symfony: ^5.0|^6.0|^7.0
演示
👉 在线演示在这里
您还可以在 demo/
目录 中查看示例代码。
安装
$ composer require ttskch/paginator-bundle
// config/bundles.php return [ // ... Ttskch\PaginatorBundle\TtskchPaginatorBundle::class => ['all' => true], ];
基本用法
与 Doctrine ORM 一起使用
// FooController.php use Symfony\Component\HttpFoundation\Response; use Ttskch\PaginatorBundle\Counter\Doctrine\ORM\QueryBuilderCounter; use Ttskch\PaginatorBundle\Criteria\Criteria; use Ttskch\PaginatorBundle\Paginator; use Ttskch\PaginatorBundle\Slicer\Doctrine\ORM\QueryBuilderSlicer; /** * @param Paginator<\Traversable<array-key, Foo>, Criteria> $paginator */ public function index(FooRepository $fooRepository, Paginator $paginator): Response { $qb = $fooRepository->createQueryBuilder('f'); $paginator->initialize(new QueryBuilderSlicer($qb), new QueryBuilderCounter($qb), new Criteria('id')); return $this->render('index.html.twig', [ 'foos' => $paginator->getSlice(), ]); }
{# index.html.twig #} <table> <thead> <tr> <th>{{ ttskch_paginator_sortable('id', 'Id') }}</th> <th>{{ ttskch_paginator_sortable('name', 'Name') }}</th> <th>{{ ttskch_paginator_sortable('email', 'Email') }}</th> </tr> </thead> <tbody> {% for foo in foos %} <tr> <td>{{ foo.id }}</td> <td>{{ foo.name }}</td> <td>{{ foo.email }}</td> </tr> {% endfor %} </tbody> </table> {{ ttskch_paginator_pager() }}
查看 src/Twig/TtskchPaginatorExtension.php 了解有关 twig 函数的更多信息。
按连接实体的属性排序
只需像以下这样做。
{# index.html.twig #} {# ... #} <th>{{ ttskch_paginator_sortable('id', 'Id') }}</th> <th>{{ ttskch_paginator_sortable('name', 'Name') }}</th> <th>{{ ttskch_paginator_sortable('email', 'Email') }}</th> <th>{{ ttskch_paginator_sortable('bar.id', 'Bar') }}</th> <th>{{ ttskch_paginator_sortable('bar.baz.id', 'Baz') }}</th> {# ... #}
使用数组
// FooController.php use Symfony\Component\HttpFoundation\Response; use Ttskch\PaginatorBundle\Counter\ArrayCounter; use Ttskch\PaginatorBundle\Criteria\Criteria; use Ttskch\PaginatorBundle\Paginator; use Ttskch\PaginatorBundle\Slicer\ArraySlicer; /** * @param Paginator<array<array{id: int, name: string, email: string}>, Criteria> $paginator */ public function index(Paginator $paginator): Response { $array = [ ['id' => 1, 'name' => 'Tommy Yount', 'email' => 'tommy_yount@gmail.com'], ['id' => 2, 'name' => 'Hye Panter', 'email' => 'hye_panter@gmail.com'], ['id' => 3, 'name' => 'Vi Yohe', 'email' => 'vi_yohe@gmail.com'], ['id' => 4, 'name' => 'Keva Bandy', 'email' => 'keva_bandy@gmail.com'], ['id' => 5, 'name' => 'Hannelore Corning', 'email' => 'hannelore_corning@gmail.com'], ['id' => 6, 'name' => 'Delorse Whitcher', 'email' => 'delorse_whitcher@gmail.com'], ['id' => 7, 'name' => 'Katharyn Marrinan', 'email' => 'katharyn_marrinan@gmail.com'], ['id' => 8, 'name' => 'Jeannine Tope', 'email' => 'jeannine_tope@gmail.com'], ['id' => 9, 'name' => 'Jamila Braggs', 'email' => 'jamila_braggs@gmail.com'], ['id' => 10, 'name' => 'Eden Cunniff', 'email' => 'eden_cunniff@gmail.com'], // ... ['id' => 299, 'name' => 'Deshawn Kennedy', 'email' => 'deshawn_kennedy@gmail.com'], ['id' => 300, 'name' => 'Elenore Evens', 'email' => 'elenore_evens@gmail.com'], ]; $paginator->initialize( new ArraySlicer($array), new ArrayCounter($array), new Criteria('id'), ); return $this->render('index.html.twig', [ 'foos' => $paginator->getSlice(), ]); }
使用其他类型的数据
按如下方式自行实现切片器和计数器。
use Symfony\Component\HttpFoundation\Response; use Ttskch\PaginatorBundle\Counter\CallbackCounter; use Ttskch\PaginatorBundle\Criteria\Criteria; use Ttskch\PaginatorBundle\Paginator; use Ttskch\PaginatorBundle\Slicer\CallbackSlicer; /** * @param Paginator<TypeOfYourOwnSlice>, Criteria> $paginator */ public function index(Paginator $paginator): Response { $yourOwnData = /* ... */; $paginator->initialize( new CallbackSlicer(function (Criteria $criteria) use ($yourOwnData) { /* ... */ return $yourOwnSlice; }), new CallbackCounter(function (Criteria $criteria) use ($yourOwnData) { /* ... */ return $totalItemsCount; }), new Criteria('default_sort_key'), ); return $this->render('index.html.twig', [ 'yourOwnSlice' => $paginator->getSlice(), ]); }
配置
$ bin/console config:dump-reference ttskch_paginator # Default configuration for extension with alias: "ttskch_paginator" ttskch_paginator: page: name: page range: 5 limit: name: limit default: 10 sort: key: name: sort direction: name: direction # "asc" or "desc" default: asc template: pager: '@TtskchPaginator/pager/default.html.twig' sortable: '@TtskchPaginator/sortable/default.html.twig'
自定义视图
使用预设的 Bootstrap4/5 主题
只需按以下方式配置包。
# config/packages/ttskch_paginator.yaml ttskch_paginator: template: pager: '@TtskchPaginator/pager/bootstrap5.html.twig' # pager: '@TtskchPaginator/pager/bootstrap4.html.twig'
使用您自己的主题
创建自己的模板并按以下方式配置包。
# config/packages/ttskch_paginator.yaml ttskch_paginator: template: pager: 'your/own/pager.html.twig' sortable: 'your/own/sortable.html.twig'
与搜索表单一起使用
// FooCriteria.php use Ttskch\PaginatorBundle\Criteria\AbstractCriteria; class FooCriteria extends AbstractCriteria { public ?string $query = null; public function __construct(string $sort) { parent::__construct($sort); } public function getFormTypeClass(): string { return FooSearchType::class; } }
// FooSearchType.php use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\SearchType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Ttskch\PaginatorBundle\Form\CriteriaType; class FooSearchType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('query', SearchType::class) ; } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => FooCriteria::class, // if your app depends on symfony/security-csrf adding below is recommended // 'csrf_protection' => false, ]); } public function getParent(): string { return CriteriaType::class; } }
// FooRepository.php use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\ORM\QueryBuilder; use Ttskch\PaginatorBundle\Counter\Doctrine\ORM\QueryBuilderCounter; use Ttskch\PaginatorBundle\Slicer\Doctrine\ORM\QueryBuilderSlicer; /** * @extends ServiceEntityRepository<Foo> */ class FooRepository extends ServiceEntityRepository { // ... /** * @return \Traversable<array-key, Foo> */ public function sliceByCriteria(FooCriteria $criteria): \Traversable { $qb = $this->createQueryBuilderFromCriteria($criteria); $slicer = new QueryBuilderSlicer($qb); return $slicer->slice($criteria); } public function countByCriteria(FooCriteria $criteria): int { $qb = $this->createQueryBuilderFromCriteria($criteria); $counter = new QueryBuilderCounter($qb); return $counter->count($criteria); } private function createQueryBuilderFromCriteria(FooCriteria $criteria): QueryBuilder { return $this->createQueryBuilder('f') ->orWhere('f.name like :query') ->orWhere('f.email like :query') ->setParameter('query', '%'.str_replace('%', '\%', $criteria->query).'%') ; } }
// FooController.php use Symfony\Component\HttpFoundation\Response; use Ttskch\PaginatorBundle\Paginator; /** * @param Paginator<\Traversable<array-key, Foo>, FooCriteria> $paginator */ public function index(FooRepository $fooRepository, Paginator $paginator): Response { $paginator->initialize( $fooRepository->sliceByCriteria(...), $fooRepository->countByCriteria(...), // or if PHP < 8.1 // \Closure::fromCallable([$fooRepository, 'sliceByCriteria']), // \Closure::fromCallable([$fooRepository, 'countByCriteria']), new FooCriteria('id'), ); return $this->render('index.html.twig', [ 'foos' => $paginator->getSlice(), 'form' => $paginator->getForm()->createView(), ]); }
{# index.html.twig #} {{ form(form, {action: path('foo_index'), method: 'get'}) }} <table> <thead> <tr> <th>{{ ttskch_paginator_sortable('id', 'Id') }}</th> <th>{{ ttskch_paginator_sortable('name', 'Name') }}</th> <th>{{ ttskch_paginator_sortable('email', 'Email') }}</th> </tr> </thead> <tbody> {% for foo in foos %} <tr> <td>{{ foo.id }}</td> <td>{{ foo.name }}</td> <td>{{ foo.email }}</td> </tr> {% endfor %} </tbody> </table> {{ ttskch_paginator_pager() }}
与连接查询一起使用
// FooRepository.php use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\ORM\QueryBuilder; use Ttskch\PaginatorBundle\Counter\Doctrine\ORM\QueryBuilderCounter; use Ttskch\PaginatorBundle\Slicer\Doctrine\ORM\QueryBuilderSlicer; /** * @extends ServiceEntityRepository<Foo> */ class FooRepository extends ServiceEntityRepository { // ... /** * @return \Traversable<array-key, Foo> */ public function sliceByCriteria(FooCriteria $criteria): \Traversable { $qb = $this->createQueryBuilderFromCriteria($criteria); $slicer = new QueryBuilderSlicer($qb, alreadyJoined: true); // **PAY ATTENTION HERE** return $slicer->slice($criteria); } public function countByCriteria(FooCriteria $criteria): int { $qb = $this->createQueryBuilderFromCriteria($criteria); $counter = new QueryBuilderCounter($qb); return $counter->count($criteria); } private function createQueryBuilderFromCriteria(FooCriteria $criteria): QueryBuilder { return $this->createQueryBuilder('f') ->leftJoin('f.bar', 'bar') ->leftJoin('bar.baz', 'baz') ->orWhere('f.name like :query') ->orWhere('f.email like :query') ->orWhere('bar.name like :query') ->orWhere('baz.name like :query') ->setParameter('query', '%'.str_replace('%', '\%', $criteria->query).'%') ; } }
// FooController.php use Symfony\Component\HttpFoundation\Response; use Ttskch\PaginatorBundle\Paginator; /** * @param Paginator<\Traversable<array-key, Foo>, FooCriteria> $paginator */ public function index(FooRepository $fooRepository, Paginator $paginator): Response { $paginator->initialize( $fooRepository->sliceByCriteria(...), $fooRepository->countByCriteria(...), // or if PHP < 8.1 // \Closure::fromCallable([$fooRepository, 'sliceByCriteria']), // \Closure::fromCallable([$fooRepository, 'countByCriteria']), new FooCriteria('f.id') ); return $this->render('index.html.twig', [ 'foos' => $paginator->getSlice(), 'form' => $paginator->getForm()->createView(), ]); }
{# index.html.twig #} {{ form(form, {action: path('foo_index'), method: 'get'}) }} <table> <thead> <tr> <th>{{ ttskch_paginator_sortable('f.id', 'Id') }}</th> <th>{{ ttskch_paginator_sortable('f.name', 'Name') }}</th> <th>{{ ttskch_paginator_sortable('f.email', 'Email') }}</th> <th>{{ ttskch_paginator_sortable('bar.name', 'Bar') }}</th> <th>{{ ttskch_paginator_sortable('baz.name', 'Baz') }}</th> </tr> </thead> <tbody> {% for foo in foos %} <tr> <td>{{ foo.id }}</td> <td>{{ foo.name }}</td> <td>{{ foo.email }}</td> <td>{{ foo.bar.name }}</td> <td>{{ foo.bar.baz.name }}</td> </tr> {% endfor %} </tbody> </table> {{ ttskch_paginator_pager() }}
参与其中
$ composer install
# Develop...
$ composer tests