{"id":17865801,"url":"https://github.com/ttskch/ttskchpaginatorbundle","last_synced_at":"2025-09-02T11:42:03.124Z","repository":{"id":45641863,"uuid":"282145174","full_name":"ttskch/TtskchPaginatorBundle","owner":"ttskch","description":"The most thin and simple paginator bundle for Symfony","archived":false,"fork":false,"pushed_at":"2023-12-20T03:08:42.000Z","size":683,"stargazers_count":7,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2023-12-20T13:50:49.968Z","etag":null,"topics":["customizable","pager","pagination","paginator","searchable","sortable","symfony"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ttskch.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-07-24T06:50:02.000Z","updated_at":"2023-12-21T20:28:36.168Z","dependencies_parsed_at":"2023-12-21T20:28:31.507Z","dependency_job_id":"c07830a8-cc0c-4852-8590-912208dd1bf9","html_url":"https://github.com/ttskch/TtskchPaginatorBundle","commit_stats":{"total_commits":47,"total_committers":5,"mean_commits":9.4,"dds":"0.12765957446808507","last_synced_commit":"d257bcd45fda227a84d61c8410ab6857987ea259"},"previous_names":[],"tags_count":23,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttskch%2FTtskchPaginatorBundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttskch%2FTtskchPaginatorBundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttskch%2FTtskchPaginatorBundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttskch%2FTtskchPaginatorBundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ttskch","download_url":"https://codeload.github.com/ttskch/TtskchPaginatorBundle/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221811465,"owners_count":16884335,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["customizable","pager","pagination","paginator","searchable","sortable","symfony"],"created_at":"2024-10-28T09:25:02.717Z","updated_at":"2024-10-28T09:25:03.160Z","avatar_url":"https://github.com/ttskch.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TtskchPaginatorBundle\n\n[![](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)\n[![codecov](https://codecov.io/gh/ttskch/TtskchPaginatorBundle/graph/badge.svg?token=OJWIKJIDTY)](https://codecov.io/gh/ttskch/TtskchPaginatorBundle)\n[![Latest Stable Version](https://poser.pugx.org/ttskch/paginator-bundle/version?format=flat-square)](https://packagist.org/packages/ttskch/paginator-bundle)\n[![Total Downloads](https://poser.pugx.org/ttskch/paginator-bundle/downloads?format=flat-square)](https://packagist.org/packages/ttskch/paginator-bundle/stats)\n\nThe most thin, simple and customizable paginator bundle for Symfony.\n\n![](https://github.com/ttskch/TtskchPaginatorBundle/assets/4360663/b1e72eb4-ba61-4307-a153-8be46290cf27)\n\n## Features\n\n* So **light weight**\n* **Well typed** (PHPStan level max)\n* **Depends on nothing** other than Symfony and Twig\n* But also easy to use with **Doctrine ORM**\n* Of course **can paginate everything**\n* Customizable **twig-templated views**\n* Very easy-to-use **sortable link** feature\n* Easy to use with **your own search form**\n* Preset beautiful **Bootstrap4/5 theme**\n\n## Requirements\n\n* PHP: ^8.0\n* Symfony: ^5.0|^6.0|^7.0\n\n## Demo\n\n👉 [Live demo is here](https://ttskchpaginatorbundle.herokuapp.com)\n\nYou can also see a sample code on [`demo/` directory](demo).\n\n## Installation\n\n```bash\n$ composer require ttskch/paginator-bundle\n```\n\n```php\n// config/bundles.php\n\nreturn [\n    // ...\n    Ttskch\\PaginatorBundle\\TtskchPaginatorBundle::class =\u003e ['all' =\u003e true],\n];\n```\n\n## Basic usages\n\n### With Doctrine ORM\n\n```php\n// FooController.php\n\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Ttskch\\PaginatorBundle\\Counter\\Doctrine\\ORM\\QueryBuilderCounter;\nuse Ttskch\\PaginatorBundle\\Criteria\\Criteria;\nuse Ttskch\\PaginatorBundle\\Paginator;\nuse Ttskch\\PaginatorBundle\\Slicer\\Doctrine\\ORM\\QueryBuilderSlicer;\n\n/**\n * @param Paginator\u003c\\Traversable\u003carray-key, Foo\u003e, Criteria\u003e $paginator\n */\npublic function index(FooRepository $fooRepository, Paginator $paginator): Response\n{\n    $qb = $fooRepository-\u003ecreateQueryBuilder('f');\n    $paginator-\u003einitialize(new QueryBuilderSlicer($qb), new QueryBuilderCounter($qb), new Criteria('id'));\n\n    return $this-\u003erender('index.html.twig', [\n        'foos' =\u003e $paginator-\u003egetSlice(),\n    ]);\n}\n```\n\n```twig\n{# index.html.twig #}\n\n\u003ctable\u003e\n  \u003cthead\u003e\n  \u003ctr\u003e\n    \u003cth\u003e{{ ttskch_paginator_sortable('id', 'Id') }}\u003c/th\u003e\n    \u003cth\u003e{{ ttskch_paginator_sortable('name', 'Name') }}\u003c/th\u003e\n    \u003cth\u003e{{ ttskch_paginator_sortable('email', 'Email') }}\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n  {% for foo in foos %}\n    \u003ctr\u003e\n      \u003ctd\u003e{{ foo.id }}\u003c/td\u003e\n      \u003ctd\u003e{{ foo.name }}\u003c/td\u003e\n      \u003ctd\u003e{{ foo.email }}\u003c/td\u003e\n    \u003c/tr\u003e\n  {% endfor %}\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n{{ ttskch_paginator_pager() }}\n```\n\nSee [src/Twig/TtskchPaginatorExtension.php](src/Twig/TtskchPaginatorExtension.php) to learn more about twig functions.\n\n#### Sort with property of joined entity\n\nJust do like as following.\n\n```twig\n{# index.html.twig #}\n\n{# ... #}\n\n\u003cth\u003e{{ ttskch_paginator_sortable('id', 'Id') }}\u003c/th\u003e\n\u003cth\u003e{{ ttskch_paginator_sortable('name', 'Name') }}\u003c/th\u003e\n\u003cth\u003e{{ ttskch_paginator_sortable('email', 'Email') }}\u003c/th\u003e\n\u003cth\u003e{{ ttskch_paginator_sortable('bar.id', 'Bar') }}\u003c/th\u003e\n\u003cth\u003e{{ ttskch_paginator_sortable('bar.baz.id', 'Baz') }}\u003c/th\u003e\n\n{# ... #}\n```\n\n### With array\n\n```php\n// FooController.php\n\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Ttskch\\PaginatorBundle\\Counter\\ArrayCounter;\nuse Ttskch\\PaginatorBundle\\Criteria\\Criteria;\nuse Ttskch\\PaginatorBundle\\Paginator;\nuse Ttskch\\PaginatorBundle\\Slicer\\ArraySlicer;\n\n/**\n * @param Paginator\u003carray\u003carray{id: int, name: string, email: string}\u003e, Criteria\u003e $paginator\n */\npublic function index(Paginator $paginator): Response\n{\n    $array = [\n        ['id' =\u003e 1, 'name' =\u003e 'Tommy Yount', 'email' =\u003e 'tommy_yount@gmail.com'],\n        ['id' =\u003e 2, 'name' =\u003e 'Hye Panter', 'email' =\u003e 'hye_panter@gmail.com'],\n        ['id' =\u003e 3, 'name' =\u003e 'Vi Yohe', 'email' =\u003e 'vi_yohe@gmail.com'],\n        ['id' =\u003e 4, 'name' =\u003e 'Keva Bandy', 'email' =\u003e 'keva_bandy@gmail.com'],\n        ['id' =\u003e 5, 'name' =\u003e 'Hannelore Corning', 'email' =\u003e 'hannelore_corning@gmail.com'],\n        ['id' =\u003e 6, 'name' =\u003e 'Delorse Whitcher', 'email' =\u003e 'delorse_whitcher@gmail.com'],\n        ['id' =\u003e 7, 'name' =\u003e 'Katharyn Marrinan', 'email' =\u003e 'katharyn_marrinan@gmail.com'],\n        ['id' =\u003e 8, 'name' =\u003e 'Jeannine Tope', 'email' =\u003e 'jeannine_tope@gmail.com'],\n        ['id' =\u003e 9, 'name' =\u003e 'Jamila Braggs', 'email' =\u003e 'jamila_braggs@gmail.com'],\n        ['id' =\u003e 10, 'name' =\u003e 'Eden Cunniff', 'email' =\u003e 'eden_cunniff@gmail.com'],\n        // ...\n        ['id' =\u003e 299, 'name' =\u003e 'Deshawn Kennedy', 'email' =\u003e 'deshawn_kennedy@gmail.com'],\n        ['id' =\u003e 300, 'name' =\u003e 'Elenore Evens', 'email' =\u003e 'elenore_evens@gmail.com'],\n    ];\n\n    $paginator-\u003einitialize(\n        new ArraySlicer($array),\n        new ArrayCounter($array),\n        new Criteria('id'),\n    );\n\n    return $this-\u003erender('index.html.twig', [\n        'foos' =\u003e $paginator-\u003egetSlice(),\n    ]);\n}\n```\n\n### With something other data\n\nImplement slicer and counter by yourself like as following.\n\n```php\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Ttskch\\PaginatorBundle\\Counter\\CallbackCounter;\nuse Ttskch\\PaginatorBundle\\Criteria\\Criteria;\nuse Ttskch\\PaginatorBundle\\Paginator;\nuse Ttskch\\PaginatorBundle\\Slicer\\CallbackSlicer;\n\n/**\n * @param Paginator\u003cTypeOfYourOwnSlice\u003e, Criteria\u003e $paginator\n */\npublic function index(Paginator $paginator): Response\n{\n    $yourOwnData = /* ... */;\n\n    $paginator-\u003einitialize(\n        new CallbackSlicer(function (Criteria $criteria) use ($yourOwnData) {\n            /* ... */\n            return $yourOwnSlice;\n        }),\n        new CallbackCounter(function (Criteria $criteria) use ($yourOwnData) {\n            /* ... */\n            return $totalItemsCount;\n        }),\n        new Criteria('default_sort_key'),\n    );\n\n    return $this-\u003erender('index.html.twig', [\n        'yourOwnSlice' =\u003e $paginator-\u003egetSlice(),\n    ]);\n}\n```\n\n## Configuring\n\n```bash\n$ bin/console config:dump-reference ttskch_paginator\n# Default configuration for extension with alias: \"ttskch_paginator\"\nttskch_paginator:\n    page:\n        name:                 page\n        range:                5\n    limit:\n        name:                 limit\n        default:              10\n    sort:\n        key:\n            name:                 sort\n        direction:\n            name:                 direction\n\n            # \"asc\" or \"desc\"\n            default:              asc\n    template:\n        pager:                '@TtskchPaginator/pager/default.html.twig'\n        sortable:             '@TtskchPaginator/sortable/default.html.twig'\n```\n\n## Customizing views\n\n### Using preset Bootstrap4/5 theme\n\nJust configure bundle like below.\n\n```yaml\n# config/packages/ttskch_paginator.yaml\n\nttskch_paginator:\n  template:\n    pager: '@TtskchPaginator/pager/bootstrap5.html.twig'\n#   pager: '@TtskchPaginator/pager/bootstrap4.html.twig'\n```\n\n### Using your own theme\n\nCreate your own templates and configure bundle like below.\n\n```yaml\n# config/packages/ttskch_paginator.yaml\n\nttskch_paginator:\n  template:\n    pager: 'your/own/pager.html.twig'\n    sortable: 'your/own/sortable.html.twig'\n```\n\n## Using with search form\n\n```php\n// FooCriteria.php\n\nuse Ttskch\\PaginatorBundle\\Criteria\\AbstractCriteria;\n\nclass FooCriteria extends AbstractCriteria\n{\n    public ?string $query = null;\n\n    public function __construct(string $sort)\n    {\n        parent::__construct($sort);\n    }\n\n    public function getFormTypeClass(): string\n    {\n        return FooSearchType::class;\n    }\n}\n```\n\n```php\n// FooSearchType.php\n\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\SearchType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\nuse Symfony\\Component\\OptionsResolver\\OptionsResolver;\nuse Ttskch\\PaginatorBundle\\Form\\CriteriaType;\n\nclass FooSearchType extends AbstractType\n{\n    public function buildForm(FormBuilderInterface $builder, array $options): void\n    {\n        $builder\n            -\u003eadd('query', SearchType::class)\n        ;\n    }\n\n    public function configureOptions(OptionsResolver $resolver): void\n    {\n        $resolver-\u003esetDefaults([\n            'data_class' =\u003e FooCriteria::class,\n            // if your app depends on symfony/security-csrf adding below is recommended\n            // 'csrf_protection' =\u003e false,\n        ]);\n    }\n\n    public function getParent(): string\n    {\n        return CriteriaType::class;\n    }\n}\n```\n\n```php\n// FooRepository.php\n\nuse Doctrine\\Bundle\\DoctrineBundle\\Repository\\ServiceEntityRepository;\nuse Doctrine\\ORM\\QueryBuilder;\nuse Ttskch\\PaginatorBundle\\Counter\\Doctrine\\ORM\\QueryBuilderCounter;\nuse Ttskch\\PaginatorBundle\\Slicer\\Doctrine\\ORM\\QueryBuilderSlicer;\n\n/**\n * @extends ServiceEntityRepository\u003cFoo\u003e\n */\nclass FooRepository extends ServiceEntityRepository\n{\n    // ...\n\n    /**\n     * @return \\Traversable\u003carray-key, Foo\u003e\n     */\n    public function sliceByCriteria(FooCriteria $criteria): \\Traversable\n    {\n        $qb = $this-\u003ecreateQueryBuilderFromCriteria($criteria);\n        $slicer = new QueryBuilderSlicer($qb);\n    \n        return $slicer-\u003eslice($criteria);\n    }\n    \n    public function countByCriteria(FooCriteria $criteria): int\n    {\n        $qb = $this-\u003ecreateQueryBuilderFromCriteria($criteria);\n        $counter = new QueryBuilderCounter($qb);\n    \n        return $counter-\u003ecount($criteria);\n    }\n    \n    private function createQueryBuilderFromCriteria(FooCriteria $criteria): QueryBuilder\n    {\n        return $this-\u003ecreateQueryBuilder('f')\n            -\u003eorWhere('f.name like :query')\n            -\u003eorWhere('f.email like :query')\n            -\u003esetParameter('query', '%'.str_replace('%', '\\%', $criteria-\u003equery).'%')\n        ;\n    }\n}\n```\n\n```php\n// FooController.php\n\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Ttskch\\PaginatorBundle\\Paginator;\n\n/**\n * @param Paginator\u003c\\Traversable\u003carray-key, Foo\u003e, FooCriteria\u003e $paginator\n */\npublic function index(FooRepository $fooRepository, Paginator $paginator): Response\n{\n    $paginator-\u003einitialize(\n        $fooRepository-\u003esliceByCriteria(...),\n        $fooRepository-\u003ecountByCriteria(...),\n        // or if PHP \u003c 8.1\n        // \\Closure::fromCallable([$fooRepository, 'sliceByCriteria']),\n        // \\Closure::fromCallable([$fooRepository, 'countByCriteria']),\n        new FooCriteria('id'),\n    );\n\n    return $this-\u003erender('index.html.twig', [\n        'foos' =\u003e $paginator-\u003egetSlice(),\n        'form' =\u003e $paginator-\u003egetForm()-\u003ecreateView(),\n    ]);\n}\n```\n\n```twig\n{# index.html.twig #}\n\n{{ form(form, {action: path('foo_index'), method: 'get'}) }}\n\n\u003ctable\u003e\n    \u003cthead\u003e\n    \u003ctr\u003e\n        \u003cth\u003e{{ ttskch_paginator_sortable('id', 'Id') }}\u003c/th\u003e\n        \u003cth\u003e{{ ttskch_paginator_sortable('name', 'Name') }}\u003c/th\u003e\n        \u003cth\u003e{{ ttskch_paginator_sortable('email', 'Email') }}\u003c/th\u003e\n    \u003c/tr\u003e\n    \u003c/thead\u003e\n    \u003ctbody\u003e\n    {% for foo in foos %}\n        \u003ctr\u003e\n            \u003ctd\u003e{{ foo.id }}\u003c/td\u003e\n            \u003ctd\u003e{{ foo.name }}\u003c/td\u003e\n            \u003ctd\u003e{{ foo.email }}\u003c/td\u003e\n        \u003c/tr\u003e\n    {% endfor %}\n    \u003c/tbody\u003e\n\u003c/table\u003e\n\n{{ ttskch_paginator_pager() }}\n```\n\n## Using with joined query\n\n```php\n// FooRepository.php\n\nuse Doctrine\\Bundle\\DoctrineBundle\\Repository\\ServiceEntityRepository;\nuse Doctrine\\ORM\\QueryBuilder;\nuse Ttskch\\PaginatorBundle\\Counter\\Doctrine\\ORM\\QueryBuilderCounter;\nuse Ttskch\\PaginatorBundle\\Slicer\\Doctrine\\ORM\\QueryBuilderSlicer;\n\n/**\n * @extends ServiceEntityRepository\u003cFoo\u003e\n */\nclass FooRepository extends ServiceEntityRepository\n{\n    // ...\n\n    /**\n     * @return \\Traversable\u003carray-key, Foo\u003e\n     */\n    public function sliceByCriteria(FooCriteria $criteria): \\Traversable\n    {\n        $qb = $this-\u003ecreateQueryBuilderFromCriteria($criteria);\n        $slicer = new QueryBuilderSlicer($qb, alreadyJoined: true); // **PAY ATTENTION HERE**\n    \n        return $slicer-\u003eslice($criteria);\n    }\n    \n    public function countByCriteria(FooCriteria $criteria): int\n    {\n        $qb = $this-\u003ecreateQueryBuilderFromCriteria($criteria);\n        $counter = new QueryBuilderCounter($qb);\n    \n        return $counter-\u003ecount($criteria);\n    }\n    \n    private function createQueryBuilderFromCriteria(FooCriteria $criteria): QueryBuilder\n    {\n        return $this-\u003ecreateQueryBuilder('f')\n            -\u003eleftJoin('f.bar', 'bar')\n            -\u003eleftJoin('bar.baz', 'baz')\n            -\u003eorWhere('f.name like :query')\n            -\u003eorWhere('f.email like :query')\n            -\u003eorWhere('bar.name like :query')\n            -\u003eorWhere('baz.name like :query')\n            -\u003esetParameter('query', '%'.str_replace('%', '\\%', $criteria-\u003equery).'%')\n        ;\n    }\n}\n```\n\n```php\n// FooController.php\n\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Ttskch\\PaginatorBundle\\Paginator;\n\n/**\n * @param Paginator\u003c\\Traversable\u003carray-key, Foo\u003e, FooCriteria\u003e $paginator\n */\npublic function index(FooRepository $fooRepository, Paginator $paginator): Response\n{\n    $paginator-\u003einitialize(\n        $fooRepository-\u003esliceByCriteria(...),\n        $fooRepository-\u003ecountByCriteria(...),\n        // or if PHP \u003c 8.1\n        // \\Closure::fromCallable([$fooRepository, 'sliceByCriteria']),\n        // \\Closure::fromCallable([$fooRepository, 'countByCriteria']),\n        new FooCriteria('f.id')\n    );\n\n    return $this-\u003erender('index.html.twig', [\n        'foos' =\u003e $paginator-\u003egetSlice(),\n        'form' =\u003e $paginator-\u003egetForm()-\u003ecreateView(),\n    ]);\n}\n```\n\n```twig\n{# index.html.twig #}\n\n{{ form(form, {action: path('foo_index'), method: 'get'}) }}\n\n\u003ctable\u003e\n    \u003cthead\u003e\n    \u003ctr\u003e\n        \u003cth\u003e{{ ttskch_paginator_sortable('f.id', 'Id') }}\u003c/th\u003e\n        \u003cth\u003e{{ ttskch_paginator_sortable('f.name', 'Name') }}\u003c/th\u003e\n        \u003cth\u003e{{ ttskch_paginator_sortable('f.email', 'Email') }}\u003c/th\u003e\n        \u003cth\u003e{{ ttskch_paginator_sortable('bar.name', 'Bar') }}\u003c/th\u003e\n        \u003cth\u003e{{ ttskch_paginator_sortable('baz.name', 'Baz') }}\u003c/th\u003e\n    \u003c/tr\u003e\n    \u003c/thead\u003e\n    \u003ctbody\u003e\n    {% for foo in foos %}\n        \u003ctr\u003e\n            \u003ctd\u003e{{ foo.id }}\u003c/td\u003e\n            \u003ctd\u003e{{ foo.name }}\u003c/td\u003e\n            \u003ctd\u003e{{ foo.email }}\u003c/td\u003e\n            \u003ctd\u003e{{ foo.bar.name }}\u003c/td\u003e\n            \u003ctd\u003e{{ foo.bar.baz.name }}\u003c/td\u003e\n        \u003c/tr\u003e\n    {% endfor %}\n    \u003c/tbody\u003e\n\u003c/table\u003e\n\n{{ ttskch_paginator_pager() }}\n```\n\n## Getting involved\n\n```shell\n$ composer install\n\n# Develop...\n\n$ composer tests\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fttskch%2Fttskchpaginatorbundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fttskch%2Fttskchpaginatorbundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fttskch%2Fttskchpaginatorbundle/lists"}