Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

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

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

return [
// ...
Ttskch\PaginatorBundle\TtskchPaginatorBundle::class => ['all' => true],
];
```

## Basic usages

### With Doctrine ORM

```php
// 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, 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.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, 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.yaml

ttskch_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.yaml

ttskch_paginator:
template:
pager: 'your/own/pager.html.twig'
sortable: 'your/own/sortable.html.twig'
```

## Using with search form

```php
// 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;
}
}
```

```php
// 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;
}
}
```

```php
// 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
*/
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.php

use 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.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
*/
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.php

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