PHPackages                             ttskch/paginator-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. ttskch/paginator-bundle

ActiveSymfony-bundle[Utility &amp; Helpers](/categories/utility)

ttskch/paginator-bundle
=======================

The most thin, simple and customizable paginator bundle for Symfony

6.2.0(1y ago)1113.2k↓37.3%4[5 PRs](https://github.com/ttskch/TtskchPaginatorBundle/pulls)MITPHPPHP ^8.0

Since Jul 24Pushed 1y agoCompare

[ Source](https://github.com/ttskch/TtskchPaginatorBundle)[ Packagist](https://packagist.org/packages/ttskch/paginator-bundle)[ RSS](/packages/ttskch-paginator-bundle/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (15)Versions (33)Used By (0)

TtskchPaginatorBundle
=====================

[](#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://camo.githubusercontent.com/4998ad54323741f520e3f69eb213daf26803c669a66d69f142096de63d0caf79/68747470733a2f2f636f6465636f762e696f2f67682f7474736b63682f5474736b6368506167696e61746f7242756e646c652f67726170682f62616467652e7376673f746f6b656e3d4f4a57494b4a49445459)](https://codecov.io/gh/ttskch/TtskchPaginatorBundle)[![Latest Stable Version](https://camo.githubusercontent.com/c265027c8eb6fa2ee57f8a2b4ea503f3664d537aa2d61629e8f135ba40ad1156/68747470733a2f2f706f7365722e707567782e6f72672f7474736b63682f706167696e61746f722d62756e646c652f76657273696f6e3f666f726d61743d666c61742d737175617265)](https://packagist.org/packages/ttskch/paginator-bundle)[![Total Downloads](https://camo.githubusercontent.com/ea6f363c8224eb29b49172624787405cbe6dc2fdd62911f10d6033f8ee60fc02/68747470733a2f2f706f7365722e707567782e6f72672f7474736b63682f706167696e61746f722d62756e646c652f646f776e6c6f6164733f666f726d61743d666c61742d737175617265)](https://packagist.org/packages/ttskch/paginator-bundle/stats)

The most thin, simple and customizable paginator bundle for Symfony.

[![](https://private-user-images.githubusercontent.com/4360663/291766019-b1e72eb4-ba61-4307-a153-8be46290cf27.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzIxNTU5NTcsIm5iZiI6MTc3MjE1NTY1NywicGF0aCI6Ii80MzYwNjYzLzI5MTc2NjAxOS1iMWU3MmViNC1iYTYxLTQzMDctYTE1My04YmU0NjI5MGNmMjcucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDIyNyUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAyMjdUMDEyNzM3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MGJmMjI0OTkxODlmMTgxYWRkNWE0NzMyZTNmNDU0MDdmMThhNDUyODE1N2Y3NTU3MzRkZmFjNjJlODM1Nzk0YSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.6hx7eLa6gK2KOHQ2g5ab_JSKBtvMqj0EwO4289MCpLk)](https://private-user-images.githubusercontent.com/4360663/291766019-b1e72eb4-ba61-4307-a153-8be46290cf27.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzIxNTU5NTcsIm5iZiI6MTc3MjE1NTY1NywicGF0aCI6Ii80MzYwNjYzLzI5MTc2NjAxOS1iMWU3MmViNC1iYTYxLTQzMDctYTE1My04YmU0NjI5MGNmMjcucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDIyNyUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAyMjdUMDEyNzM3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MGJmMjI0OTkxODlmMTgxYWRkNWE0NzMyZTNmNDU0MDdmMThhNDUyODE1N2Y3NTU3MzRkZmFjNjJlODM1Nzk0YSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.6hx7eLa6gK2KOHQ2g5ab_JSKBtvMqj0EwO4289MCpLk)

Features
--------

[](#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
------------

[](#requirements)

- PHP: ^8.0
- Symfony: ^5.0|^6.0|^7.0

Demo
----

[](#demo)

👉 [Live demo is here](https://ttskchpaginatorbundle.herokuapp.com)

You can also see a sample code on [`demo/` directory](demo).

Installation
------------

[](#installation)

```
$ composer require ttskch/paginator-bundle
```

```
// config/bundles.php

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

Basic usages
------------

[](#basic-usages)

### With Doctrine ORM

[](#with-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 $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 #}

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

[](#sort-with-property-of-joined-entity)

Just do like as following.

```
{# 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

[](#with-array)

```
// 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 $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(),
    ]);
}
```

### With something other data

[](#with-something-other-data)

Implement slicer and counter by yourself like as following.

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

[](#configuring)

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

[](#customizing-views)

### Using preset Bootstrap4/5 theme

[](#using-preset-bootstrap45-theme)

Just configure bundle like below.

```
# config/packages/ttskch_paginator.yaml

ttskch_paginator:
  template:
    pager: '@TtskchPaginator/pager/bootstrap5.html.twig'
#   pager: '@TtskchPaginator/pager/bootstrap4.html.twig'
```

### Using your own theme

[](#using-your-own-theme)

Create your own templates and configure bundle like below.

```
# config/packages/ttskch_paginator.yaml

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

Using with search form
----------------------

[](#using-with-search-form)

```
// 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
 */
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).'%')
        ;
    }
}
```

```
// FooController.php

use Symfony\Component\HttpFoundation\Response;
use Ttskch\PaginatorBundle\Paginator;

/**
 * @param Paginator $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'}) }}

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

[](#using-with-joined-query)

```
// 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).'%')
        ;
    }
}
```

```
// FooController.php

use Symfony\Component\HttpFoundation\Response;
use Ttskch\PaginatorBundle\Paginator;

/**
 * @param Paginator $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'}) }}

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

[](#getting-involved)

```
$ composer install

# Develop...

$ composer tests
```

###  Health Score

42

—

FairBetter than 90% of packages

Maintenance37

Infrequent updates — may be unmaintained

Popularity34

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity71

Established project with proven stability

 Bus Factor1

Top contributor holds 97.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~57 days

Recently: every ~63 days

Total

27

Last Release

629d ago

Major Versions

1.0.13 → 2.0.02021-02-03

2.0.1 → 3.0.02021-06-15

3.3.0 → 4.0.02023-01-22

4.1.0 → 5.0.02023-12-20

5.0.0 → 6.0.02023-12-23

PHP version history (3 changes)1.0.0PHP &gt;=7.1.3

1.0.3PHP &gt;=7.1.3|&gt;=7.2.5

5.0.0PHP ^8.0

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/4360663?v=4)[Takashi Kanemoto](/maintainers/ttskch)[@ttskch](https://github.com/ttskch)

---

Top Contributors

[![ttskch](https://avatars.githubusercontent.com/u/4360663?v=4)](https://github.com/ttskch "ttskch (89 commits)")[![77web](https://avatars.githubusercontent.com/u/296615?v=4)](https://github.com/77web "77web (1 commits)")[![nesso-pfl](https://avatars.githubusercontent.com/u/32949286?v=4)](https://github.com/nesso-pfl "nesso-pfl (1 commits)")

---

Tags

customizablepagerpaginationpaginatorsearchablesortablesymfonysymfonypagerpaginatorpaginationsortablesearchablecustomizable

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/ttskch-paginator-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/ttskch-paginator-bundle/health.svg)](https://phpackages.com/packages/ttskch-paginator-bundle)
```

###  Alternatives

[knplabs/knp-paginator-bundle

Paginator bundle for Symfony to automate pagination and simplify sorting and other features

1.8k42.8M315](/packages/knplabs-knp-paginator-bundle)[pugx/autocompleter-bundle

Add an autocomplete type to forms

93861.6k3](/packages/pugx-autocompleter-bundle)[netgen/layouts-core

Netgen Layouts enables you to build and manage complex web pages in a simpler way and with less coding. This is the core of Netgen Layouts, its heart and soul.

3689.4k10](/packages/netgen-layouts-core)[netgen/content-browser

Netgen Content Browser is a Symfony bundle that provides an interface which selects items from any kind of backend and returns the IDs of selected items back to the calling code.

14112.1k8](/packages/netgen-content-browser)[codefog/contao-haste

haste extension for Contao Open Source CMS

42650.8k139](/packages/codefog-contao-haste)[spomky-labs/pwa-bundle

Progressive Web App Manifest Generator Bundle for Symfony.

6144.4k1](/packages/spomky-labs-pwa-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
