Laravel Fractal pagination with simplePaginate

Posted by Danny Herran on Mar 8, 2019 in Backend | No comments
Laravel Fractal

By default Fractal has native support for Laravel::paginate. Those using simplePaginate() have to resort to either using Fractal Cursors or build their own adapter. In this post, we are going to quickly build an adapter that fully supports Laravel::simplePaginate.

This tutorial assumes you are using the amazing Laravel Fractal wrapper provided by the guys at Spatie.

Start by creating a new paginator adapter within any folder of your application. For this example, we’ll store it in App\Http\Transformers as that’s where I normally store stuff that’s Transformer related:

<?php

/**
 * This is a custom implementation of League\Fractal\Pagination\IlluminatePaginatorAdapter
 *
 * As of March 2019 Fractal doesn't have a paginator for Laravel simplePaginate()
 */

namespace App\Http\Transformers;

use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Contracts\Pagination\Paginator;
use League\Fractal\Pagination\PaginatorInterface;

/**
 * A paginator adapter for illuminate/pagination.
 *
 * @author Danny Herran <me@dannyherran.com>
 */
class IlluminatePaginatorAdapter implements PaginatorInterface
{
    /**
     * The paginator instance.
     *
     * @var \Illuminate\Contracts\Pagination\Paginator
     */
    protected $paginator;

    /**
     * Create a new illuminate pagination adapter.
     *
     * @param \Illuminate\Contracts\Pagination\Paginator $paginator
     *
     * @return void
     */
    public function __construct(Paginator $paginator)
    {
        $this->paginator = $paginator;
    }

    /**
     * Get the current page.
     *
     * @return int
     */
    public function getCurrentPage()
    {
        return $this->paginator->currentPage();
    }

    /**
     * Get the last page.
     *
     * @return int
     */
    public function getLastPage()
    {
        return $this->paginator->hasMorePages() ? $this->getCurrentPage() + 1 : $this->getCurrentPage();
    }

    /**
     * Get the total.
     *
     * @return int
     */
    public function getTotal()
    {
        return null;
    }

    /**
     * Get the count.
     *
     * @return int
     */
    public function getCount()
    {
        return $this->paginator->count();
    }

    /**
     * Get the number per page.
     *
     * @return int
     */
    public function getPerPage()
    {
        return $this->paginator->perPage();
    }

    /**
     * Get the url for the given page.
     *
     * @param int $page
     *
     * @return string
     */
    public function getUrl($page)
    {
        return $this->paginator->url($page);
    }

    /**
     * Get the paginator instance.
     *
     * @return \Illuminate\Contracts\Pagination\Paginator
     */
    public function getPaginator()
    {
        return $this->paginator;
    }
}

Point your Controller or Service library to the new adapter and use it in the Fractal transformation like so:

<?php
namespace App\Http\Controllers;

use App\Book;
use App\Http\Transformers\BookTransformer;
use App\Http\Transformers\IlluminatePaginatorAdapter;

class BookController extends Controller
{
    public function indexThings()
    {
        $paginator = Book::paginate();
        $books = $paginator->getCollection();

        $response = fractal()
                    ->collection($books, new BookTransformer())
                    ->paginateWith(new IlluminatePaginatorAdapter($paginator))
                    ->toArray();

        return response()->json($response);
    }
}

But here is the kicker: depending on your default serializer, you may end up with a botched result like this:

{
   "0": {"foo": "bar"},
   "1": {"foo": "bar"},
   "2": {"foo": "bar"},
   "3": {"foo": "bar"},
   "4": {"foo": "bar"},
   "meta":{
      "pagination":{
         "total":0,
         "count":10,
         "per_page":10,
         "current_page":1,
         "total_pages":2,
         "links":{
            "next":"http:\/\/myapp.test\/api\/nicepage?page=2"
         }
      }
   }
}

And that is bad! If you had a frontend application looping through that object, it will crash when it encounters the meta object. To solve this, just point your transformation to the default ArraySerializer that comes packed with Fractal, like so:

<?php
namespace App\Http\Controllers;

use App\Book;
use App\Http\Transformers\BookTransformer;
use App\Http\Transformers\IlluminatePaginatorAdapter;
use League\Fractal\Serializer\ArraySerializer;

class BookController extends Controller
{
    public function indexThings()
    {
        $paginator = Book::paginate();
        $books = $paginator->getCollection();

        $response = fractal()
                    ->collection($books, new BookTransformer())
                    ->serializeWith(new ArraySerializer)
                    ->paginateWith(new IlluminatePaginatorAdapter($paginator))
                    ->toArray();

        return response()->json($response);
    }
}

Your end result will be something like this:

{
   "data":[
      {"foo": "bar"},
      {"foo": "bar"},
      {"foo": "bar"},
      {"foo": "bar"},
      {"foo": "bar"}
   ],
   "meta":{
      "pagination":{
         "total":0,
         "count":10,
         "per_page":10,
         "current_page":1,
         "total_pages":2,
         "links":{
            "next":"http:\/\/myapp.test\/api\/nicepage?page=2"
         }
      }
   }
}

So in your application, loop through the data array and grab the pagination metadata from the meta object.

Enjoy!