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

Pager Bundle Laravel Package

smilingplants/pager-bundle

smilingplants/pager-bundle provides lightweight pagination for PHP applications, helping split large result sets into manageable pages for lists, search results, and admin screens. It offers a simple pager API with configurable page sizes and minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before you type composer require. smilingplants/pager-bundle is a Symfony 2 / Twig pager bundle, not a Laravel package. It registers through AppKernel::registerBundles(), ships Twig templates, and will almost certainly fail Composer resolution on PHP 8 / Laravel 10–11 because its composer.json pins symfony/* to a 2.x range. There is no service provider, no facade, no Blade view — installing it into a Laravel app is not a supported path.

What you can do day-to-day is treat the repo as a design reference and build the pager on Laravel's native primitives. That is the realistic "first use case."

Minimal steps (Laravel):

  1. Read, don't install. Clone the repo and skim three things in order:
    • Resources/views/ — the pager markup (window logic, first/last/ellipsis, disabled states).
    • Twig/ — the extension that builds page ranges and computes offset/limit.
    • Resources/config/services.yml — service wiring, mostly to confirm how thin the abstraction really is.
  2. Use Laravel's paginator as the substrate.
    $posts = Post::query()
        ->when($request->q, fn ($q, $s) => $q->where('title', 'like', "%{$s}%"))
        ->orderByDesc('created_at')
        ->paginate(15)
        ->withQueryString();
    
  3. Override the view, not the paginator. Publish or point at a custom Blade partial that mirrors the bundle's markup:
    {{ $posts->onEachSide(2)->links('vendor.pagination.pager') }}
    
  4. First useful outcome: a working pager on one index page, with page-window size, first/last, and query-string preservation — matching the bundle's UX in an afternoon, with zero dependency risk.

If you genuinely must run this inside a Symfony 2 app, install with dev-master, register SmilingPlants\PagerBundle\SmilingPlantsPagerBundle in AppKernel, then read services.yml to find the pager service id (it is not guessable) before writing any controller code.

Implementation Patterns

The pattern that matters: reimplement the intent, not the code. The bundle's only durable value is its pager behavior — window sizing, ellipsis rules, edge-case handling for empty results and single-page results. That lives in a handful of lines of Twig and PHP and is trivially expressible in Blade + LengthAwarePaginator. Copy the interaction spec; discard the implementation.

Custom pager view (mirrors the bundle's markup contract).

{{-- resources/views/vendor/pagination/pager.blade.php --}}
@if ($paginator->hasPages())
    <nav role="navigation" aria-label="Pagination">
        <ul class="pager">
            {{-- Previous --}}
            @if ($paginator->onFirstPage())
                <li class="disabled" aria-disabled="true"><span>&laquo;</span></li>
            @else
                <li><a href="{{ $paginator->previousPageUrl() }}" rel="prev">&laquo;</a></li>
            @endif

            {{-- Window --}}
            @foreach ($elements as $element)
                @if (is_string($element))
                    <li class="disabled"><span>{{ $element }}</span></li>
                @endif
                @if (is_array($element))
                    @foreach ($element as $page => $url)
                        @if ($page == $paginator->currentPage())
                            <li class="active" aria-current="page"><span>{{ $page }}</span></li>
                        @else
                            <li><a href="{{ $url }}">{{ $page }}</a></li>
                        @endif
                    @endforeach
                @endif
            @endforeach

            {{-- Next --}}
            @if ($paginator->hasMorePages())
                <li><a href="{{ $paginator->nextPageUrl() }}" rel="next">&raquo;</a></li>
            @else
                <li class="disabled" aria-disabled="true"><span>&raquo;</span></li>
            @endif
        </ul>
    </nav>
@endif

Reusable page-window helper (when you need the bundle's offset/limit math outside Blade).

// app/Support/Pagination/PageWindow.php
namespace App\Support\Pagination;

final class PageWindow
{
    public static function offset(int $page, int $perPage): int
    {
        return max(0, ($page - 1) * $perPage);
    }

    public static function range(int $page, int $perPage, int $total): array
    {
        if ($total === 0) {
            return [0, 0, 0]; // offset, limit, lastPage
        }
        $last  = (int) ceil($total / $perPage);
        $page  = min(max(1, $page), $last);
        return [self::offset($page, $perPage), $perPage, $last];
    }
}

Pattern: cursor pagination when tables grow. The bundle only models offset paging, which degrades at scale. Laravel's cursorPaginate() is the correct upgrade and requires no pager markup changes if you keep the view abstract:

$posts = Post::orderByDesc('id')->cursorPaginate(15);
// in Blade: $posts->previousPageUrl() / $posts->nextPageUrl() still work

Pattern: multi-page contexts on one screen. If a dashboard shows two independently paginated lists (e.g. Posts and Comments), give each its own query-string prefix instead of fighting a shared page param:

Post::paginate(10, ['*'], 'posts_page');
Comment::paginate(10, ['*'], 'comments_page');

The Symfony bundle would require you to instantiate two pager services and manually reconcile params — Laravel does this natively via the third argument.

Integration tip: keep the paginator out of the controller body. If the pager logic is starting to leak into controllers (matching the Symfony bundle's $pager->setTotalItems(...)->setOffset(...) style), that's a signal to move to a scoped query builder or a PaginationRequest DTO. The bundle's imperative setters are a pattern to unlearn — Laravel's paginator returns the data and metadata together.

Gotchas and Tips

The package is Symfony 2-only — do not force it into Laravel. composer require will fail against modern PHP/Symfony constraints. Do not use --ignore-platform-reqs to get past it. That defers an end-of-life Twig 1.x dependency (with published security advisories) into runtime, where a template-injection vector becomes your incident, not your build's.

Query string loss is the classic bug. Laravel pagination drops existing filters by default. Every paginated query that reads from $request->query(...) needs ->withQueryString(). The Symfony bundle handles this inside its link builder; in Laravel it's a per-call opt-in. Add a test asserting filters survive the ?page=2 navigation.

$elements vs $paginator->url() — pick one. $elements gives you the windowed list with "..." as a string element; iterating 1..lastPage manually gives you full control but ignores onEachSide(). Mixing them produces duplicate or mis-numbered links. Stick with $elements for standard cases.

Escaping semantics differ. Twig's autoescape and Blade's {{ }} are conceptually similar but not identical when you build URLs from raw query strings. If you copy Symfony template output verbatim, ensure anything interpolated into href is passed through {{ }} (escaped) — not {!! !!} — and validate page params as integers before they reach the URL builder.

Edge cases to explicitly handle (the bundle covers these; Blade partials often don't):

  • Zero results — hide the <nav> entirely (@if ($paginator->hasPages())).
  • Exactly one page of results — same as above; no pager is correct.
  • Page param out of range — Laravel clamps internally, but custom link builders that compute ceil($total / $perPage) manually will render a broken "last" link. Use $paginator->lastPage(), not your own math.
  • Negative or 0 page params — cast with $request->query->getInt('page', 1) and clamp with max(1, ...).

onEachSide() is cheap, but don't over-tune it. Renders 2 * onEachSide + 1 windowed links plus ellipsis. Values above 3 produce wide pagers on mobile and defeat the "window" concept. The Symfony bundle's window size is a config default; in Laravel it's a fluent call — keep it a named constant in a view composer or config so every pager in the app stays consistent.

Debugging pagination issues — order of checks:

  1. Is the query returning the expected total count? (->total() before ->links().)
  2. Is withQueryString() present? (Check url()->current() inside the view.)
  3. Is the paginator instance being reused across queries in the same request? (You cannot — a LengthAwarePaginator is single-use data-bound.)
  4. Does the Blade view use $paginator->url($page) or raw string building? Raw strings are the usual culprit for dropped params.

Extension points you should keep (even though the bundle's are unusable):

  • A single publishable Blade view under resources/views/vendor/pagination/ so styling is centralized.
  • A config/pagination.php for default per-page and onEachSide values, read via config() in view composers. This is the Laravel-native equivalent of the Symfony bundle's DI parameters and gives you one knob to change behavior app-wide.

When to escalate to keyset pagination. Offset paging (LIMIT ... OFFSET ...) scans and discards rows; performance collapses past ~100k rows. If a page in your app is slow because of pagination, no pager package — this one or Laravel's — fixes it. Move to cursorPaginate() at the query layer, and accept that the UI becomes prev/next-only. The Symfony bundle has no answer for this; that's a signal, not a limitation.

Ramp-up asymmetry is the hidden cost. The concept of a pager is a 20-minute ramp for any PHP developer. This artifact requires reverse-engineering a dead Symfony 2 bundle's conventions — a day of friction that produces nothing reusable. Prefer the native path: ->links('vendor.pagination.pager') is immediately legible to any Laravel hire, and the maintenance burden stays inside the framework's upgrade surface, not yours.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core