Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Pagerfanta Laravel Package

nitra/pagerfanta

Laravel integration for the Pagerfanta pagination library. It bridges Eloquent and query builder results with Pagerfanta paginators, provides Blade-friendly rendering and configurable templates, and makes it easy to add consistent pagination to Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a legacy fork of the original Pagerfanta library (Pablo Díez / White October), pinned to PHP 5.3-era syntax and published under the Nitra vendor prefix. Treat it as a drop-in Pagerfanta\Pagerfanta core with a small set of adapters and PHP view renderers — not a Symfony bundle, not a maintained package. Before anything else, verify what you actually checked out:

  1. Open the main class file first. Find Pagerfanta (typically src/Pagerfanta/Pagerfanta.php, or lib/… in older layouts) and note the namespace. The fork may keep Pagerfanta\ or rewrite it to Nitra\Pagerfanta\; every use statement downstream depends on this.
  2. Check for composer.json. If present, confirm the autoload section (psr-0 is likely given the era) and the exact package name before running composer require nitra/pagerfanta. If absent, you will be wiring a legacy require_once/spl_autoload_register block by hand.
  3. Inventory the adapters. Look under Adapter/ — you should find ArrayAdapter, DoctrineORMAdapter (or DoctrineAdapter), possibly DoctrineODMAdapter and ModelAdapter, plus the AdapterInterface. This tells you what your data layer options are.
  4. Inventory the views. Look under View/ for DefaultView (and possibly a Bootstrap variant). If only DefaultView exists, plan on writing your own markup wrapper.

First use case — paginate an array. This is the fastest sanity check that autoloading, namespacing and the view layer are wired correctly before you touch Doctrine:

use Pagerfanta\Pagerfanta;
use Pagerfanta\Adapter\ArrayAdapter;
use Pagerfanta\View\DefaultView;

$pager = new Pagerfanta(new ArrayAdapter($items));
$pager->setMaxPerPage(20);
$pager->setCurrentPage(isset($_GET['page']) ? (int) $_GET['page'] : 1);

$pageItems = $pager->getCurrentPageResults();

$view = new DefaultView();
echo $view->render($pager, function ($page) {
    return '?page=' . $page;
});

If that renders links and $pageItems contains the right 20 rows, the package is functional and you can move on to a real adapter.

Implementation Patterns

Adapter-first architecture. Everything funnels through AdapterInterface::getNbResults() and getSlice($offset, $length). The Pagerfanta object never touches your data source directly. Day-to-day, you will almost always write or configure an adapter rather than subclass Pagerfanta.

  • Array / in-memory data: ArrayAdapter — fine for admin screens, small config lists, or tests. It materializes the whole set, so do not point it at a large table.
  • Doctrine ORM: build a QueryBuilder with your filters and ORDER BY, hand it to the ORM adapter, and let it clone the query, strip SELECT/setMaxResults, and run a COUNT plus a sliced fetch. Always apply a deterministic ORDER BY — pagination over an unordered result set silently duplicates or skips rows.
  • Custom source: implement the two-method interface yourself. This is the clean hook for a raw PDO/DBAL query, a REST API call, or a legacy Nitra model layer:
class LegacySqlAdapter implements AdapterInterface
{
    public function getNbResults()
    {
        // SELECT COUNT(*) FROM ... WHERE ...
    }

    public function getSlice($offset, $length)
    {
        // SELECT ... LIMIT $length OFFSET $offset
    }
}

Controller / action pattern. Standard flow in a controller or service:

  1. Parse page from the request, cast to int, default to 1, and guard against 0 and negatives (max(1, $page)).
  2. Build the adapter from a query whose filters are already applied.
  3. setMaxPerPage() — read it from config or a per_page request param, but clamp it to an allow-list so a user cannot request 100,000 rows.
  4. setCurrentPage() only after haveToPaginate()/getNbPages() is known, or wrap in a try/catch (see Gotchas).
  5. Pass both the pager (for links) and getCurrentPageResults() (for the loop) to the template.

View rendering. ViewInterface::render() takes the pager, a route generator closure, and an options array. The closure must return an absolute or relative URL for a given page number. Because this is PHP 5.3-era code, remember:

$router = $this->router;                 // capture first
$urlGenerator = function ($page) use ($router, $categoryId) {
    return $router->generate('list', array('page' => $page, 'cat' => $categoryId));
};

You cannot use $this inside a closure under PHP 5.3 — capture $this->router into a local first. Useful options passed to render(): proximity (how many page links around the current one), prev_message, next_message, and the CSS container class. If your template engine supports it, wrap the render call in a helper so every list view gets identical markup.

Twig / template integration. This fork does not ship a Symfony bundle or Twig extension. Register a small Twig function or a view helper in your framework's service container that accepts a Pagerfanta instance and a route name, builds the closure internally, and returns the rendered HTML marked safe.

State preservation. The route generator is where you keep filters, sort order and search terms alive across pages. Pass every non-page query parameter through explicitly; losing a filter on page 2 is the single most common complaint about paginated UIs.

Count caching. getNbResults() runs a COUNT(*) on every page view. For heavy tables, cache the count for a short TTL keyed on the filter hash, or return from a cheaper source (a pre-aggregated table) inside your custom adapter's getNbResults().

Gotchas and Tips

  • setCurrentPage() out of range throws. Pagerfanta throws an out-of-range exception when the page exceeds getNbPages(). Two safe patterns: clamp first with min($page, max(1, $pager->getNbPages())), or catch the exception and redirect to page 1 or the last page. Do not let it bubble to a 500 — crawlers and stale bookmarks will hit it constantly.
  • Zero results is a special case. getNbPages() returns 0 when there are no results. Guard any min($page, $pager->getNbPages()) logic against 0 producing an invalid current page, and in templates check $pager->getNbResults() > 0 before rendering the link block at all.
  • The Doctrine adapter mutates expectations, not your query. It clones the QueryBuilder; if you reuse the same builder elsewhere after handing it over, re-verify the state you get back. Hydration mode matters too — getArrayResult() versus getResult() is controlled by the adapter version, so inspect the source rather than assuming entity objects come out.
  • ORDER BY is mandatory and must be unique-ish. Add a tiebreaker column (usually the primary key) to any ordering used with pagination, otherwise rows can repeat across pages under concurrent writes.
  • PHP 5.3 syntax ceiling. Any adapter, view or trait-like helper you contribute must avoid short array syntax, ::class, yield, variadics, and $this inside closures. If you plan to modernize, that is a strong argument to switch to pagerfanta/pagerfanta instead of patching this fork.
  • Namespace and autoload drift. Forks of this vintage frequently rename the root namespace or use psr-0 with a directory structure that does not match the namespace. Symptom: Class 'Pagerfanta\Pagerfanta' not found despite the file existing. Fix the composer autoload map or the require path — do not add require_once scattered through your codebase as a workaround.
  • No composer install = no dependency resolution. If there is no composer.json, you own transitive dependencies (Doctrine version, PSR interfaces) manually. Pin them in your own root composer.json and exclude the package from autoloading surprises.
  • Escaping. View renderers concatenate HTML. If you pass user-controlled prev_message/next_message or route parameters derived from input, escape them yourself or you have XSS in the pager markup.
  • Accessibility and SEO. The default markup is a plain list of links with no rel="prev"/rel="next", no aria-label on the nav, and no handling of ?page= canonicalization. Add these in a custom view subclass (override render() and call the parent, or copy DefaultView into your app namespace — preferable, since you can then drop the dependency on the package's view layer entirely).
  • Abandonment risk. Zero stars, zero dependents, a Bitbucket repository, and a PHP 5.3 baseline means no security patches, no PHP 8 compatibility, and no ecosystem answers when something breaks. If you inherit it, isolate it behind your own adapter/renderer interfaces so swapping in modern Pagerfanta (or a simple hand-rolled paginator) later is a one-file change. Do not build new features directly on its view classes.
  • Debugging tip. When page links look wrong, echo the output of your route-generator closure for pages 1, 2 and the last page before rendering. Nine times out of ten the pager is correct and the closure is dropping a query parameter.
  • Extension point to prefer. If you need custom link markup, custom page-number windows, or framework-specific routing, subclass DefaultView and pass your own options through render(). If you need a different data source, implement AdapterInterface. Avoid modifying the vendored files — you will never be able to update the fork cleanly.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route