Často narážím na problém, kdy si přes Eloquent vytáhnu data pro paginaci, ale kvůli složitější logice nejsem schopen vše vyřešit již při vykonávání dotazu. Vzniká tak problém jak data upravit, ale zároveň stále zachovat celou funkčnost paginace od Laravelu.

Problém

Prvně si vysvětlíme kde vzniká problém. Půjdeme to nejjednodušší cestou, například máme v databázi články a budeme chtít zobrazit jejich název velkými písmeny. Žádná magie, nicméně si představme, že toto nejsem schopni vyřešit již na straně Eloquentu a získáme tak názvy článků s malými písmeny. Jako první nás může napadnout výsledek transformovat (protože nepotřebujeme získat novou kolekci):

    $posts = Post::paginate(1);

    $posts = $posts->transform(function(Post $post) {
        $post->title = Str::upper($post->title);

        return $post;
    });

A tady hned vzniká problém, konkrétně nám vyhodí chybu Method Illuminate\Database\Eloquent\Collection::links does not exist. Chyba vznikla kvůli tomu, že ačkoliv paginace podporuje všechny metody kolekce, tak metody naopak vracejí pouze "obyčejnou" kolekci namísto třídy Illuminate\Pagination\LengthAwarePaginator, která mimo jiné obsahuje i metodu links(), která vykresluje paginaci v šabloně.

Řešení

Existují dvě možnosti jak toto vyřešit. První možností je vytvořit novou instanci paginatoru a vložit data do ní:

new LengthAwarePaginator($items, $total, $perPage, $currentPage);

Ano, bude to fungovat, nicméně tím vzniká další problém, který je potřeba řešit, a to na jaké stránce jsme aby jsme podle toho získali potřebné data pro limit/offset. Celé řešení se najednou komplikuje a vzniká z toho poměrně komplexní logika.

Druhá možnost, o dost jednodušší, je využít metod getCollection a setCollection. Tato možnost bohužel není v oficiální dokumentaci Laravelu popsána, nicméně ji najdete v Laravel API:

    $posts = Post::paginate(1);

    $toTransform = $posts->getCollection();

    $itemsTransformed = $toTransform->transform(function(Post $post) {
        $post->title = Str::upper($post->title);

        return $post;
    });

    $posts->setCollection($itemsTransformed);

Myslím, že kód není moc potřeba vysvětlovat. Z paginace získáme kolekci, tu si klasickým způsobem upravíme, a nakonec ji paginátoru opět nastavíme. Získáme tak zcela novou kolekci dat v paginaci, kterou můžeme využít. Můžeme tak data nejen upravovat, ale také do položek přidávat nové.

Tohle řešení mi často ulehčilo práci, kdy jsem nemusel složitě řešit věci přes Eloquent (či klasický raw SQL dotaz), jen abych data dostal rovnou ve správném formátu pro paginaci. Není to samozřejmě silver bullet pro všechno, ale ve většině případů si myslím, že se využije.

A nakonec opět demo na Laravel Playground.

Máte dotazy, mám někde chybu nebo máte jiné řešení? Napište mi do komentářů!