Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/ttskch/ttskchpaginatorbundle
The most thin and simple paginator bundle for Symfony
https://github.com/ttskch/ttskchpaginatorbundle
customizable pager pagination paginator searchable sortable symfony
Last synced: 2 months ago
JSON representation
The most thin and simple paginator bundle for Symfony
- Host: GitHub
- URL: https://github.com/ttskch/ttskchpaginatorbundle
- Owner: ttskch
- License: mit
- Created: 2020-07-24T06:50:02.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2023-12-20T03:08:42.000Z (about 1 year ago)
- Last Synced: 2023-12-20T13:50:49.968Z (about 1 year ago)
- Topics: customizable, pager, pagination, paginator, searchable, sortable, symfony
- Language: PHP
- Homepage:
- Size: 667 KB
- Stars: 7
- Watchers: 2
- Forks: 3
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# TtskchPaginatorBundle
[![](https://github.com/ttskch/TtskchPaginatorBundle/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/ttskch/TtskchPaginatorBundle/actions/workflows/ci.yaml?query=branch:main)
[![codecov](https://codecov.io/gh/ttskch/TtskchPaginatorBundle/graph/badge.svg?token=OJWIKJIDTY)](https://codecov.io/gh/ttskch/TtskchPaginatorBundle)
[![Latest Stable Version](https://poser.pugx.org/ttskch/paginator-bundle/version?format=flat-square)](https://packagist.org/packages/ttskch/paginator-bundle)
[![Total Downloads](https://poser.pugx.org/ttskch/paginator-bundle/downloads?format=flat-square)](https://packagist.org/packages/ttskch/paginator-bundle/stats)The most thin, simple and customizable paginator bundle for Symfony.
![](https://github.com/ttskch/TtskchPaginatorBundle/assets/4360663/b1e72eb4-ba61-4307-a153-8be46290cf27)
## Features
* So **light weight**
* **Well typed** (PHPStan level max)
* **Depends on nothing** other than Symfony and Twig
* But also easy to use with **Doctrine ORM**
* Of course **can paginate everything**
* Customizable **twig-templated views**
* Very easy-to-use **sortable link** feature
* Easy to use with **your own search form**
* Preset beautiful **Bootstrap4/5 theme**## Requirements
* PHP: ^8.0
* Symfony: ^5.0|^6.0|^7.0## Demo
👉 [Live demo is here](https://ttskchpaginatorbundle.herokuapp.com)
You can also see a sample code on [`demo/` directory](demo).
## Installation
```bash
$ composer require ttskch/paginator-bundle
``````php
// config/bundles.phpreturn [
// ...
Ttskch\PaginatorBundle\TtskchPaginatorBundle::class => ['all' => true],
];
```## Basic usages
### With Doctrine ORM
```php
// FooController.phpuse 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, 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(),
]);
}
``````twig
{# index.html.twig #}
{{ ttskch_paginator_sortable('id', 'Id') }}
{{ ttskch_paginator_sortable('name', 'Name') }}
{{ ttskch_paginator_sortable('email', 'Email') }}
{% for foo in foos %}
{{ foo.id }}
{{ foo.name }}
{{ foo.email }}
{% endfor %}
{{ ttskch_paginator_pager() }}
```See [src/Twig/TtskchPaginatorExtension.php](src/Twig/TtskchPaginatorExtension.php) to learn more about twig functions.
#### Sort with property of joined entity
Just do like as following.
```twig
{# index.html.twig #}{# ... #}
{{ ttskch_paginator_sortable('id', 'Id') }}
{{ ttskch_paginator_sortable('name', 'Name') }}
{{ ttskch_paginator_sortable('email', 'Email') }}
{{ ttskch_paginator_sortable('bar.id', 'Bar') }}
{{ ttskch_paginator_sortable('bar.baz.id', 'Baz') }}{# ... #}
```### With array
```php
// FooController.phpuse 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, Criteria> $paginator
*/
public function index(Paginator $paginator): Response
{
$array = [
['id' => 1, 'name' => 'Tommy Yount', 'email' => '[email protected]'],
['id' => 2, 'name' => 'Hye Panter', 'email' => '[email protected]'],
['id' => 3, 'name' => 'Vi Yohe', 'email' => '[email protected]'],
['id' => 4, 'name' => 'Keva Bandy', 'email' => '[email protected]'],
['id' => 5, 'name' => 'Hannelore Corning', 'email' => '[email protected]'],
['id' => 6, 'name' => 'Delorse Whitcher', 'email' => '[email protected]'],
['id' => 7, 'name' => 'Katharyn Marrinan', 'email' => '[email protected]'],
['id' => 8, 'name' => 'Jeannine Tope', 'email' => '[email protected]'],
['id' => 9, 'name' => 'Jamila Braggs', 'email' => '[email protected]'],
['id' => 10, 'name' => 'Eden Cunniff', 'email' => '[email protected]'],
// ...
['id' => 299, 'name' => 'Deshawn Kennedy', 'email' => '[email protected]'],
['id' => 300, 'name' => 'Elenore Evens', 'email' => '[email protected]'],
];$paginator->initialize(
new ArraySlicer($array),
new ArrayCounter($array),
new Criteria('id'),
);return $this->render('index.html.twig', [
'foos' => $paginator->getSlice(),
]);
}
```### With something other data
Implement slicer and counter by yourself like as following.
```php
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, 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(),
]);
}
```## Configuring
```bash
$ 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'
```## Customizing views
### Using preset Bootstrap4/5 theme
Just configure bundle like below.
```yaml
# config/packages/ttskch_paginator.yamlttskch_paginator:
template:
pager: '@TtskchPaginator/pager/bootstrap5.html.twig'
# pager: '@TtskchPaginator/pager/bootstrap4.html.twig'
```### Using your own theme
Create your own templates and configure bundle like below.
```yaml
# config/packages/ttskch_paginator.yamlttskch_paginator:
template:
pager: 'your/own/pager.html.twig'
sortable: 'your/own/sortable.html.twig'
```## Using with search form
```php
// FooCriteria.phpuse 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;
}
}
``````php
// FooSearchType.phpuse 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;
}
}
``````php
// FooRepository.phpuse 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
*/
class FooRepository extends ServiceEntityRepository
{
// .../**
* @return \Traversable
*/
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).'%')
;
}
}
``````php
// FooController.phpuse Symfony\Component\HttpFoundation\Response;
use Ttskch\PaginatorBundle\Paginator;/**
* @param Paginator<\Traversable, 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(),
]);
}
``````twig
{# index.html.twig #}{{ form(form, {action: path('foo_index'), method: 'get'}) }}
{{ ttskch_paginator_sortable('id', 'Id') }}
{{ ttskch_paginator_sortable('name', 'Name') }}
{{ ttskch_paginator_sortable('email', 'Email') }}
{% for foo in foos %}
{{ foo.id }}
{{ foo.name }}
{{ foo.email }}
{% endfor %}
{{ ttskch_paginator_pager() }}
```## Using with joined query
```php
// FooRepository.phpuse 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
*/
class FooRepository extends ServiceEntityRepository
{
// .../**
* @return \Traversable
*/
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).'%')
;
}
}
``````php
// FooController.phpuse Symfony\Component\HttpFoundation\Response;
use Ttskch\PaginatorBundle\Paginator;/**
* @param Paginator<\Traversable, 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(),
]);
}
``````twig
{# index.html.twig #}{{ form(form, {action: path('foo_index'), method: 'get'}) }}
{{ ttskch_paginator_sortable('f.id', 'Id') }}
{{ ttskch_paginator_sortable('f.name', 'Name') }}
{{ ttskch_paginator_sortable('f.email', 'Email') }}
{{ ttskch_paginator_sortable('bar.name', 'Bar') }}
{{ ttskch_paginator_sortable('baz.name', 'Baz') }}
{% for foo in foos %}
{{ foo.id }}
{{ foo.name }}
{{ foo.email }}
{{ foo.bar.name }}
{{ foo.bar.baz.name }}
{% endfor %}
{{ ttskch_paginator_pager() }}
```## Getting involved
```shell
$ composer install# Develop...
$ composer tests
```