{"id":14983863,"url":"https://github.com/rekalogika/rekapager","last_synced_at":"2025-04-10T19:43:14.790Z","repository":{"id":230601868,"uuid":"779763078","full_name":"rekalogika/rekapager","owner":"rekalogika","description":"Pagination library for PHP, supporting both offset-based and keyset-based pagination.","archived":false,"fork":false,"pushed_at":"2025-03-17T17:29:20.000Z","size":995,"stargazers_count":33,"open_issues_count":6,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-24T17:21:23.068Z","etag":null,"topics":["bundle","doctrine","keyset","keyset-pagination","limit","offset","offset-pagination","pager","pagerfanta","pagination","rekapager","symfony","symfony-bundle"],"latest_commit_sha":null,"homepage":"https://rekalogika.dev/rekapager","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/rekalogika.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"priyadi"}},"created_at":"2024-03-30T18:04:46.000Z","updated_at":"2025-01-18T07:12:04.000Z","dependencies_parsed_at":"2024-05-01T19:26:30.424Z","dependency_job_id":"90f51adc-b890-4f9b-a095-18b5fef1b08d","html_url":"https://github.com/rekalogika/rekapager","commit_stats":{"total_commits":225,"total_committers":2,"mean_commits":112.5,"dds":0.0888888888888889,"last_synced_commit":"334feb71f4d71c02690ec70bea7635191591154b"},"previous_names":["rekalogika/rekapager"],"tags_count":55,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rekalogika%2Frekapager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rekalogika%2Frekapager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rekalogika%2Frekapager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rekalogika%2Frekapager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rekalogika","download_url":"https://codeload.github.com/rekalogika/rekapager/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248281424,"owners_count":21077423,"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":["bundle","doctrine","keyset","keyset-pagination","limit","offset","offset-pagination","pager","pagerfanta","pagination","rekapager","symfony","symfony-bundle"],"created_at":"2024-09-24T14:08:05.443Z","updated_at":"2025-04-10T19:43:14.771Z","avatar_url":"https://github.com/rekalogika.png","language":"PHP","funding_links":["https://github.com/sponsors/priyadi"],"categories":[],"sub_categories":[],"readme":"# Rekapager\n\nRekapager is a pagination library for PHP, supporting both offset-based and\nkeyset-based pagination (also called cursor-based pagination).\n\nFull documentation is available at [rekalogika.dev/rekapager](https://rekalogika.dev/rekapager)\n\n## Keyset Pagination (or Cursor-Based Pagination)\n\nKeyset pagination is a method of pagination that uses the last row of the\ncurrent page as an anchor for the next page. This method has these advantages\ncompared to the traditional offset-based pagination:\n\n* It is more efficient because it leverages the index. It does not require the\n  database to scan all rows from the beginning to reach the desired page.\n* It is more resilient to data changes. The data will not drift when rows are\n  inserted or deleted.\n\n### Identifying Pages\n\nInstead of using page numbers, a page identifier object is used to reference a\npage. This identifier is encoded into a string and passed as a single query\nparameter.\n\nBecause it requires only a single query parameter, it works similarly on the\nsurface with offset pagination. Migrating from offset pagination to keyset\npagination will be straightforward. The difference is that instead of having\npage numbers in the URL, we'll be getting an 'ugly' identifier, which is opaque\nto the user, but meaningful to the application.\n\nIt also easily allows us to keep the pagination job separate from the filtering\nand sorting logic. The library does not require a specific way to filter or sort\nyour data.\n\n### Queries\n\nMultiple sort columns are supported. The library will automatically generate the\nWHERE query for you, including the complex cases involving more than two sort\ncolumns. The only requirement is that the query needs to have a deterministic\nsort order.\n\nSome backends also have the option to use SQL row values syntax for a slightly\nbetter performance.\n\n### Bidirectional Navigation and Page Skipping\n\nBidirectional navigation is supported. The user will be able to navigate forward\nand backward from the current page. It also supports offset seeking, allowing\nthe user to skip the immediate next or previous page up to the configured\nproximity setting.\n\nIn the user interface, the pager will look like a regular pagination control:\n\n![with pages around the current page](https://rekalogika.dev/rekapager/middle.png)\n\nThe page number is informational only, and carried over from the start page.\n\n### Jumping to the Last Page\n\nSeeking to the last page is possible. And with keyset pagination, it will be as\nfast as seeking to the first page:\n\n![last page](https://rekalogika.dev/rekapager/last-without-count.png)\n\n### Page Numbers and Counting\n\nNegative page numbers shown above indicate the page numbers from the end. The\nlast page is -1, the second to last is -2, and so on. It is done this way\nbecause by default the pager does not fetch the total count from the underlying\ndata, which is another common performance issue involving pagination.\n\nThe pager can work without knowing the total count, but if the count is\navailable, the pager will use it:\n\n![last page with count](https://rekalogika.dev/rekapager/last-with-count.png)\n\nIt can query the count from the underlying data, or the caller can supply the\ncount. The count can also be an approximation, and the pager will work without\nan exact count.\n\n## Offset Pagination\n\nThe library also supports the traditional offset pagination method with several\nimportant improvements. \n\n### No Counting by Default\n\nIt can paginate without the total count of the data. If the count is not known,\nthe pager won't allow the user to navigate to the last page:\n\n![no last page](https://rekalogika.dev/rekapager/unknown-last.png)\n\nAs with keyset pagination, the count can be supplied by the caller, or the pager\ncan query the count from the underlying data.\n\n### Page Number Limit\n\nIt also limits the maximum page number that can be navigated to. By default, the\nlimit is 100. The UI will indicate that the disabled page exists, but the user\nis not allowed to navigate to it:\n\n![page limit](https://rekalogika.dev/rekapager/limit.png)\n\nThis limit can be configured, or disabled entirely. But if you need the function\nto seek beyond a certain number of pages, you should consider switching to\nkeyset pagination instead.\n\n### Pagerfanta Interoperability\n\nFor interoperability, the library supports offset pagination using any of the\nexisting Pagerfanta adapters, as well as adapting a Pagerfanta instance into an\n`OffsetPageableInterface` instance.\n\n### Same Interface for Both Offset and Keyset Pagination\n\nBoth offset pagination and keyset pagination use the same interface. This allows\nyou to keep using offset pagination and later switch to keyset pagination\nwithout too much effort.\n\n## Secure by Default (or Prevents AI Crawlers from Wreaking Havoc)\n\nWith keyset pagination, not counting by default, and limiting pages in\noffset pagination, the library is secure by default. It prevents denials of\nservice, either maliciously or accidentally. In most cases, a real user won't\nhave a good reason for accessing page 56267264, but doing so can cause a denial\nof service to the web server, application, and the database.\n\nAfter the AI craze, there is a surge of web crawlers that are looking for\ncontents for AI training. Unlike traditional search engine crawlers, these new\ncrawlers tend to be much dumber and much less respectful. Some would foolishly\ntraverse thousands of paginated contents with a sub-second delay, causing a\ndenial of service to the server. If your application is public and uses\npagination, this library can help to prevent this problem.\n\n## Supported Data Types\n\n* Doctrine Collections `Selectable` and `Collection`\n* Doctrine ORM `QueryBuilder` and `NativeQuery`\n* Doctrine DBAL `QueryBuilder`\n* Pagerfanta adapters\n\n## Framework Integrations\n\n* Symfony\n* API Platform\n\n## Usage\n\n### Transforming the underlying data into a `PageableInterface` object\n\nThis part is framework-independent.\n\n```php\nuse Doctrine\\DBAL\\Types\\Types;\nuse Doctrine\\ORM\\EntityRepository;\nuse Rekalogika\\Rekapager\\Doctrine\\ORM\\QueryBuilderAdapter;\nuse Rekalogika\\Rekapager\\Keyset\\KeysetPageable;\nuse Rekalogika\\Rekapager\\Offset\\OffsetPageable;\n\n// The underlying data in this example is a Doctrine ORM QueryBuilder\n\n/** @var EntityRepository $postRepository */\n\n$queryBuilder = $postRepository\n    -\u003ecreateQueryBuilder('p')\n    -\u003ewhere('p.group = :group')\n    -\u003esetParameter('group', $group)\n    -\u003eaddOrderBy('p.date', 'DESC')\n    -\u003eaddOrderBy('p.title', 'ASC')\n    -\u003eaddOrderBy('p.id', 'ASC');\n\n// The adapter provides an uniform interface for the different types of\n// underlying data collection\n\n$adapter = new QueryBuilderAdapter(\n    queryBuilder: $queryBuilder,\n    typeMapping: [\n        'p.date' =\u003e Types::DATE_MUTABLE\n    ]\n),\n\n// A pageable represents something that can be partitioned into pages. This\n// example uses KeysetPageable, which is a pageable that supports keyset\n// pagination.\n\n$pageable = new KeysetPageable(\n    adapter: $adapter,\n    itemsPerPage: $itemsPerPage,\n    count: $count,\n);\n\n// There is also an OffsetPageable for offset pagination. An adapter can\n// support either or both types of pagination.\n\n$pageable = new OffsetPageable(\n    adapter: $adapter,\n    itemsPerPage: $itemsPerPage,\n    count: $count,\n);\n```\n\n### Transforming the `PageableInterface` into a `PagerInterface` object\n\nIn this phase, we start involving the framework used in the application. The\nexample below uses Symfony integration provided by\n`rekalogika/rekapager-bundle`.\n\n```php\nuse Rekalogika\\Rekapager\\Bundle\\Contracts\\PagerFactoryInterface;\nuse Symfony\\Component\\HttpFoundation\\Request;\n\n/** @var PagerFactoryInterface $pagerFactory */\n/** @var Request $request */\n\n// The pager factory is a service that creates a PagerInterface from a\n// PageableInterface\n\n$pager = $pagerFactory-\u003ecreateFromPageable(\n    pageable: $pageable,\n    request: $request,\n    options: new PagerOptions(\n        proximity: 3,\n    )\n);\n\n$currentPage = $pager-\u003egetCurrentPage();\n\nforeach ($currentPage as $item) {\n    // Do something with the item\n}\n```\n\n### Rendering the Pager\n\nThe `PagerInterface` object contains all the necessary information to render the\npagination control in the user interface. The example below uses the Twig\nintegration provided by `rekalogika/rekapager-bundle`.\n\n```twig\n{# Outputs the item from the current page #}\n\n\u003ctable class=\"table\"\u003e\n    \u003cthead\u003e\n        \u003ctr\u003e\n            \u003cth\u003eID\u003c/th\u003e\n            \u003cth\u003eTitle\u003c/th\u003e\n            \u003cth\u003eDate\u003c/th\u003e\n            \u003cth\u003eContent\u003c/th\u003e\n        \u003c/tr\u003e\n    \u003c/thead\u003e\n    \u003ctbody {{ rekapager_infinite_scrolling_content() }}\u003e\n        {% for post in pager.currentPage %}\n            \u003ctr\u003e\n                \u003ctd\u003e{{ post.id }}\u003c/td\u003e\n                \u003ctd\u003e{{ post.title }}\u003c/td\u003e\n                \u003ctd\u003e{{ post.date|date('Y-m-d') }}\u003c/td\u003e\n                \u003ctd\u003e{{ post.content }}\u003c/td\u003e\n            \u003c/tr\u003e\n        {% endfor %}\n    \u003c/tbody\u003e\n\u003c/table\u003e\n\n{# Render the pager #}\n\n{{ rekapager(pager) }}\n```\n\n## Batch Processing\n\nA `PageableInterface` object can also be used for batch processing a large\namount of underlying data. The example below demonstrates how to do batch\nprocessing using Doctrine.\n\n```php\nuse Doctrine\\ORM\\EntityManagerInterface;\nuse Rekalogika\\Rekapager\\PageableInterface;\n\n/** @var PageableInterface $pageable */\n/** @var EntityManagerInterface $entityManager */\n\nforeach ($pageable-\u003ewithItemsPerPage(1000)-\u003egetPages() as $page) {\n    foreach ($page as $item) {\n        // Do something with the item\n    }\n\n    // Do something after each page here\n    $entityManager-\u003eflush(); // if required\n    $entitymanager-\u003eclear();\n}\n```\n\nThere is also a console command framework, so you can easily create console\ncommands to run your batch jobs. Your console commands will come with all these\nfeatures: informative output, batch process resuming, progress file, running the\ncommand up to the specified duration, signal handling, and more.\n\n## Demo\n\nYou can try the demo by running the following command:\n\n```bash\ndocker run --rm -p 8187:80 ghcr.io/rekalogika/rekapager:latest\n```\n\nThen access the demo at [http://localhost:8187](http://localhost:8187).\n\n## Acknowledgements\n\n* [Use the Index, Luke](https://use-the-index-luke.com/no-offset)\n* [Pagerfanta](https://www.babdev.com/open-source/packages/pagerfanta/docs/4.x/intro)\n* [PagerWave](https://gitlab.com/pagerwave/PagerWave)\n* [fast-doctrine-paginator](https://github.com/mentionapp/fast-doctrine-paginator)\n\n## Documentation\n\n[rekalogika.dev/rekapager](https://rekalogika.dev/rekapager)\n\n## License\n\nMIT\n\n## Contributing\n\nThis framework consists of multiple repositories split from a monorepo. Be\nsure to submit issues and pull requests to the\n[`rekalogika/rekapager`](https://github.com/rekalogika/rekapager) monorepo.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frekalogika%2Frekapager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frekalogika%2Frekapager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frekalogika%2Frekapager/lists"}